Minecraft Forge Gui modding

Version 1.14.4

Register the screen manager in your basic Mod class

    public MyMod() {
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup);
    }

    private void clientSetup(FMLClientSetupEvent event) {
        ScreenManager.registerFactory(MyContainers.MYCONTAINER, MyScreen::new);
    }

Define the container

public class MyContainer extends Container {
    private final IWorldPosCallable iWorldPosCallable;
    private final IntReferenceHolder intReferenceHolder = IntReferenceHolder.single();
    private final World world;
    private ItemStack result = ItemStack.EMPTY;
    private long timeElapsed;
    private final Slot inputSlot;
    private final Slot outputSlot;
    private Runnable inventoryUpdateListener = () -> {
    };
    private final IInventory inventory = new Inventory(1) {
        @Override
        public void markDirty() {
            super.markDirty();
            onCraftMatrixChanged(this);
            inventoryUpdateListener.run();
        }
    };
    private final CraftResultInventory resultInventory = new CraftResultInventory();

    public MyContainer(int id, PlayerInventory playerInventory) {
        this(id, playerInventory, IWorldPosCallable.DUMMY);
    }

    public MyContainer(int id, PlayerInventory playerInventory, final IWorldPosCallable iWorldPosCallable) {
        super(ModContainers.MYCONTAINER, id);
        this.iWorldPosCallable = iWorldPosCallable;
        this.world = playerInventory.player.world;
        this.inputSlot = addSlot(new Slot(this.inventory, 0, 20, 33));
        this.outputSlot = addSlot(new Slot(this.resultInventory, 1, 143, 33) {
            @Override
            public boolean isItemValid(ItemStack stack) {
                return false;
            }

            @Override
            public ItemStack onTake(PlayerEntity thePlayer, ItemStack stack) {
                ItemStack itemstack = inputSlot.decrStackSize(1);
                stack.getItem().onCreated(stack, thePlayer.world, thePlayer);
                return super.onTake(thePlayer, stack);
            }
        });
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 9; ++j) {
                addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
            }
        }
        for (int k = 0; k < 9; ++k) {
            addSlot(new Slot(playerInventory, k, 8 + k * 18, 142));
        }
        trackInt(this.intReferenceHolder);
    }

    @OnlyIn(Dist.CLIENT)
    public int func_217073_e() {
        return this.intReferenceHolder.get();
    }

    @OnlyIn(Dist.CLIENT)
    public boolean func_217083_h() {
        return this.inputSlot.getHasStack();
    }

    @Override
    public boolean canInteractWith(PlayerEntity playerIn) {
        return this.iWorldPosCallable.applyOrElse((world, pos) -> ModBlocks.MYCONTAINER.contains(world.getBlockState(pos).getBlock()) && playerIn.getDistanceSq((double) pos.getX() + 0.5d, (double) pos.getY() + 0.5d, (double) pos.getZ() + 0.5d) <= 64d, true);
    }

    @Override
    public void onCraftMatrixChanged(IInventory inventoryIn) {
        ItemStack stack = this.inputSlot.getStack();
        if (stack.getItem() != this.result.getItem()) {
            this.result = stack.copy();
        }
    }

    @Override
    public ContainerType<?> getType() {
        return ModContainers.MYCONTAINER;
    }

    @OnlyIn(Dist.CLIENT)
    public void setInventoryUpdateListener(Runnable runnable) {
        this.inventoryUpdateListener = runnable;
    }

    @Override
    public boolean canMergeSlot(ItemStack stack, Slot slotIn) {
        return slotIn.inventory != this.inventory && super.canMergeSlot(stack, slotIn);
    }

    @Override
    public ItemStack transferStackInSlot(PlayerEntity playerIn, int index) {
        ItemStack stack = ItemStack.EMPTY;
        Slot slot = this.inventorySlots.get(index);
        if (slot != null && slot.getHasStack()) {
            ItemStack itemstack1 = slot.getStack();
            Item item = itemstack1.getItem();
            stack = itemstack1.copy();
            if (index == 1) {
                item.onCreated(itemstack1, playerIn.world, playerIn);
                if (!this.mergeItemStack(itemstack1, 2, 38, true)) {
                    return ItemStack.EMPTY;
                }
                slot.onSlotChange(itemstack1, stack);
            } else if (index == 0) {
                if (!this.mergeItemStack(itemstack1, 2, 38, false)) {
                    return ItemStack.EMPTY;
                }
            } else if (item.isIn(ModTags.Items.ALLOWED_ITEMS)) {
                if (!this.mergeItemStack(itemstack1, 0, 1, false)) {
                    return ItemStack.EMPTY;
                }
            } else if (index < 29) {
                if (!this.mergeItemStack(itemstack1, 29, 38, false)) {
                    return ItemStack.EMPTY;
                }
            } else if (index < 38 && !this.mergeItemStack(itemstack1, 2, 29, false)) {
                return ItemStack.EMPTY;
            }
            if (itemstack1.isEmpty()) {
                slot.putStack(ItemStack.EMPTY);
            }
            slot.onSlotChanged();
            if (itemstack1.getCount() == stack.getCount()) {
                return ItemStack.EMPTY;
            }
            slot.onTake(playerIn, itemstack1);
            detectAndSendChanges();
        }

        return stack;
    }

    @Override
    public void onContainerClosed(PlayerEntity playerIn) {
        super.onContainerClosed(playerIn);
        this.resultInventory.removeStackFromSlot(1);
        this.iWorldPosCallable.consume((world, pos) -> clearContainer(playerIn, world, this.inventory));
    }
}

Register the container

@ObjectHolder(MOD_ID)
@Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class MyContainers {
    public static final ContainerType<MyContainer> MYCONTAINER = new ContainerType<>(MyContainer::new);

    @SubscribeEvent
    public static void registerContainers(final RegistryEvent.Register<ContainerType<?>> event) {
        event.getRegistry().register(MYCONTAINER.setRegistryName(MOD_ID, "mycontainer"));
    }
}

Define your screen manager

@OnlyIn(Dist.CLIENT)
public class MyScreen extends ContainerScreen<MyContainer> {
    private static final ResourceLocation BACKGROUND_TEXTURE = new ResourceLocation("textures/gui/container/my.png");

    public MyScreen(MyContainer containerIn, PlayerInventory playerInv, ITextComponent title) {
        super(containerIn, playerInv, title);
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
        this.renderBackground();
        GlStateManager.color4f(1f, 1f, 1f, 1f);
        getMinecraft().getTextureManager().bindTexture(BACKGROUND_TEXTURE);
    }
}

Use it in a block (right click)

   @Override
    public boolean onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, BlockRayTraceResult hit) {
        if (!worldIn.isRemote) {
            player.openContainer(state.getContainer(worldIn, pos));
        }
        return true;
    }

    @Override
    @Nullable
    public INamedContainerProvider getContainer(BlockState state, World worldIn, BlockPos pos) {
        ITextComponent translation = new TranslationTextComponent("mycontainer");
        return new SimpleNamedContainerProvider((id, playerInventory, player) -> new MyContainer(id, playerInventory, IWorldPosCallable.of(worldIn, pos)), translation);
    }