Last active
May 25, 2025 11:15
-
-
Save isXander/c969700ee7206dc068f69cbe91dd155f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi; | |
import net.minecraft.resources.ResourceLocation; | |
public interface HudLayerRegistry { | |
void renderFirst(ResourceLocation thisElement, HudLayerRenderer renderer); | |
void renderLast(ResourceLocation thisElement, HudLayerRenderer renderer); | |
/** | |
* Renders the given element before the specified element. | |
* If that element does not exist by the end of registration, an exception will be thrown. | |
* @param before the identifier of the element to render before | |
* @param thisElement the identifier of this element to render | |
* @param renderer the renderer to use for this element | |
*/ | |
void renderBefore(ResourceLocation before, ResourceLocation thisElement, HudLayerRenderer renderer); | |
/** | |
* Renders the given element before the specified element, if that element exists. | |
* If that element does not exist, the given element will not be rendered without warning or error. | |
* @param before the identifier of the element to render before | |
* @param thisElement the identifier of this element to render | |
* @param renderer the renderer to use for this element | |
*/ | |
void renderBeforeMaybe(ResourceLocation before, ResourceLocation thisElement, HudLayerRenderer renderer); | |
/** | |
* Renders the given element after the specified element. | |
* If that element does not exist by the end of registration, an exception will be thrown. | |
* @param after the identifier of the element to render after | |
* @param thisElement the identifier of this element to render | |
* @param renderer the renderer to use for this element | |
*/ | |
void renderAfter(ResourceLocation after, ResourceLocation thisElement, HudLayerRenderer renderer); | |
/** | |
* Renders the given element after the specified element. | |
* If that element does not exist, the given element will not be rendered without warning or error. | |
* @param after the identifier of the element to render after | |
* @param thisElement the identifier of this element to render | |
* @param renderer the renderer to use for this element | |
*/ | |
void renderAfterMaybe(ResourceLocation after, ResourceLocation thisElement, HudLayerRenderer renderer); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi; | |
import net.minecraft.client.DeltaTracker; | |
import net.minecraft.client.gui.GuiGraphics; | |
@FunctionalInterface | |
public interface HudLayerRenderer { | |
void render(GuiGraphics graphics, DeltaTracker deltaTracker); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi; | |
import net.fabricmc.fabric.api.event.Event; | |
import net.fabricmc.fabric.api.event.EventFactory; | |
@FunctionalInterface | |
public interface HudRegistrationCallback { | |
Event<HudRegistrationCallback> EVENT = EventFactory.createArrayBacked(HudRegistrationCallback.class, | |
listeners -> registry -> { | |
for (HudRegistrationCallback listener : listeners) { | |
listener.register(registry); | |
} | |
}); | |
void register(HudLayerRegistry registry); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi; | |
import net.minecraft.resources.ResourceLocation; | |
public class HudRegistrationException extends RuntimeException { | |
private final ResourceLocation layerId; | |
public HudRegistrationException(ResourceLocation layerId, String message) { | |
super(message); | |
this.layerId = layerId; | |
} | |
public ResourceLocation getLayerId() { | |
return layerId; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi; | |
import net.minecraft.resources.ResourceLocation; | |
public final class VanillaHudLayers { | |
private VanillaHudLayers() {} | |
public static final ResourceLocation FIRST = ResourceLocation.withDefaultNamespace("first"); | |
public static final ResourceLocation HEALTH = ResourceLocation.withDefaultNamespace("health"); | |
// etc... | |
public static final ResourceLocation LAST = ResourceLocation.withDefaultNamespace("last"); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi.impl; | |
import dev.isxander.controlify.platform.client.fabric.hudapi.HudLayerRenderer; | |
import net.fabricmc.fabric.api.event.Event; | |
import net.fabricmc.fabric.api.event.EventFactory; | |
import net.minecraft.resources.ResourceLocation; | |
public class HudLayer { | |
private final ResourceLocation id; | |
private final Event<HudLayerRenderer> beforeEvent; | |
private final Event<HudLayerRenderer> afterEvent; | |
private boolean hasBeforeInvoker, hasAfterInvoker; | |
private boolean lenient; | |
public HudLayer(ResourceLocation id, boolean lenient) { | |
this.id = id; | |
this.beforeEvent = createEvent(); | |
this.afterEvent = createEvent(); | |
this.lenient = lenient; | |
} | |
public ResourceLocation getId() { | |
return this.id; | |
} | |
public Event<HudLayerRenderer> getBeforeEvent() { | |
return this.beforeEvent; | |
} | |
public Event<HudLayerRenderer> getAfterEvent() { | |
return this.afterEvent; | |
} | |
public void markHasBeforeInvoker() { | |
this.hasBeforeInvoker = true; | |
} | |
public void markHasAfterInvoker() { | |
this.hasAfterInvoker = true; | |
} | |
public boolean hasBeforeInvoker() { | |
return this.hasBeforeInvoker; | |
} | |
public boolean hasAfterInvoker() { | |
return this.hasAfterInvoker; | |
} | |
public boolean isLenient() { | |
return this.lenient; | |
} | |
public void setLenient(boolean lenient) { | |
this.lenient = lenient; | |
} | |
private static Event<HudLayerRenderer> createEvent() { | |
return EventFactory.createArrayBacked(HudLayerRenderer.class, listeners -> (graphics, deltaTracker) -> { | |
for (HudLayerRenderer listener : listeners) { | |
listener.render(graphics, deltaTracker); | |
} | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.isxander.controlify.platform.client.fabric.hudapi.impl; | |
import dev.isxander.controlify.platform.client.fabric.hudapi.*; | |
import net.fabricmc.fabric.api.event.Event; | |
import net.minecraft.client.DeltaTracker; | |
import net.minecraft.client.gui.GuiGraphics; | |
import net.minecraft.resources.ResourceLocation; | |
import java.util.HashMap; | |
import java.util.Map; | |
public class HudLayerRegistryImpl implements HudLayerRegistry { | |
private final HudLayer firstLayer, lastLayer; | |
private final Map<ResourceLocation, HudLayer> layers = new HashMap<>(); | |
private boolean registrationContext = false; | |
public HudLayerRegistryImpl() { | |
this.firstLayer = new HudLayer(VanillaHudLayers.FIRST, false); | |
this.firstLayer.markHasBeforeInvoker(); | |
this.lastLayer = new HudLayer(VanillaHudLayers.LAST, false); | |
this.lastLayer.markHasAfterInvoker(); | |
this.layers.put(firstLayer.getId(), firstLayer); | |
this.layers.put(lastLayer.getId(), lastLayer); | |
} | |
@Override | |
public void renderFirst(ResourceLocation thisElement, HudLayerRenderer renderer) { | |
this.guardRegistrationContext(thisElement); | |
this.createLayerAtLayer(thisElement, renderer, this.firstLayer.getBeforeEvent(), false); | |
} | |
@Override | |
public void renderLast(ResourceLocation thisElement, HudLayerRenderer renderer) { | |
this.guardRegistrationContext(thisElement); | |
this.createLayerAtLayer(thisElement, renderer, this.lastLayer.getAfterEvent(), false); | |
} | |
@Override | |
public void renderBefore(ResourceLocation before, ResourceLocation thisElement, HudLayerRenderer renderer) { | |
this.guardRegistrationContext(thisElement); | |
this.guardFirstOrLast(before, thisElement, "renderBefore"); | |
this.createLayerAtLayer(thisElement, renderer, this.getOrCreateLayer(before).getBeforeEvent(), false); | |
} | |
@Override | |
public void renderBeforeMaybe(ResourceLocation before, ResourceLocation thisElement, HudLayerRenderer renderer) { | |
this.guardRegistrationContext(thisElement); | |
this.guardFirstOrLast(before, thisElement, "renderBeforeMaybe"); | |
this.createLayerAtLayer(thisElement, renderer, this.getOrCreateLayer(before).getBeforeEvent(), true); | |
} | |
@Override | |
public void renderAfter(ResourceLocation after, ResourceLocation thisElement, HudLayerRenderer renderer) { | |
this.guardRegistrationContext(thisElement); | |
this.guardFirstOrLast(after, thisElement, "renderAfter"); | |
this.createLayerAtLayer(thisElement, renderer, this.getOrCreateLayer(after).getAfterEvent(), false); | |
} | |
@Override | |
public void renderAfterMaybe(ResourceLocation after, ResourceLocation thisElement, HudLayerRenderer renderer) { | |
this.guardRegistrationContext(thisElement); | |
this.guardFirstOrLast(after, thisElement, "renderAfterMaybe"); | |
this.createLayerAtLayer(thisElement, renderer, this.getOrCreateLayer(after).getAfterEvent(), true); | |
} | |
// Called from a mixin | |
public void wrapRenderVanillaLayer(ResourceLocation id, GuiGraphics graphics, DeltaTracker deltaTracker, Runnable vanillaRenderCall) { | |
this.getOrCreateLayer(id).getBeforeEvent().invoker().render(graphics, deltaTracker); | |
vanillaRenderCall.run(); | |
this.getOrCreateLayer(id).getAfterEvent().invoker().render(graphics, deltaTracker); | |
} | |
private void createLayerAtLayer(ResourceLocation id, HudLayerRenderer renderer, Event<HudLayerRenderer> atLayer, boolean lenient) { | |
var layer = new HudLayer(id, lenient); | |
var oldLayer = this.layers.put(id, layer); | |
if (oldLayer != null) { | |
if (oldLayer.hasAfterInvoker() || oldLayer.hasBeforeInvoker()) { | |
throw new HudRegistrationException(id, "Layer with ID " + id + " has already been registered."); | |
} else { | |
layer = oldLayer; | |
layer.setLenient(lenient); | |
} | |
} | |
HudLayer finalLayer = layer; | |
atLayer.register((graphics, deltaTracker) -> { | |
finalLayer.getBeforeEvent().invoker().render(graphics, deltaTracker); | |
finalLayer.markHasBeforeInvoker(); | |
renderer.render(graphics, deltaTracker); | |
finalLayer.getAfterEvent().invoker().render(graphics, deltaTracker); | |
finalLayer.markHasAfterInvoker(); | |
}); | |
} | |
public void doRegistration() { | |
try { | |
this.registrationContext = true; | |
HudRegistrationCallback.EVENT.invoker().register(this); | |
} catch (HudRegistrationException e) { | |
throw new IllegalStateException("Exception during HUD layer registration at layer " + e.getLayerId(), e); | |
} finally { | |
this.registrationContext = false; | |
} | |
// Ensure that all layers that have been registered have an invoker | |
// e.g. someone may have set a layer to render before/after another layer, | |
// but by the end of registration, that layer may not have been registered | |
for (HudLayer layer : this.layers.values()) { | |
if (!layer.isLenient() && (!layer.hasBeforeInvoker() && layer != this.lastLayer) && (!layer.hasAfterInvoker() && layer != this.firstLayer)) { | |
if (layer.getId().getNamespace().equals("minecraft")) { | |
// If the layer is a vanilla layer, we can just skip it, since it's not registered at registration time. | |
continue; | |
} | |
throw new IllegalStateException("Layer " + layer.getId() + " has no invoker registered for it, but it is not lenient. " + | |
"This means that the layer was not registered properly, or it was registered after the registration context ended."); | |
} | |
} | |
} | |
private void guardRegistrationContext(ResourceLocation id) { | |
if (!registrationContext) { | |
throw new HudRegistrationException(id, "Cannot register HUD layers outside of the registration context"); | |
} | |
} | |
private void guardFirstOrLast(ResourceLocation target, ResourceLocation callerLayer, String callerMethod) { | |
boolean isFirst = target.equals(firstLayer.getId()); | |
boolean isLast = target.equals(lastLayer.getId()); | |
if (isFirst || isLast) { | |
// e.g.: "Cannot use renderBefore to target the first layer: my_mod:custom_layer. Use the renderFirst method instead." | |
throw new HudRegistrationException(callerLayer, "Cannot use " + callerMethod + " to target the " + (isFirst ? "first" : "last") + " layer. " + | |
"Use the render" + (isFirst ? "First" : "Last") + " method instead."); | |
} | |
} | |
private HudLayer getOrCreateLayer(ResourceLocation id) { | |
return layers.computeIfAbsent(id, key -> new HudLayer(key, false)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment