Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Hud Render Events #4119

Open
wants to merge 37 commits into
base: 1.21.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9b16164
Translation updates (#4027)
FabricMCBot Aug 26, 2024
3fc0e55
Dont invoke ItemGroupEvents.MODIFY_ENTRIES_ALL for the OP tab, when t…
AshyBoxy Aug 26, 2024
2122d82
After Damage Event (#4051)
TheDeathlyCow Aug 26, 2024
f054fb5
Bump version
modmuss50 Aug 26, 2024
3d2379b
Add `c:animal_foods` tag (#4080)
TelepathicGrunt Sep 10, 2024
aa34100
New Crowdin updates (#4059)
FabricMCBot Sep 10, 2024
4053855
Add missing `minecraft:enchantable/vanishing` to `c:enchantables` (#4…
TelepathicGrunt Sep 10, 2024
d38f898
Add TransferVariant.getComponentMap() (#4074)
modmuss50 Sep 10, 2024
427f7cb
Use unix line endings on all files (#4079)
modmuss50 Sep 10, 2024
e521378
Bump version
modmuss50 Sep 10, 2024
9724bae
Add HudRenderEvents
kevinthegreat1 Sep 23, 2024
584d9e8
Add HudRenderEventsTests and deprecate HudRenderCallback
kevinthegreat1 Sep 25, 2024
a7ac0f1
Update tests
kevinthegreat1 Sep 25, 2024
668c533
Add client parameter and apply suggestions
kevinthegreat1 Sep 25, 2024
f781eb7
Split HudRenderEvents into separate interfaces
kevinthegreat1 Oct 3, 2024
f13daa5
Fix before chat and last
kevinthegreat1 Oct 3, 2024
ce182ed
Add after sleep overlay event and update after main hud injection point
kevinthegreat1 Oct 3, 2024
6362810
Add comments for injection points
kevinthegreat1 Oct 11, 2024
28557b1
Revert splitting HudRenderEvents into separate interfaces
kevinthegreat1 Oct 11, 2024
0d2bcf2
Use vanilla layered drawer layer interface
kevinthegreat1 Oct 23, 2024
4a9d3d8
Cleanup InGameHudMixin
kevinthegreat1 Oct 25, 2024
c565190
POC of hud modification
modmuss50 Jan 9, 2025
abf5b3e
Implement HudLayerRegistrationCallback
kevinthegreat1 Jan 10, 2025
a5b91a4
Merge branch '1.21.4' into hud-render-events
kevinthegreat1 Jan 10, 2025
0706fb1
Delete HudRenderEvents
kevinthegreat1 Jan 10, 2025
0d48d21
Fix sub drawers and add basic documentation
kevinthegreat1 Jan 10, 2025
8d27c16
Fix checkstyle
kevinthegreat1 Jan 11, 2025
f48425c
Apply suggestions from code review
kevinthegreat1 Jan 15, 2025
d8be82b
Add Javadocs
kevinthegreat1 Jan 16, 2025
93dbcf5
Add more unit tests
kevinthegreat1 Jan 16, 2025
a0879f8
Apply suggestions from code review
kevinthegreat1 Jan 16, 2025
61a8ca9
Javadoc oddities
kevinthegreat1 Jan 18, 2025
fe46942
Merge branch '1.21.4' into hud-render-events
kevinthegreat1 Jan 27, 2025
7c43842
Add client gametests
kevinthegreat1 Jan 27, 2025
ef1e938
Finish client gametests
kevinthegreat1 Jan 28, 2025
619f679
Change method and add documentation
kevinthegreat1 Jan 28, 2025
d4e2082
Ensure test environment is correct
kevinthegreat1 Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.rendering.v1;

import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;

/**
* Callback for when hud layers are registered.
*
* <p>To register a layer, register a listener to this event and register your layers in the listener.
* For common use cases, see {@link LayeredDrawerWrapper}.
*
* <p>For example, the following code registers a layer after {@link IdentifiedLayer#MISC_OVERLAYS}:
* <pre>{@code
* HudLayerRegistrationCallback.EVENT.register(layeredDrawer -> layeredDrawer.addLayerAfter(IdentifiedLayer.MISC_OVERLAYS, Identifier.of("example", "example_layer"), (context, tickDelta) -> {
* // Your rendering code here
* }));
* }</pre>
*
* @see LayeredDrawerWrapper
*/
public interface HudLayerRegistrationCallback {
kevinthegreat1 marked this conversation as resolved.
Show resolved Hide resolved
Event<HudLayerRegistrationCallback> EVENT = EventFactory.createArrayBacked(HudLayerRegistrationCallback.class, callbacks -> layeredDrawer -> {
for (HudLayerRegistrationCallback callback : callbacks) {
callback.register(layeredDrawer);
}
});

/**
* Called when registering hud layers.
*
* @param layeredDrawer the layered drawer to register layers to
* @see LayeredDrawerWrapper
*/
void register(LayeredDrawerWrapper layeredDrawer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;

/**
* @deprecated Use {@link HudLayerRegistrationCallback} instead. For the closest equivalent, register a layer after {@link IdentifiedLayer#SUBTITLES}.
kevinthegreat1 marked this conversation as resolved.
Show resolved Hide resolved
*/
@Deprecated
public interface HudRenderCallback {
Event<HudRenderCallback> EVENT = EventFactory.createArrayBacked(HudRenderCallback.class, (listeners) -> (matrixStack, delta) -> {
Event<HudRenderCallback> EVENT = EventFactory.createArrayBacked(HudRenderCallback.class, (listeners) -> (context, tickCounter) -> {
for (HudRenderCallback event : listeners) {
event.onHudRender(matrixStack, delta);
event.onHudRender(context, tickCounter);
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.rendering.v1;

import net.minecraft.client.gui.LayeredDrawer;
import net.minecraft.util.Identifier;

import net.fabricmc.fabric.impl.client.rendering.WrappedLayer;

/**
* A hud layer that has an identifier attached for use in {@link LayeredDrawerWrapper}.
*
* <p>For common use cases, see {@link LayeredDrawerWrapper}.
*/
public interface IdentifiedLayer extends LayeredDrawer.Layer {
/**
* The identifier for the vanilla miscellaneous overlays (such as vignette, spyglass, and powder snow) layer.
*/
Identifier MISC_OVERLAYS = Identifier.ofVanilla("misc_overlays");
/**
* The identifier for the vanilla crosshair layer.
*/
Identifier CROSSHAIR = Identifier.ofVanilla("crosshair");
/**
* The identifier for the vanilla hotbar, spectator hud, experience bar, and status bars layer.
*/
Identifier HOTBAR_AND_BARS = Identifier.ofVanilla("hotbar_and_bars");
/**
* The identifier for the vanilla experience level layer.
*/
Identifier EXPERIENCE_LEVEL = Identifier.ofVanilla("experience_level");
/**
* The identifier for the vanilla status effects layer.
*/
Identifier STATUS_EFFECTS = Identifier.ofVanilla("status_effects");
/**
* The identifier for the vanilla boss bar layer.
*/
Identifier BOSS_BAR = Identifier.ofVanilla("boss_bar");
/**
* The identifier for the vanilla sleep overlay layer.
*/
Identifier SLEEP = Identifier.ofVanilla("sleep");
/**
* The identifier for the vanilla demo timer layer.
*/
Identifier DEMO_TIMER = Identifier.ofVanilla("demo_timer");
/**
* The identifier for the vanilla debug hud layer.
*/
Identifier DEBUG = Identifier.ofVanilla("debug");
/**
* The identifier for the vanilla scoreboard layer.
*/
Identifier SCOREBOARD = Identifier.ofVanilla("scoreboard");
/**
* The identifier for the vanilla overlay message layer.
*/
Identifier OVERLAY_MESSAGE = Identifier.ofVanilla("overlay_message");
/**
* The identifier for the vanilla title and subtitle layer.
*
* <p>Note that this is not the sound subtitles.
*/
Identifier TITLE_AND_SUBTITLE = Identifier.ofVanilla("title_and_subtitle");
/**
* The identifier for the vanilla chat layer.
*/
Identifier CHAT = Identifier.ofVanilla("chat");
/**
* The identifier for the vanilla player list layer.
*/
Identifier PLAYER_LIST = Identifier.ofVanilla("player_list");
/**
* The identifier for the vanilla sound subtitles layer.
*/
Identifier SUBTITLES = Identifier.ofVanilla("subtitles");

/**
* @return the identifier of the layer
*/
Identifier id();

/**
* Wraps a hud layer in an identified layer.
*
* @param id the identifier to give the layer
* @param layer the layer to wrap
* @return the identified layer
*/
static IdentifiedLayer of(Identifier id, LayeredDrawer.Layer layer) {
return new WrappedLayer(id, layer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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.rendering.v1;

import java.util.function.Function;

import net.minecraft.client.gui.LayeredDrawer;
import net.minecraft.util.Identifier;

/**
* A layered drawer that has an identifier attached to each layer and methods to add layers in specific positions.
*
* <p>Common places to add layers (as of 1.21.4):
* <table>
* <tr>
* <th>Injection Point</th>
* <th>Use Case</th>
* </tr>
* <tr>
* <td>Before {@link IdentifiedLayer#MISC_OVERLAYS MISC_OVERLAYS}</td>
* <td>Render before everything</td>
* </tr>
* <tr>
* <td>After {@link IdentifiedLayer#MISC_OVERLAYS MISC_OVERLAYS}</td>
* <td>Render after misc overlays (vignette, spyglass, and powder snow) and before the crosshair</td>
* </tr>
* <tr>
* <td>After {@link IdentifiedLayer#EXPERIENCE_LEVEL EXPERIENCE_LEVEL}</td>
* <td>Render after most main hud elements like hotbar, spectator hud, status bars, experience bar, status effects overlays, and boss bar and before the sleep overlay</td>
* </tr>
* <tr>
* <td>Before {@link IdentifiedLayer#DEMO_TIMER DEMO_TIMER}</td>
* <td>Render after sleep overlay and before the demo timer, debug HUD, scoreboard, overlay message (action bar), and title and subtitle</td>
* </tr>
* <tr>
* <td>Before {@link IdentifiedLayer#CHAT CHAT}</td>
* <td>Render after the debug HUD, scoreboard, overlay message (action bar), and title and subtitle and before {@link net.minecraft.client.gui.hud.ChatHud ChatHud}, player list, and sound subtitles</td>
* </tr>
* <tr>
* <td>After {@link IdentifiedLayer#SUBTITLES SUBTITLES}</td>
* <td>Render after everything</td>
* </tr>
* </table>
*/
public interface LayeredDrawerWrapper {
/**
* Adds a layer to the end of the layered drawer.
*
* @param layer the layer to add
* @return this layered drawer
*/
LayeredDrawerWrapper addLayer(IdentifiedLayer layer);

/**
* Adds a layer before the layer with the specified identifier.
*
* @param beforeThis the identifier of the layer to add the new layer before
* @param layer the layer to add
* @return this layered drawer
*/
LayeredDrawerWrapper addLayerBefore(Identifier beforeThis, IdentifiedLayer layer);

/**
* Adds a layer before the layer with the specified identifier.
*
* @param beforeThis the identifier of the layer to add the new layer before
* @param identifier the identifier of the new layer
* @param layer the layer to add
* @return this layered drawer
*/
default LayeredDrawerWrapper addLayerBefore(Identifier beforeThis, Identifier identifier, LayeredDrawer.Layer layer) {
return addLayerBefore(beforeThis, IdentifiedLayer.of(identifier, layer));
}

/**
* Adds a layer after the layer with the specified identifier.
*
* @param afterThis the identifier of the layer to add the new layer after
* @param layer the layer to add
* @return this layered drawer
*/
LayeredDrawerWrapper addLayerAfter(Identifier afterThis, IdentifiedLayer layer);

/**
* Adds a layer after the layer with the specified identifier.
*
* @param afterThis the identifier of the layer to add the new layer after
* @param identifier the identifier of the new layer
* @param layer the layer to add
* @return this layered drawer
*/
default LayeredDrawerWrapper addLayerAfter(Identifier afterThis, Identifier identifier, LayeredDrawer.Layer layer) {
return addLayerAfter(afterThis, IdentifiedLayer.of(identifier, layer));
}

/**
* Removes a layer with the specified identifier.
*
* @param identifier the identifier of the layer to remove
* @return this layered drawer
*/
LayeredDrawerWrapper removeLayer(Identifier identifier);

/**
* Replaces a layer with the specified identifier.
*
* @param identifier the identifier of the layer to replace
* @param replacer a function that takes the old layer and returns the new layer
* @return this layered drawer
*/
LayeredDrawerWrapper replaceLayer(Identifier identifier, Function<IdentifiedLayer, IdentifiedLayer> replacer);
}
Original file line number Diff line number Diff line change
@@ -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.impl.client.rendering;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;

import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.spongepowered.asm.mixin.injection.InjectionPoint;
import org.spongepowered.asm.mixin.injection.struct.InjectionPointData;
import org.spongepowered.asm.mixin.injection.struct.MemberInfo;

public class LayerInjectionPoint extends InjectionPoint {
private final MemberInfo target;

public LayerInjectionPoint(InjectionPointData data) {
super(data);
this.target = (MemberInfo) data.getTarget();
}

@Override
public boolean find(String desc, InsnList insns, Collection<AbstractInsnNode> nodes) {
List<AbstractInsnNode> targetNodes = new ArrayList<>();

ListIterator<AbstractInsnNode> iterator = insns.iterator();

outer: while (iterator.hasNext()) {
AbstractInsnNode insn = iterator.next();

if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC && matchesInvokeDynamic((InvokeDynamicInsnNode) insn)) {
// We have found our target InvokeDynamicInsnNode, now we need to find the next INVOKEVIRTUAL

while (iterator.hasNext()) {
insn = iterator.next();

if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) {
targetNodes.add(insn);
break outer;
}
}
}
}

nodes.addAll(targetNodes);
return !targetNodes.isEmpty();
}

private boolean matchesInvokeDynamic(InvokeDynamicInsnNode insnNode) {
for (Object bsmArg : insnNode.bsmArgs) {
if (bsmArg instanceof Handle handle && matchesHandle(handle)) {
return true;
}
}

return false;
}

private boolean matchesHandle(Handle handle) {
return handle.getOwner().equals(target.getOwner())
&& handle.getName().equals(target.getName())
&& handle.getDesc().equals(target.getDesc());
}
}
Loading