Skip to content

Commit 69d094e

Browse files
authored
Add renderer mixin (#146)
* Minor changes * Add render mixin * Updated keybinds to new namespace standard * Added refmap for loom * Fixed unnecessary warnings * Implement START callback for consistency * Removed 7.3.12 from breaking * Formatting and javadoc comments
1 parent 2092525 commit 69d094e

16 files changed

Lines changed: 272 additions & 51 deletions

File tree

worldeditcui-fabric/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ loom {
3232
}
3333
}
3434

35+
mixin {
36+
defaultRefmapName.set("worldeditcui-refmap.json")
37+
}
38+
3539
accessWidenerPath.set(project.file("src/main/resources/worldeditcui.accesswidener"))
3640
}
3741

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2011-2024 WorldEditCUI team and contributors
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package org.enginehub.worldeditcui.callback;
11+
12+
import com.mojang.blaze3d.buffers.GpuBufferSlice;
13+
import com.mojang.blaze3d.vertex.PoseStack;
14+
import com.mojang.blaze3d.vertex.VertexConsumer;
15+
import net.fabricmc.fabric.api.event.Event;
16+
import net.fabricmc.fabric.api.event.EventFactory;
17+
import net.minecraft.client.Camera;
18+
import net.minecraft.client.Minecraft;
19+
import net.minecraft.client.renderer.state.BlockOutlineRenderState;
20+
21+
public interface BlockOutlineRenderCallback {
22+
record Context(
23+
Minecraft client,
24+
Camera camera,
25+
PoseStack poseStack,
26+
GpuBufferSlice projectionMatrixBuffer,
27+
VertexConsumer vertexConsumer,
28+
double camX, double camY, double camZ,
29+
BlockOutlineRenderState state
30+
) {}
31+
32+
/**
33+
* Return true to cancel vanilla outline (replace it), false to let vanilla draw after you.
34+
*/
35+
boolean render(Context ctx);
36+
37+
Event<BlockOutlineRenderCallback> EVENT =
38+
EventFactory.createArrayBacked(BlockOutlineRenderCallback.class, cbs -> ctx -> {
39+
boolean cancel = false;
40+
for (var cb : cbs) cancel |= cb.render(ctx);
41+
return cancel;
42+
});
43+
}
44+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2011-2024 WorldEditCUI team and contributors
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package org.enginehub.worldeditcui.callback;
11+
12+
import net.fabricmc.fabric.api.event.Event;
13+
import net.fabricmc.fabric.api.event.EventFactory;
14+
import org.enginehub.worldeditcui.render.WecuiRenderContext;
15+
16+
/**
17+
* A re-implementation of the old world render events for Fabric.
18+
* Temporary until Fabric finishes their new rendering API.
19+
*/
20+
public interface WorldRenderCallback {
21+
void render(WecuiRenderContext ctx);
22+
23+
/**
24+
* Fires at the end of world rendering, after all other rendering is complete.
25+
*/
26+
Event<WorldRenderCallback> LAST = EventFactory.createArrayBacked(WorldRenderCallback.class, cbs -> ctx -> {
27+
for (var cb : cbs) cb.render(ctx);
28+
});
29+
30+
/**
31+
* Fires immediately after the translucent rendering pass.
32+
*/
33+
Event<WorldRenderCallback> AFTER_TRANSLUCENT = EventFactory.createArrayBacked(WorldRenderCallback.class, cbs -> ctx -> {
34+
for (var cb : cbs) cb.render(ctx);
35+
});
36+
}

worldeditcui-fabric/src/main/java/org/enginehub/worldeditcui/fabric/FabricModWorldEditCUI.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,21 @@
99
*/
1010
package org.enginehub.worldeditcui.fabric;
1111

12-
import com.mojang.blaze3d.platform.InputConstants;
1312
import com.mojang.blaze3d.systems.RenderSystem;
1413
import net.fabricmc.api.ModInitializer;
1514
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
1615
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
1716
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
1817
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
19-
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
20-
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
2118
import net.fabricmc.fabric.api.networking.v1.PacketSender;
2219
import net.minecraft.client.KeyMapping;
2320
import net.minecraft.client.Minecraft;
2421
import net.minecraft.client.multiplayer.ClientPacketListener;
2522
import net.minecraft.client.player.LocalPlayer;
23+
import net.minecraft.resources.ResourceLocation;
2624
import net.minecraft.world.level.Level;
2725
import org.enginehub.worldeditcui.WorldEditCUI;
26+
import org.enginehub.worldeditcui.callback.WorldRenderCallback;
2827
import org.enginehub.worldeditcui.config.CUIConfiguration;
2928
import org.enginehub.worldeditcui.event.listeners.CUIListenerChannel;
3029
import org.enginehub.worldeditcui.event.listeners.CUIListenerWorldRender;
@@ -33,6 +32,7 @@
3332
import org.enginehub.worldeditcui.render.OptifinePipelineProvider;
3433
import org.enginehub.worldeditcui.render.PipelineProvider;
3534
import org.enginehub.worldeditcui.render.VanillaPipelineProvider;
35+
import org.enginehub.worldeditcui.render.WecuiRenderContext;
3636
import org.lwjgl.glfw.GLFW;
3737
import org.spongepowered.asm.mixin.MixinEnvironment;
3838

@@ -49,10 +49,12 @@ public final class FabricModWorldEditCUI implements ModInitializer {
4949
public static final String MOD_ID = "worldeditcui";
5050
private static FabricModWorldEditCUI instance;
5151

52-
private static final String KEYBIND_CATEGORY_WECUI = "key.categories.worldeditcui";
53-
private final KeyMapping keyBindToggleUI = key("toggle", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN);
54-
private final KeyMapping keyBindClearSel = key("clear", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN);
55-
private final KeyMapping keyBindChunkBorder = key("chunk", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN);
52+
private static final KeyMapping.Category KEYBIND_CATEGORY_WECUI
53+
= new KeyMapping.Category(ResourceLocation.fromNamespaceAndPath(MOD_ID, "general"));
54+
55+
private final KeyMapping keyBindToggleUI = key("toggle", GLFW.GLFW_KEY_UNKNOWN);
56+
private final KeyMapping keyBindClearSel = key("clear", GLFW.GLFW_KEY_UNKNOWN);
57+
private final KeyMapping keyBindChunkBorder = key("chunk", GLFW.GLFW_KEY_UNKNOWN);
5658

5759
private static final List<PipelineProvider> RENDER_PIPELINES = List.of(
5860
new OptifinePipelineProvider(),
@@ -73,12 +75,12 @@ public final class FabricModWorldEditCUI implements ModInitializer {
7375
* Register a key binding
7476
*
7577
* @param name id, will be used as a localization key under {@code key.worldeditcui.<name>}
76-
* @param type type
7778
* @param code default value
7879
* @return new, registered keybinding in the mod category
7980
*/
80-
private static KeyMapping key(final String name, final InputConstants.Type type, final int code) {
81-
return KeyBindingHelper.registerKeyBinding(new KeyMapping("key." + MOD_ID + '.' + name, type, code, KEYBIND_CATEGORY_WECUI));
81+
private static KeyMapping key(final String name, final int code) {
82+
return KeyBindingHelper.registerKeyBinding(
83+
new KeyMapping("key." + MOD_ID + '.' + name, code, KEYBIND_CATEGORY_WECUI));
8284
}
8385

8486
@Override
@@ -94,11 +96,11 @@ public void onInitialize() {
9496
ClientLifecycleEvents.CLIENT_STARTED.register(this::onGameInitDone);
9597
CUINetworking.subscribeToCuiPacket(this::onPluginMessage);
9698
ClientPlayConnectionEvents.JOIN.register(this::onJoinGame);
97-
WorldRenderEvents.AFTER_TRANSLUCENT.register(ctx -> {
99+
WorldRenderCallback.AFTER_TRANSLUCENT.register(ctx -> {
98100
if (ctx.advancedTranslucency()) {
99101
try {
100102
RenderSystem.getModelViewStack().pushMatrix();
101-
RenderSystem.getModelViewStack().mul(ctx.matrixStack().last().pose());
103+
RenderSystem.getModelViewStack().mul(ctx.poseStack().last().pose());
102104
// RenderSystem.applyModelViewMatrix();
103105
//ctx.worldRenderer().getTranslucentTarget().bindWrite(false);
104106
this.onPostRenderEntities(ctx);
@@ -108,11 +110,11 @@ public void onInitialize() {
108110
}
109111
}
110112
});
111-
WorldRenderEvents.LAST.register(ctx -> {
113+
WorldRenderCallback.LAST.register(ctx -> {
112114
if (!ctx.advancedTranslucency()) {
113115
try {
114116
RenderSystem.getModelViewStack().pushMatrix();
115-
RenderSystem.getModelViewStack().mul(ctx.matrixStack().last().pose());
117+
RenderSystem.getModelViewStack().mul(ctx.poseStack().last().pose());
116118
// RenderSystem.applyModelViewMatrix();
117119
this.onPostRenderEntities(ctx);
118120
} finally {
@@ -192,9 +194,9 @@ public void onJoinGame(final ClientPacketListener handler, final PacketSender se
192194
this.helo(handler);
193195
}
194196

195-
public void onPostRenderEntities(final WorldRenderContext ctx) {
197+
public void onPostRenderEntities(final WecuiRenderContext ctx) {
196198
if (this.visible) {
197-
this.worldRenderListener.onRender(ctx.tickCounter().getRealtimeDeltaTicks());
199+
this.worldRenderListener.onRender(ctx.delta().getRealtimeDeltaTicks());
198200
}
199201
}
200202

worldeditcui-fabric/src/main/java/org/enginehub/worldeditcui/gui/CUIConfigList.java

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
import net.minecraft.util.FormattedCharSequence;
2929
import org.enginehub.worldeditcui.config.CUIConfiguration;
3030
import org.enginehub.worldeditcui.config.Colour;
31+
import org.jetbrains.annotations.NotNull;
3132
import org.slf4j.Logger;
3233

3334
import java.util.List;
35+
import java.util.Objects;
3436

3537
public class CUIConfigList extends ContainerObjectSelectionList<CUIConfigList.ConfigEntry> {
3638
private static final Logger LOGGER = LogUtils.getLogger();
@@ -87,28 +89,28 @@ public OnOffEntry(String tag) {
8789
}
8890

8991
@Override
90-
public void render(GuiGraphics gfx, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTick) {
91-
super.render(gfx, index, top, left, width, height, mouseX, mouseY, isMouseOver, partialTick);
92-
93-
this.toggleBotton.setX(left + 105);
94-
this.toggleBotton.setY(top);
95-
this.toggleBotton.render(gfx, mouseX, mouseY, partialTick);
96-
}
97-
98-
@Override
99-
public List<? extends GuiEventListener> children() {
92+
public @NotNull List<? extends GuiEventListener> children() {
10093
return ImmutableList.of(this.resetButton, this.toggleBotton);
10194
}
10295

10396
@Override
104-
public List<? extends NarratableEntry> narratables() {
97+
public @NotNull List<? extends NarratableEntry> narratables() {
10598
return ImmutableList.of(this.resetButton, this.toggleBotton);
10699
}
107100

108101
@Override
109102
protected void updateFromConfig() {
110103
this.toggleBotton.setValue((Boolean)configuration.getConfigArray().get(tag));
111104
}
105+
106+
@Override
107+
public void renderContent(GuiGraphics gfx, int mouseX, int mouseY, boolean isMouseOver, float partialTick) {
108+
super.renderContent(gfx, mouseX, mouseY, isMouseOver, partialTick);
109+
110+
this.toggleBotton.setX(getRowLeft() + 105);
111+
this.toggleBotton.setY(getY());
112+
this.toggleBotton.render(gfx, mouseX, mouseY, partialTick);
113+
}
112114
}
113115

114116
public class ColorConfigEntry extends ConfigEntry {
@@ -127,7 +129,7 @@ public ColorConfigEntry(String tag) {
127129
configuration.changeValue(tag, tested);
128130
}
129131
});
130-
textField.setFormatter((string, integer) -> {
132+
textField.addFormatter((string, integer) -> {
131133
final String colorSource = textField.getValue();
132134
if (colorSource.length() != 9) {
133135
return FormattedCharSequence.forward(string, invalidFormat);
@@ -155,27 +157,27 @@ public ColorConfigEntry(String tag) {
155157
}
156158

157159
@Override
158-
public void render(GuiGraphics gfx, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTick) {
159-
super.render(gfx, index, top, left, width, height, mouseX, mouseY, isMouseOver, partialTick);
160-
this.textField.setX(left + 105);
161-
this.textField.setY(top);
162-
this.textField.render(gfx, mouseX, mouseY, partialTick);
163-
}
164-
165-
@Override
166-
public List<? extends GuiEventListener> children() {
160+
public @NotNull List<? extends GuiEventListener> children() {
167161
return ImmutableList.of(this.resetButton, this.textField);
168162
}
169163

170164
@Override
171-
public List<? extends NarratableEntry> narratables() {
165+
public @NotNull List<? extends NarratableEntry> narratables() {
172166
return ImmutableList.of(this.resetButton, this.textField);
173167
}
174168

175169
@Override
176170
protected void updateFromConfig() {
177171
this.textField.setValue(((Colour)configuration.getConfigArray().get(tag)).hexString());
178172
}
173+
174+
@Override
175+
public void renderContent(GuiGraphics gfx, int mouseX, int mouseY, boolean isMouseOver, float partialTick) {
176+
super.renderContent(gfx, mouseX, mouseY, isMouseOver, partialTick);
177+
this.textField.setX(getRowLeft() + 105);
178+
this.textField.setY(getY());
179+
this.textField.render(gfx, mouseX, mouseY, partialTick);
180+
}
179181
}
180182

181183
public abstract class ConfigEntry extends ContainerObjectSelectionList.Entry<ConfigEntry> {
@@ -186,21 +188,26 @@ public abstract class ConfigEntry extends ContainerObjectSelectionList.Entry<Con
186188
public ConfigEntry(String tag) {
187189
this.tag = tag;
188190

189-
this.resetButton = Button.builder(Component.translatable("controls.reset"), (button) -> {
191+
this.resetButton = Button.builder(Component.translatable("controls.reset"), button -> {
190192
configuration.changeValue(tag, configuration.getDefaultValue(tag));
191193
updateFromConfig();
192194
}).bounds(0, 0, 50, BUTTON_HEIGHT).build();
193195

194-
textField = new StringWidget(configuration.getDescription(tag), minecraft.font);
195-
textField.alignLeft();
196+
197+
textField = new StringWidget(Objects.requireNonNull(configuration.getDescription(tag)), minecraft.font);
198+
//textField.alignLeft();
196199
Component tooltip = configuration.getTooltip(tag);
197200
if (tooltip != null) {
198201
textField.setTooltip(Tooltip.create(tooltip));
199202
}
200203
}
201204

202205
@Override
203-
public void render(GuiGraphics gfx, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTick) {
206+
public void renderContent(GuiGraphics gfx, int mouseX, int mouseY, boolean hovered, float partialTick) {
207+
// new API handles entry position internally
208+
int left = this.getX();
209+
int top = this.getY(); // or getRowTop()
210+
204211
int textLeft = left + 90 - maxNameWidth;
205212

206213
this.textField.setX(textLeft);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2011-2024 WorldEditCUI team and contributors
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package org.enginehub.worldeditcui.mixin;
11+
12+
import com.mojang.blaze3d.buffers.GpuBufferSlice;
13+
import com.mojang.blaze3d.resource.GraphicsResourceAllocator;
14+
import com.mojang.blaze3d.vertex.PoseStack;
15+
import net.minecraft.client.Camera;
16+
import net.minecraft.client.DeltaTracker;
17+
import net.minecraft.client.Minecraft;
18+
import net.minecraft.client.renderer.LevelRenderer;
19+
import org.enginehub.worldeditcui.callback.WorldRenderCallback;
20+
import org.enginehub.worldeditcui.render.WecuiRenderContext;
21+
import org.joml.Matrix4f;
22+
import org.joml.Vector4f;
23+
import org.spongepowered.asm.mixin.Final;
24+
import org.spongepowered.asm.mixin.Mixin;
25+
import org.spongepowered.asm.mixin.Shadow;
26+
import org.spongepowered.asm.mixin.injection.At;
27+
import org.spongepowered.asm.mixin.injection.Inject;
28+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
29+
30+
/**
31+
* Mixin to LevelRenderer to inject our world render callbacks.
32+
* Temporary until Fabric finishes their new rendering API.
33+
*/
34+
@Mixin(LevelRenderer.class)
35+
public abstract class LevelRendererMixin {
36+
@Shadow @Final private Minecraft minecraft;
37+
38+
// LAST (old LAST)
39+
@Inject(method = "renderLevel", at = @At("TAIL"))
40+
private void wecui$last(
41+
GraphicsResourceAllocator gfx, DeltaTracker delta, boolean renderBlockOutline,
42+
Camera camera, Matrix4f modelView, Matrix4f projection, Matrix4f inverseProjection,
43+
GpuBufferSlice slice, Vector4f clearColor, boolean renderSky, CallbackInfo ci
44+
) {
45+
PoseStack pose = new PoseStack();
46+
pose.last().pose().set(modelView);
47+
WorldRenderCallback.LAST.invoker()
48+
.render(new WecuiRenderContext(this.minecraft, camera, delta, pose, projection));
49+
}
50+
51+
// AFTER_TRANSLUCENT — fires immediately after translucent pass queued/executed
52+
@Inject(
53+
method = "renderLevel",
54+
at = @At(
55+
value = "INVOKE",
56+
target = "Lnet/minecraft/client/renderer/LevelRenderer;addParticlesPass(Lcom/mojang/blaze3d/framegraph/FrameGraphBuilder;Lcom/mojang/blaze3d/buffers/GpuBufferSlice;)V"
57+
)
58+
)
59+
private void wecui$afterTranslucent(
60+
GraphicsResourceAllocator gfx, DeltaTracker delta, boolean renderBlockOutline,
61+
Camera camera, Matrix4f modelView, Matrix4f projection, Matrix4f inverseProjection,
62+
GpuBufferSlice slice, Vector4f clearColor, boolean renderSky,
63+
CallbackInfo ci
64+
) {
65+
var pose = new PoseStack();
66+
pose.last().pose().set(modelView);
67+
WorldRenderCallback.AFTER_TRANSLUCENT.invoker()
68+
.render(new WecuiRenderContext(this.minecraft, camera, delta, pose, projection));
69+
}
70+
}

0 commit comments

Comments
 (0)