Skip to content

FancyMenu breaks custom item rendering in a custom Screen #1530

@Lunrk

Description

@Lunrk

Describe the bug
My CardSection extends AbstractWidget, which means FancyMenu's mixin (MixinAbstractWidget) hooks into its render() method. The RenderWidgetEvent.Pre fired by FancyMenu seems to interfere with the rendering when the widget has a Y rotation of 180° applied to the PoseStack — likely a culling or render state issue caused by FancyMenu modifying the render pipeline before my renderWidget() is called.

To Reproduce
Create this class in a fabric mod and try to open the screen when using a specific item

package dev.lunark.cobblemontcg.screen;

import dev.lunark.cobblemontcg.net.SelectBoosterPackC2SPacket;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.architectury.networking.NetworkManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.sounds.SoundEvents;
import com.mojang.math.Axis;

import java.util.*;

public class BoosterPackScreen extends Screen {

    private final Set<CardSection> sections;
    private final Set<Integer> selected;

    public BoosterPackScreen() {
        super(Component.literal("Booster Pack"));
        this.sections = new HashSet<>();
        this.selected = new LinkedHashSet<>();
    }

    @Override
    public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
        super.render(context, mouseX, mouseY, delta);
        LocalPlayer player = Minecraft.getInstance().player;
        if(player == null) return;
        ItemStack stack = player.getItemInHand(player.getUsedItemHand());
        ItemContainerContents container = stack.getOrDefault(DataComponents.CONTAINER, null);
        if(container == null) return;

        List<ItemStack> items = new ArrayList<>();
        container.nonEmptyItems().forEach(items::add);

        float width = 56.0F;
        float spanX = width * items.size();

        for(CardSection section : this.sections) {
            this.removeWidget(section);
        }

        this.sections.clear();

        int cardHeight = 64; // 16 * scale(4)
        int cardY = (int)(this.height / 2.0F - cardHeight / 2.0F);

        for(int i = 0; i < items.size(); i++) {
            ItemStack outcome = items.get(i);
            float x = this.width / 2.0F - spanX / 2.0F + i * width;
            CardSection element = new CardSection(i, outcome, (int)x, cardY, (int)width, cardHeight);
            this.addRenderableWidget(element);
            this.sections.add(element);
        }
    }

    @Override
    public boolean isPauseScreen() {
        return false;
    }

    @Override
    public void onClose() {
        NetworkManager.sendToServer(new SelectBoosterPackC2SPacket(this.selected));
        super.onClose();
    }

    public class CardSection extends AbstractWidget {
        private final int index;
        private final ItemStack stack;

        public CardSection(int index, ItemStack stack, int x, int y, int width, int height) {
            super(x, y, width, height, Component.empty());
            this.index = index;
            this.stack = stack;
        }

        @Override
        public void renderWidget(GuiGraphics context, int mouseX, int mouseY, float delta) {
            if((this.isFocused() || this.isHovered()) && !BoosterPackScreen.this.selected.contains(this.index)) {
                //context.fillGradient(this.getX(), this.getY(), this.getX() + this.getWidth(),
                //        this.getY() + this.getHeight(), -1000, 0x15CCCCCC, 0x15CCCCCC);
            }

            RenderSystem.disableDepthTest();

            boolean selected = this.isHovered() || this.isFocused();
            float scale = 4.0F;
            context.pose().pushPose();
            context.pose().translate(this.getX() + this.width / 2.0F, this.getY() + this.height / 2.0F, 0.0F);

            if(!BoosterPackScreen.this.selected.contains(this.index)) {
                context.pose().mulPose(Axis.YP.rotationDegrees(180.0F));

                if(selected) {
                    context.pose().mulPose(Axis.ZP.rotationDegrees(-1.0F));
                    scale += 0.5F;
                }
            }

            context.pose().scale(scale, scale, 1.0F);
            context.pose().translate(-8.0F, -8.0F, 0.0F);
            context.renderItem(this.stack, 0, 0);
            context.pose().popPose();

            RenderSystem.enableDepthTest();

            if(this.isHovered() && BoosterPackScreen.this.selected.contains(this.index)) {
                context.renderTooltip(Minecraft.getInstance().font, this.stack, mouseX, mouseY);
            }
        }

        @Override
        protected void updateWidgetNarration(NarrationElementOutput builder) {

        }

        @Override
        public boolean mouseClicked(double mouseX, double mouseY, int button) {
            if(this.active && this.visible && !BoosterPackScreen.this.selected.contains(this.index)) {
                if(this.isValidClickButton(button)) {
                    boolean bl = this.isMouseOver(mouseX, mouseY);
                    if(bl) {
                        Minecraft.getInstance().getSoundManager()
                                .play(SimpleSoundInstance.forUI(SoundEvents.AMETHYST_CLUSTER_HIT, 1.5F));
                        this.onClick(mouseX, mouseY);
                        return true;
                    }
                }

                return false;
            } else {
                return false;
            }
        }

        @Override
        public void onClick(double mouseX, double mouseY) {
            BoosterPackScreen.this.selected.add(this.index);
        }
    }

}

What I think is happening:
My CardSection extends AbstractWidget, which means FancyMenu's mixin (MixinAbstractWidget) hooks into its render() method. The RenderWidgetEvent.Pre fired by FancyMenu seems to interfere with the rendering when the widget has a Y rotation of 180° applied to the PoseStack — likely a culling or render state issue caused by FancyMenu modifying the render pipeline before my renderWidget() is called.

What I've already tried:

  • RenderSystem.disableCull() before rendering
  • RenderSystem.setShaderColor(1, 1, 1, 1) to reset alpha
  • Forcing bufferSource.endBatch() after rendering
  • Translating on the Z axis

None of these fixed the issue.

Is there a recommended way to either exclude a specific AbstractWidget subclass from FancyMenu's mixin, or a proper API to signal that a widget should not be touched by FancyMenu's render pipeline ?

I'm not sure how it works exactly or why this is happening but what I know is, when I remove FancyMenu, it all works fine

Expected behavior
My abstract widgets should render even when rotated 180° to see the back of the widget/item.

Game Log
11:29:01] [Server thread/INFO]:CaptainPrysm[local:E:e5e3f9ee] logged in with entity id 3 at (133.1950102830541, 76.0, 90.63101850077675)
[11:29:01] [Render thread/INFO]:[FANCYMENU] ScreenCustomizationLayer registered: receiving_level_screen
[11:29:01] [Server thread/INFO]:CaptainPrysm joined the game
[11:29:02] [Render thread/INFO]:Attaching attributes for 1 cobblemon_tcg:card_album to class_746['CaptainPrysm'/3, l='ClientLevel', x=133.20, y=76.00, z=90.63]
[11:29:02] [Render thread/INFO]:[FANCYMENU] Connected to a server with FancyMenu installed: local_lan_world
[11:29:02] [Server thread/INFO]:Attaching attributes for 1 cobblemon_tcg:card_album to class_3222['CaptainPrysm'/3, l='ServerLevel[New World]', x=133.20, y=76.00, z=90.63]
[11:29:02] [Server thread/INFO]:[TCG-Debug] Album trouvé: Card Album
[11:29:02] [Server thread/INFO]:[TCG-Debug] Taille du container: 12
[11:29:02] [Server thread/INFO]:[TCG-Debug] CARTE VALIDE slot 0 - Attachement au root...
[11:29:02] [Server thread/INFO]:[TCG-Debug] CARTE VALIDE slot 1 - Attachement au root...
[11:29:02] [Server thread/INFO]:[TCG-Debug] CARTE VALIDE slot 2 - Attachement au root...
[11:29:02] [Server thread/INFO]:[FANCYMENU] A client with FancyMenu installed joined the server: CaptainPrysm
[11:29:02] [Render thread/INFO]:Loaded 72 advancements
[11:29:02] [Render thread/INFO]:Attaching attributes for 1 cobblemon_tcg:card_album to class_746['CaptainPrysm'/3, l='ClientLevel', x=133.20, y=76.00, z=90.63]
[11:29:03] [Render thread/INFO]:[FANCYMENU] ScreenCustomizationLayer registered: pause_screen
[11:29:04] [Server thread/INFO]:Saving and pausing game...
[11:29:04] [Server thread/INFO]:Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld
[11:29:04] [Server thread/INFO]:Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether
[11:29:04] [Server thread/INFO]:Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end
[11:29:35] [Render thread/INFO]:[FANCYMENU] ScreenCustomizationLayer registered: dev.lunark.cobblemontcg.screen.BoosterPackScreen

Screenshots

Without FancyMenu
Image

With FancyMenu
Image

Basic Information (please complete the following information):

  • OS: Windows
  • FancyMenu Version : 2.8.1
  • Fabric Version 0.17.2
  • Minecraft Version 1.21.1
  • Active Mods : FancyMenu and its dependencies, Cobblemon, Architectury API, Trinkets + OWO Lib, Fabric API, Global Datapacks

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions