diff --git a/gradle.properties b/gradle.properties index a840fbc1..41c2ded8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -162,7 +162,7 @@ enableSpotless = false # Enable JUnit testing platform used for testing your code. # Uses JUnit 5. See guide and documentation here: https://junit.org/junit5/docs/current/user-guide/ -enableJUnit = false +enableJUnit = true # Deployment debug setting # Uncomment this to test deployments to CurseForge and Modrinth diff --git a/src/main/java/com/cleanroommc/modularui/ClientEventHandler.java b/src/main/java/com/cleanroommc/modularui/ClientEventHandler.java index 7540b164..01b27cb4 100644 --- a/src/main/java/com/cleanroommc/modularui/ClientEventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/ClientEventHandler.java @@ -1,8 +1,7 @@ package com.cleanroommc.modularui; import com.cleanroommc.modularui.drawable.Stencil; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; -import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.fml.common.eventhandler.EventPriority; @@ -11,7 +10,6 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; import java.io.IOException; @@ -32,17 +30,6 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { } } - @SubscribeEvent - public static void onScroll(GuiScreenEvent.MouseInputEvent.Pre event) { - ModularScreen screen = ModularScreen.getCurrent(); - if (screen != null) { - int w = Mouse.getEventDWheel(); - if (w != 0 && screen.onMouseScroll(w > 0 ? ModularScreen.UpOrDown.UP : ModularScreen.UpOrDown.DOWN, Math.abs(w))) { - event.setCanceled(true); - } - } - } - @SubscribeEvent public static void preDraw(TickEvent.RenderTickEvent event) { if (event.phase == TickEvent.Phase.START) { @@ -78,6 +65,6 @@ public static void onGuiInput(GuiScreenEvent.KeyboardInputEvent.Pre event) { } private static boolean hasDraggable(GuiScreenEvent event) { - return event.getGui() instanceof GuiScreenWrapper screenWrapper && screenWrapper.getScreen().getContext().hasDraggable(); + return event.getGui() instanceof GuiContainerWrapper screenWrapper && screenWrapper.getScreen().getContext().hasDraggable(); } } diff --git a/src/main/java/com/cleanroommc/modularui/ClientProxy.java b/src/main/java/com/cleanroommc/modularui/ClientProxy.java index 4d8f1552..2c7af4a5 100644 --- a/src/main/java/com/cleanroommc/modularui/ClientProxy.java +++ b/src/main/java/com/cleanroommc/modularui/ClientProxy.java @@ -1,11 +1,13 @@ package com.cleanroommc.modularui; import com.cleanroommc.modularui.drawable.DrawableSerialization; -import com.cleanroommc.modularui.factory.GuiManager; import com.cleanroommc.modularui.holoui.HoloScreenEntity; import com.cleanroommc.modularui.holoui.ScreenEntityRender; import com.cleanroommc.modularui.keybind.KeyBindHandler; +import com.cleanroommc.modularui.overlay.OverlayManager; +import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.test.EventHandler; +import com.cleanroommc.modularui.test.OverlayTest; import com.cleanroommc.modularui.theme.ThemeManager; import com.cleanroommc.modularui.theme.ThemeReloadCommand; @@ -31,10 +33,13 @@ void preInit(FMLPreInitializationEvent event) { super.preInit(event); MinecraftForge.EVENT_BUS.register(ClientEventHandler.class); + MinecraftForge.EVENT_BUS.register(ClientScreenHandler.class); + MinecraftForge.EVENT_BUS.register(OverlayManager.class); MinecraftForge.EVENT_BUS.register(KeyBindHandler.class); if (ModularUIConfig.enabledTestGuis) { MinecraftForge.EVENT_BUS.register(EventHandler.class); + OverlayTest.init(); } DrawableSerialization.init(); diff --git a/src/main/java/com/cleanroommc/modularui/CommonProxy.java b/src/main/java/com/cleanroommc/modularui/CommonProxy.java index 28b3ceaa..5eac222e 100644 --- a/src/main/java/com/cleanroommc/modularui/CommonProxy.java +++ b/src/main/java/com/cleanroommc/modularui/CommonProxy.java @@ -1,9 +1,6 @@ package com.cleanroommc.modularui; -import com.cleanroommc.modularui.factory.GuiManager; -import com.cleanroommc.modularui.factory.ItemGuiFactory; -import com.cleanroommc.modularui.factory.SidedTileEntityGuiFactory; -import com.cleanroommc.modularui.factory.TileEntityGuiFactory; +import com.cleanroommc.modularui.factory.*; import com.cleanroommc.modularui.holoui.HoloScreenEntity; import com.cleanroommc.modularui.network.NetworkHandler; import com.cleanroommc.modularui.screen.ModularContainer; @@ -42,9 +39,7 @@ void preInit(FMLPreInitializationEvent event) { NetworkHandler.init(); - GuiManager.registerFactory(TileEntityGuiFactory.INSTANCE); - GuiManager.registerFactory(SidedTileEntityGuiFactory.INSTANCE); - GuiManager.registerFactory(ItemGuiFactory.INSTANCE); + GuiFactories.init(); } void postInit(FMLPostInitializationEvent event) { diff --git a/src/main/java/com/cleanroommc/modularui/ModularUI.java b/src/main/java/com/cleanroommc/modularui/ModularUI.java index d85a6de0..987b7d41 100644 --- a/src/main/java/com/cleanroommc/modularui/ModularUI.java +++ b/src/main/java/com/cleanroommc/modularui/ModularUI.java @@ -49,10 +49,6 @@ public void postInit(FMLPostInitializationEvent event) { proxy.postInit(event); } - @Mod.EventHandler - public void onLoadComplete(FMLLoadCompleteEvent event) { - } - @Mod.EventHandler public void onServerLoad(FMLServerStartingEvent event) { proxy.onServerLoad(event); diff --git a/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java b/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java new file mode 100644 index 00000000..61d303b9 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java @@ -0,0 +1,106 @@ +package com.cleanroommc.modularui.api; + +import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; +import com.cleanroommc.modularui.screen.ClientScreenHandler; +import com.cleanroommc.modularui.screen.ModularScreen; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Slot; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.function.IntConsumer; + +/** + * Implement this interface on a {@link GuiScreen} to be able to use it as a custom wrapper. + * The GuiScreen should have final {@link ModularScreen} field, which is set from the constructor. + * Additionally, the GuiScreen MUST call {@link ModularScreen#construct(IMuiScreen)} in its constructor. + * See {@link com.cleanroommc.modularui.screen.GuiScreenWrapper GuiScreenWrapper} and {@link com.cleanroommc.modularui.screen.GuiContainerWrapper GuiContainerWrapper} + * for default implementations. + */ +@SideOnly(Side.CLIENT) +public interface IMuiScreen { + + /** + * Returns the {@link ModularScreen} that is being wrapped. This should return a final instance field. + * + * @return the wrapped modular screen + */ + @NotNull + ModularScreen getScreen(); + + /** + * {@link GuiScreen GuiScreens} need to be focused when a text field is focused, to prevent key input from + * behaving unexpectedly. + * + * @param focused if the screen should be focused + */ + default void setFocused(boolean focused) { + getGuiScreen().setFocused(focused); + } + + /** + * This method decides how the gui background is drawn. + * The intended usage is to override {@link GuiScreen#drawWorldBackground(int)} and call this method + * with the super method reference as the second parameter. + * + * @param tint background color tint + * @param drawFunction a method reference to draw the world background normally with the tint as the parameter + */ + @ApiStatus.NonExtendable + default void handleDrawBackground(int tint, IntConsumer drawFunction) { + if (ClientScreenHandler.shouldDrawWorldBackground()) { + drawFunction.accept(tint); + } + ClientScreenHandler.drawDarkBackground(getGuiScreen(), tint); + } + + /** + * This method is called every time the {@link ModularScreen} resizes. + * This usually only affects {@link GuiContainer GuiContainers}. + * + * @param area area of the main panel + */ + default void updateGuiArea(Rectangle area) { + if (getGuiScreen() instanceof GuiContainer container) { + ClientScreenHandler.updateGuiArea(container, area); + } + } + + /** + * @return if this wrapper is a {@link GuiContainer} + */ + @ApiStatus.NonExtendable + default boolean isGuiContainer() { + return getGuiScreen() instanceof GuiContainer; + } + + /** + * Hovering widget is handled by {@link com.cleanroommc.modularui.screen.viewport.GuiContext}. + * If it detects a slot, this method is called. Only affects {@link GuiContainer GuiContainers}. + * + * @param slot hovered slot + */ + @ApiStatus.NonExtendable + default void setHoveredSlot(Slot slot) { + if (getGuiScreen() instanceof GuiContainerAccessor acc) { + acc.setHoveredSlot(slot); + } + } + + /** + * Returns the {@link GuiScreen} that wraps the {@link ModularScreen}. + * In most cases this does not need to be overridden as this interfaces should be implemented on {@link GuiScreen GuiScreens}. + * + * @return the wrapping gui screen + */ + default GuiScreen getGuiScreen() { + return (GuiScreen) this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/api/MCHelper.java b/src/main/java/com/cleanroommc/modularui/api/MCHelper.java new file mode 100644 index 00000000..ef5a0ac8 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/MCHelper.java @@ -0,0 +1,48 @@ +package com.cleanroommc.modularui.api; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.GuiScreen; + +public class MCHelper { + + public static boolean hasMc() { + return getMc() != null; + } + + public static Minecraft getMc() { + return Minecraft.getMinecraft(); + } + + public static EntityPlayerSP getPlayer() { + if (hasMc()) { + return getMc().player; + } + return null; + } + + public static boolean closeScreen() { + if (!hasMc()) return false; + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) { + player.closeScreen(); + return true; + } + Minecraft.getMinecraft().displayGuiScreen(null); + return false; + } + + public static boolean displayScreen(GuiScreen screen) { + Minecraft mc = getMc(); + if (mc != null) { + mc.displayGuiScreen(screen); + return true; + } + return false; + } + + public static GuiScreen getCurrentScreen() { + Minecraft mc = getMc(); + return mc != null ? mc.currentScreen : null; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/api/UIFactory.java b/src/main/java/com/cleanroommc/modularui/api/UIFactory.java index a4f7b0da..618b3106 100644 --- a/src/main/java/com/cleanroommc/modularui/api/UIFactory.java +++ b/src/main/java/com/cleanroommc/modularui/api/UIFactory.java @@ -1,9 +1,10 @@ package com.cleanroommc.modularui.api; import com.cleanroommc.modularui.factory.GuiData; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; +import com.cleanroommc.modularui.screen.ModularContainer; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; - import com.cleanroommc.modularui.value.sync.PanelSyncManager; import net.minecraft.entity.player.EntityPlayer; @@ -51,6 +52,22 @@ public interface UIFactory { @ApiStatus.OverrideOnly ModularScreen createScreen(D guiData, ModularPanel mainPanel); + /** + * Creates the screen wrapper for the GUI. Is only called on client side. + * + * @param container container for the gui + * @param screen the screen which was created in {@link #createScreen(GuiData, ModularPanel)} + * @return new screen wrapper + * @throws IllegalStateException if the wrapping screen is not a {@link net.minecraft.client.gui.inventory.GuiContainer GuiContainer} or if the + * container inside is not the same as the one passed to this method. This method is not the thrower, but the + * caller of this method. + */ + @SideOnly(Side.CLIENT) + @ApiStatus.OverrideOnly + default IMuiScreen createScreenWrapper(ModularContainer container, ModularScreen screen) { + return new GuiContainerWrapper(container, screen); + } + /** * Writes the gui data to a buffer. * diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java b/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java index b9e0f89f..cc9f2590 100644 --- a/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.api.drawable; +import com.cleanroommc.modularui.drawable.DrawableArray; import com.cleanroommc.modularui.drawable.Icon; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; @@ -10,6 +11,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; import com.google.gson.JsonObject; +import org.jetbrains.annotations.Nullable; /** * An object which can be drawn. This is mainly used for backgrounds and overlays in @@ -138,6 +140,14 @@ default void loadFromJson(JsonObject json) {} */ IDrawable NONE = (context, x, y, width, height, widgetTheme) -> {}; + static boolean isVisible(@Nullable IDrawable drawable) { + if (drawable == null || drawable == EMPTY || drawable == NONE) return false; + if (drawable instanceof DrawableArray array) { + return array.getDrawables().length > 0; + } + return true; + } + /** * A widget wrapping a drawable. The drawable is drawn between the background and the overlay. */ diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java b/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java index 566c57c7..11036b71 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java @@ -1,10 +1,10 @@ package com.cleanroommc.modularui.api.layout; import com.cleanroommc.modularui.screen.viewport.TransformationMatrix; +import com.cleanroommc.modularui.utils.Vector3f; import com.cleanroommc.modularui.widget.sizer.Area; import org.jetbrains.annotations.Nullable; -import org.lwjgl.util.vector.Vector3f; /** * This handles all viewports in a GUI. Also keeps track of a matrix stack used for rendering and diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiAccessor.java b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiAccessor.java new file mode 100644 index 00000000..66571447 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiAccessor.java @@ -0,0 +1,16 @@ +package com.cleanroommc.modularui.core.mixin; + +import net.minecraft.client.gui.Gui; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Gui.class) +public interface GuiAccessor { + + @Accessor + float getZLevel(); + + @Accessor + void setZLevel(float z); +} diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiButtonMixin.java b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiButtonMixin.java new file mode 100644 index 00000000..31081e03 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiButtonMixin.java @@ -0,0 +1,29 @@ +package com.cleanroommc.modularui.core.mixin; + +import com.cleanroommc.modularui.overlay.OverlayStack; + +import net.minecraft.client.gui.GuiButton; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +/** + * This mixin fixes some visual bugs that can happen with overlays. + */ +@Mixin(GuiButton.class) +public abstract class GuiButtonMixin { + + @Shadow protected boolean hovered; + + @Shadow + protected abstract int getHoverState(boolean mouseOver); + + @Redirect(method = "drawButton", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiButton;getHoverState(Z)I")) + public int draw(GuiButton instance, boolean mouseOver) { + // fixes buttons being hovered when an overlay element is already hovered + if (this.hovered) this.hovered = !OverlayStack.isHoveringOverlay(); + return getHoverState(this.hovered); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerAccessor.java b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerAccessor.java index 6a346f71..e29bc498 100644 --- a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerAccessor.java +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerAccessor.java @@ -8,9 +8,23 @@ import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; +import java.util.Set; + @Mixin(GuiContainer.class) public interface GuiContainerAccessor { + @Accessor + void setXSize(int v); + + @Accessor + void setYSize(int v); + + @Accessor + void setGuiLeft(int v); + + @Accessor + void setGuiTop(int v); + @Accessor void setHoveredSlot(Slot slot); @@ -26,6 +40,12 @@ public interface GuiContainerAccessor { @Accessor boolean getIsRightMouseClick(); + @Accessor + boolean getDragSplitting(); + + @Accessor + Set getDragSplittingSlots(); + @Accessor int getDragSplittingLimit(); @@ -52,4 +72,10 @@ public interface GuiContainerAccessor { @Accessor long getReturningStackTime(); + + @Invoker + void invokeDrawGuiContainerForegroundLayer(int mouseX, int mouseY); + + @Invoker + void invokeDrawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY); } diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerMixin.java b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerMixin.java index e2d41135..52930e96 100644 --- a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerMixin.java +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiContainerMixin.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.core.mixin; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.inventory.Slot; @@ -24,7 +24,7 @@ public class GuiContainerMixin { */ @Inject(method = "getSlotAtPosition", at = @At("HEAD"), cancellable = true) public void getSlot(int x, int y, CallbackInfoReturnable cir) { - if (((Object) this).getClass() == GuiScreenWrapper.class) { + if (((Object) this).getClass() == GuiContainerWrapper.class) { cir.setReturnValue(this.hoveredSlot); } } diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiScreenAccessor.java b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiScreenAccessor.java new file mode 100644 index 00000000..b3f75d3e --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiScreenAccessor.java @@ -0,0 +1,61 @@ +package com.cleanroommc.modularui.core.mixin; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiLabel; +import net.minecraft.client.gui.GuiScreen; + +import net.minecraft.client.renderer.RenderItem; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.io.IOException; +import java.util.List; + +@Mixin(GuiScreen.class) +public interface GuiScreenAccessor { + + @Accessor + int getTouchValue(); + + @Accessor + void setTouchValue(int value); + + @Accessor + int getEventButton(); + + @Accessor + void setEventButton(int button); + + @Accessor + long getLastMouseEvent(); + + @Accessor + void setLastMouseEvent(long event); + + @Accessor + RenderItem getItemRender(); + + @Accessor + FontRenderer getFontRenderer(); + + @Accessor + List getButtonList(); + + @Accessor + List getLabelList(); + + @Invoker + void invokeKeyTyped(char typedChar, int keyCode) throws IOException; + + @Invoker + void invokeMouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException; + + @Invoker + void invokeMouseReleased(int mouseX, int mouseY, int state); + + @Invoker + void invokeMouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick); +} diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/MinecraftMixin.java b/src/main/java/com/cleanroommc/modularui/core/mixin/MinecraftMixin.java index 2b31c1b3..0f4bf399 100644 --- a/src/main/java/com/cleanroommc/modularui/core/mixin/MinecraftMixin.java +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/MinecraftMixin.java @@ -1,8 +1,7 @@ package com.cleanroommc.modularui.core.mixin; import com.cleanroommc.modularui.ModularUI; -import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.utils.Animator; +import com.cleanroommc.modularui.screen.ClientScreenHandler; import net.minecraft.client.Minecraft; @@ -24,12 +23,8 @@ public class MinecraftMixin { public void timer(CallbackInfo ci) { if (ModularUI.proxy == null || ModularUI.proxy.getTimer60Fps() == null) return; ModularUI.proxy.getTimer60Fps().updateTimer(); - ModularScreen screen = ModularScreen.getCurrent(); - if (screen != null) { - for (int j = 0; j < Math.min(20, ModularUI.proxy.getTimer60Fps().elapsedTicks); ++j) { - screen.onFrameUpdate(); - Animator.advance(); - } + for (int j = 0; j < Math.min(20, ModularUI.proxy.getTimer60Fps().elapsedTicks); ++j) { + ClientScreenHandler.onFrameUpdate(); } } } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/Icon.java b/src/main/java/com/cleanroommc/modularui/drawable/Icon.java index 1fcbc6fa..609ed0f6 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/Icon.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/Icon.java @@ -22,6 +22,7 @@ public class Icon implements IIcon { private int width = 0, height = 0; private Alignment alignment = Alignment.Center; private final Box margin = new Box(); + private int color = 0; public Icon(IDrawable drawable) { this.drawable = drawable; @@ -57,6 +58,9 @@ public void draw(GuiContext context, int x, int y, int width, int height, Widget y += (int) (height * this.alignment.y - this.height * this.alignment.y); height = this.height; } + if (this.color != 0 && this.color != widgetTheme.getColor()) { + widgetTheme = widgetTheme.withColor(this.color); + } this.drawable.draw(context, x, y, width, height, widgetTheme); } @@ -87,6 +91,11 @@ public Icon alignment(Alignment alignment) { return this; } + public Icon color(int color) { + this.color = color; + return this; + } + public Icon margin(int left, int right, int top, int bottom) { this.margin.all(left, right, top, bottom); return this; diff --git a/src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java b/src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java index b95f9a27..9d0daf31 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java @@ -76,7 +76,8 @@ protected void drawMeasuredLines(List measuredLines) { int y0 = getStartY(measuredLines.size()); for (Line measuredLine : measuredLines) { int x0 = getStartX(measuredLine.width); - maxW = Math.max(draw(measuredLine.text, x0, y0), maxW); + maxW = Math.max(maxW, measuredLine.width); + draw(measuredLine.text, x0, y0); y0 += (int) getFontHeight(); } this.lastWidth = this.maxWidth > 0 ? Math.min(maxW, this.maxWidth) : maxW; diff --git a/src/main/java/com/cleanroommc/modularui/factory/ClientGUI.java b/src/main/java/com/cleanroommc/modularui/factory/ClientGUI.java index f40c34c4..fe7693eb 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/ClientGUI.java +++ b/src/main/java/com/cleanroommc/modularui/factory/ClientGUI.java @@ -1,10 +1,10 @@ package com.cleanroommc.modularui.factory; +import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.screen.ContainerCustomizer; import com.cleanroommc.modularui.screen.JeiSettingsImpl; import com.cleanroommc.modularui.screen.ModularScreen; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -72,13 +72,13 @@ public static void open(@NotNull ModularScreen screen, @NotNull JeiSettingsImpl * @param screen screen to open */ public static void open(GuiScreen screen) { - Minecraft.getMinecraft().displayGuiScreen(screen); + MCHelper.displayScreen(screen); } /** * Closes any GUI that is open in this tick. */ public static void close() { - Minecraft.getMinecraft().displayGuiScreen(null); + MCHelper.displayScreen(null); } } diff --git a/src/main/java/com/cleanroommc/modularui/factory/GuiData.java b/src/main/java/com/cleanroommc/modularui/factory/GuiData.java index d887145f..327289a9 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/GuiData.java +++ b/src/main/java/com/cleanroommc/modularui/factory/GuiData.java @@ -9,6 +9,15 @@ import java.util.Objects; +/** + * This class and subclasses are holding necessary data to find the exact same GUI on client and server. + * For example, if the GUI was opened by right-clicking a TileEntity, then this data needs a world and a block pos. + * Additionally, this can be used to configure JEI via {@link #getJeiSettings()}. + *

+ * Also see {@link PosGuiData} (useful for TileEntities), {@link SidedPosGuiData} (useful for covers from GregTech) and + * {@link HandGuiData} (useful for guis opened by interacting with an item in the players hand) for default implementations. + *

+ */ public class GuiData { private final EntityPlayer player; diff --git a/src/main/java/com/cleanroommc/modularui/factory/GuiFactories.java b/src/main/java/com/cleanroommc/modularui/factory/GuiFactories.java new file mode 100644 index 00000000..d876d51a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/factory/GuiFactories.java @@ -0,0 +1,39 @@ +package com.cleanroommc.modularui.factory; + +import com.cleanroommc.modularui.api.IGuiHolder; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Supplier; + +public class GuiFactories { + + public static TileEntityGuiFactory tileEntity() { + return TileEntityGuiFactory.INSTANCE; + } + + public static SidedTileEntityGuiFactory sidedTileEntity() { + return SidedTileEntityGuiFactory.INSTANCE; + } + + public static ItemGuiFactory item() { + return ItemGuiFactory.INSTANCE; + } + + public static SimpleGuiFactory createSimple(String name, IGuiHolder holder) { + return new SimpleGuiFactory(name, holder); + } + + public static SimpleGuiFactory createSimple(String name, Supplier> holder) { + return new SimpleGuiFactory(name, holder); + } + + @ApiStatus.Internal + public static void init() { + GuiManager.registerFactory(tileEntity()); + GuiManager.registerFactory(sidedTileEntity()); + GuiManager.registerFactory(item()); + } + + private GuiFactories() {} +} diff --git a/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java b/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java index 8d28cf9f..738054b0 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java +++ b/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java @@ -1,6 +1,8 @@ package com.cleanroommc.modularui.factory; +import com.cleanroommc.modularui.api.IMuiScreen; import com.cleanroommc.modularui.api.JeiSettings; +import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.UIFactory; import com.cleanroommc.modularui.network.NetworkHandler; import com.cleanroommc.modularui.network.packets.OpenGuiPacket; @@ -8,8 +10,9 @@ import com.cleanroommc.modularui.value.sync.PanelSyncManager; import com.cleanroommc.modularui.widget.WidgetTree; -import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.PacketBuffer; @@ -36,7 +39,7 @@ public class GuiManager { private static final Object2ObjectMap> FACTORIES = new Object2ObjectOpenHashMap<>(16); - private static GuiScreenWrapper lastMui; + private static IMuiScreen lastMui; private static final List openedContainers = new ArrayList<>(4); public static void registerFactory(UIFactory factory) { @@ -57,6 +60,10 @@ public static void registerFactory(UIFactory factory) { return factory; } + public static boolean hasFactory(String name) { + return FACTORIES.containsKey(name); + } + public static void open(@NotNull UIFactory factory, @NotNull T guiData, EntityPlayerMP player) { if (player instanceof FakePlayer || openedContainers.contains(player)) return; openedContainers.add(player); @@ -91,17 +98,27 @@ public static void open(int windowId, @NotNull UIFactory WidgetTree.collectSyncValues(syncManager, panel); ModularScreen screen = factory.createScreen(guiData, panel); screen.getContext().setJeiSettings(jeiSettings); - GuiScreenWrapper guiScreenWrapper = new GuiScreenWrapper(new ModularContainer(player, syncManager, panel.getName()), screen); - guiScreenWrapper.inventorySlots.windowId = windowId; - Minecraft.getMinecraft().displayGuiScreen(guiScreenWrapper); - player.openContainer = guiScreenWrapper.inventorySlots; + ModularContainer container = new ModularContainer(player, syncManager, panel.getName()); + IMuiScreen wrapper = factory.createScreenWrapper(container, screen); + if (!(wrapper.getGuiScreen() instanceof GuiContainer guiContainer)) { + throw new IllegalStateException("The wrapping screen must be a GuiContainer for synced GUIs!"); + } + if (guiContainer.inventorySlots != container) throw new IllegalStateException("Custom Containers are not yet allowed!"); + guiContainer.inventorySlots.windowId = windowId; + MCHelper.displayScreen(wrapper.getGuiScreen()); + player.openContainer = guiContainer.inventorySlots; } @SideOnly(Side.CLIENT) static void openScreen(ModularScreen screen, JeiSettingsImpl jeiSettings, ContainerCustomizer containerCustomizer) { screen.getContext().setJeiSettings(jeiSettings); - GuiScreenWrapper screenWrapper = new GuiScreenWrapper(new ModularContainer(containerCustomizer), screen); - Minecraft.getMinecraft().displayGuiScreen(screenWrapper); + GuiScreen guiScreen; + if (containerCustomizer == null) { + guiScreen = new GuiScreenWrapper(screen); + } else { + guiScreen = new GuiContainerWrapper(new ModularContainer(containerCustomizer), screen); + } + MCHelper.displayScreen(guiScreen); } @SubscribeEvent @@ -120,7 +137,7 @@ public static void onGuiOpen(GuiOpenEvent event) { } lastMui.getScreen().getPanelManager().dispose(); lastMui = null; - } else if (event.getGui() instanceof GuiScreenWrapper screenWrapper) { + } else if (event.getGui() instanceof IMuiScreen screenWrapper) { if (lastMui == null) { lastMui = screenWrapper; } else if (lastMui == event.getGui()) { diff --git a/src/main/java/com/cleanroommc/modularui/factory/HandGuiData.java b/src/main/java/com/cleanroommc/modularui/factory/HandGuiData.java index 283946a2..487bae5f 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/HandGuiData.java +++ b/src/main/java/com/cleanroommc/modularui/factory/HandGuiData.java @@ -4,6 +4,9 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.EnumHand; +/** + * See {@link GuiData} for an explanation for what this is for. + */ public class HandGuiData extends GuiData { private final EnumHand hand; diff --git a/src/main/java/com/cleanroommc/modularui/factory/ItemGuiFactory.java b/src/main/java/com/cleanroommc/modularui/factory/ItemGuiFactory.java index 95664614..237c58a1 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/ItemGuiFactory.java +++ b/src/main/java/com/cleanroommc/modularui/factory/ItemGuiFactory.java @@ -19,11 +19,19 @@ private ItemGuiFactory() { super("mui:item"); } - public static void open(EntityPlayerMP player, EnumHand hand) { + public void open(EntityPlayer player, EnumHand hand) { + if (player instanceof EntityPlayerMP entityPlayerMP) { + open(entityPlayerMP, hand); + return; + } + throw new IllegalStateException("Synced GUIs must be opened from server side"); + } + + public void open(EntityPlayerMP player, EnumHand hand) { Objects.requireNonNull(player); Objects.requireNonNull(hand); HandGuiData guiData = new HandGuiData(player, hand); - GuiManager.open(INSTANCE, guiData, player); + GuiManager.open(this, guiData, player); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/factory/PosGuiData.java b/src/main/java/com/cleanroommc/modularui/factory/PosGuiData.java index ba6ca2a5..93d182a8 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/PosGuiData.java +++ b/src/main/java/com/cleanroommc/modularui/factory/PosGuiData.java @@ -5,6 +5,9 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +/** + * See {@link GuiData} for an explanation for what this is for. + */ public class PosGuiData extends GuiData { private static final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); diff --git a/src/main/java/com/cleanroommc/modularui/factory/SidedPosGuiData.java b/src/main/java/com/cleanroommc/modularui/factory/SidedPosGuiData.java index 2a0541f8..6d67ca65 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/SidedPosGuiData.java +++ b/src/main/java/com/cleanroommc/modularui/factory/SidedPosGuiData.java @@ -3,6 +3,9 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.EnumFacing; +/** + * See {@link GuiData} for an explanation for what this is for. + */ public class SidedPosGuiData extends PosGuiData { private final EnumFacing side; diff --git a/src/main/java/com/cleanroommc/modularui/factory/SidedTileEntityGuiFactory.java b/src/main/java/com/cleanroommc/modularui/factory/SidedTileEntityGuiFactory.java index 891e3688..d4b244f2 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/SidedTileEntityGuiFactory.java +++ b/src/main/java/com/cleanroommc/modularui/factory/SidedTileEntityGuiFactory.java @@ -17,7 +17,7 @@ public class SidedTileEntityGuiFactory extends AbstractUIFactory> void open(EntityPlayer player, T tile, EnumFacing facing) { + public > void open(EntityPlayer player, T tile, EnumFacing facing) { Objects.requireNonNull(player); Objects.requireNonNull(tile); Objects.requireNonNull(facing); @@ -29,15 +29,15 @@ public static > void open(Ent } BlockPos pos = tile.getPos(); SidedPosGuiData data = new SidedPosGuiData(player, pos.getX(), pos.getY(), pos.getZ(), facing); - GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + GuiManager.open(this, data, (EntityPlayerMP) player); } - public static void open(EntityPlayer player, BlockPos pos, EnumFacing facing) { + public void open(EntityPlayer player, BlockPos pos, EnumFacing facing) { Objects.requireNonNull(player); Objects.requireNonNull(pos); Objects.requireNonNull(facing); SidedPosGuiData data = new SidedPosGuiData(player, pos.getX(), pos.getY(), pos.getZ(), facing); - GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + GuiManager.open(this, data, (EntityPlayerMP) player); } private SidedTileEntityGuiFactory() { diff --git a/src/main/java/com/cleanroommc/modularui/factory/SimpleGuiFactory.java b/src/main/java/com/cleanroommc/modularui/factory/SimpleGuiFactory.java index 40a19e45..cfe050e3 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/SimpleGuiFactory.java +++ b/src/main/java/com/cleanroommc/modularui/factory/SimpleGuiFactory.java @@ -8,12 +8,36 @@ import org.jetbrains.annotations.NotNull; +import java.util.Objects; import java.util.function.Supplier; +/** + * Sometimes you don't want to open guis which are bound to a TileEntity or an Item. + * For example by a command. You are supposed to create one simple factory per GUI to make sure they are same + * on client and server. + * These factories are registered automatically. + */ public class SimpleGuiFactory extends AbstractUIFactory { private final Supplier> guiHolderSupplier; + private IGuiHolder guiHolder; + /** + * Creates a simple gui factory. + * + * @param name name of the factory + * @param guiHolder gui holder + */ + public SimpleGuiFactory(String name, IGuiHolder guiHolder) { + this(name, () -> guiHolder); + } + + /** + * Creates a simple gui factory. + * + * @param name name of the factory + * @param guiHolderSupplier a function which retrieves a gui holder. This is only called once and then cached. + */ public SimpleGuiFactory(String name, Supplier> guiHolderSupplier) { super(name); this.guiHolderSupplier = guiHolderSupplier; @@ -39,6 +63,10 @@ public void writeGuiData(GuiData guiData, PacketBuffer buffer) { @Override public @NotNull IGuiHolder getGuiHolder(GuiData data) { - return this.guiHolderSupplier.get(); + if (this.guiHolder == null) { + this.guiHolder = this.guiHolderSupplier.get(); + Objects.requireNonNull(this.guiHolder, "IGuiHolder must not be null"); + } + return this.guiHolder; } } diff --git a/src/main/java/com/cleanroommc/modularui/factory/TileEntityGuiFactory.java b/src/main/java/com/cleanroommc/modularui/factory/TileEntityGuiFactory.java index c45aebde..80472338 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/TileEntityGuiFactory.java +++ b/src/main/java/com/cleanroommc/modularui/factory/TileEntityGuiFactory.java @@ -17,10 +17,10 @@ public class TileEntityGuiFactory extends AbstractUIFactory { public static final TileEntityGuiFactory INSTANCE = new TileEntityGuiFactory(); private TileEntityGuiFactory() { - super("mui:tile"); + super("mui:tile_entity"); } - public static > void open(EntityPlayer player, T tile) { + public > void open(EntityPlayer player, T tile) { Objects.requireNonNull(player); Objects.requireNonNull(tile); if (tile.isInvalid()) { @@ -31,14 +31,14 @@ public static > void open(EntityPl } BlockPos pos = tile.getPos(); PosGuiData data = new PosGuiData(player, pos.getX(), pos.getY(), pos.getZ()); - GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + GuiManager.open(this, data, (EntityPlayerMP) player); } - public static void open(EntityPlayer player, BlockPos pos) { + public void open(EntityPlayer player, BlockPos pos) { Objects.requireNonNull(player); Objects.requireNonNull(pos); PosGuiData data = new PosGuiData(player, pos.getX(), pos.getY(), pos.getZ()); - GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + GuiManager.open(this, data, (EntityPlayerMP) player); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/holoui/HoloScreenEntity.java b/src/main/java/com/cleanroommc/modularui/holoui/HoloScreenEntity.java index 28e4e0f1..33d68fa8 100644 --- a/src/main/java/com/cleanroommc/modularui/holoui/HoloScreenEntity.java +++ b/src/main/java/com/cleanroommc/modularui/holoui/HoloScreenEntity.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.holoui; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; import com.cleanroommc.modularui.screen.ModularContainer; import com.cleanroommc.modularui.screen.ModularScreen; @@ -26,7 +26,7 @@ @ApiStatus.Experimental public class HoloScreenEntity extends Entity { - private GuiScreenWrapper wrapper; + private GuiContainerWrapper wrapper; private ModularScreen screen; private final Plane3D plane3D; private static final DataParameter ORIENTATION = EntityDataManager.createKey(HoloScreenEntity.class, DataSerializers.BYTE); @@ -42,7 +42,7 @@ public HoloScreenEntity(World world) { public void setScreen(ModularScreen screen) { this.screen = screen; - this.wrapper = new GuiScreenWrapper(new ModularContainer(null), screen); + this.wrapper = new GuiContainerWrapper(new ModularContainer(null), screen); this.wrapper.setWorldAndResolution(Minecraft.getMinecraft(), (int) this.plane3D.getWidth(), (int) this.plane3D.getHeight()); } @@ -50,7 +50,7 @@ public ModularScreen getScreen() { return this.screen; } - public GuiScreenWrapper getWrapper() { + public GuiContainerWrapper getWrapper() { return this.wrapper; } diff --git a/src/main/java/com/cleanroommc/modularui/holoui/Plane3D.java b/src/main/java/com/cleanroommc/modularui/holoui/Plane3D.java index 3e248c67..7fcaa70d 100644 --- a/src/main/java/com/cleanroommc/modularui/holoui/Plane3D.java +++ b/src/main/java/com/cleanroommc/modularui/holoui/Plane3D.java @@ -2,10 +2,11 @@ import com.cleanroommc.modularui.utils.GuiUtils; +import com.cleanroommc.modularui.utils.Matrix4f; + import net.minecraft.client.renderer.GlStateManager; import org.jetbrains.annotations.ApiStatus; -import org.lwjgl.util.vector.Matrix4f; /** * Highly experimental diff --git a/src/main/java/com/cleanroommc/modularui/holoui/ScreenEntityRender.java b/src/main/java/com/cleanroommc/modularui/holoui/ScreenEntityRender.java index 13768614..a8e389f4 100644 --- a/src/main/java/com/cleanroommc/modularui/holoui/ScreenEntityRender.java +++ b/src/main/java/com/cleanroommc/modularui/holoui/ScreenEntityRender.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.holoui; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GlStateManager; @@ -31,7 +31,7 @@ protected ResourceLocation getEntityTexture(@NotNull HoloScreenEntity entity) { @Override public void doRender(@NotNull HoloScreenEntity entity, double x, double y, double z, float entityYaw, float partialTicks) { - GuiScreenWrapper screenWrapper = entity.getWrapper(); + GuiContainerWrapper screenWrapper = entity.getWrapper(); if (screenWrapper == null) return; Plane3D plane3D = entity.getPlane3D(); diff --git a/src/main/java/com/cleanroommc/modularui/integration/jei/JeiState.java b/src/main/java/com/cleanroommc/modularui/integration/jei/JeiState.java index e9e71aee..5f3f701a 100644 --- a/src/main/java/com/cleanroommc/modularui/integration/jei/JeiState.java +++ b/src/main/java/com/cleanroommc/modularui/integration/jei/JeiState.java @@ -21,7 +21,7 @@ public boolean test(ModularScreen screen) { DEFAULT { @Override public boolean test(ModularScreen screen) { - return !screen.getContainer().isClientOnly(); + return !screen.isClientOnly(); } } } diff --git a/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIHandler.java b/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIHandler.java index 6dfb27dd..f21f83ee 100644 --- a/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIHandler.java +++ b/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIHandler.java @@ -1,7 +1,7 @@ package com.cleanroommc.modularui.integration.jei; import com.cleanroommc.modularui.api.widget.IGuiElement; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; import com.cleanroommc.modularui.screen.ModularContainer; import com.cleanroommc.modularui.screen.ModularScreen; @@ -17,28 +17,28 @@ import java.awt.*; import java.util.List; -public class ModularUIHandler implements IAdvancedGuiHandler, IGhostIngredientHandler, IGuiScreenHandler, IRecipeTransferHandler { +public class ModularUIHandler implements IAdvancedGuiHandler, IGhostIngredientHandler, IGuiScreenHandler, IRecipeTransferHandler { @Override - public @NotNull Class getGuiContainerClass() { - return GuiScreenWrapper.class; + public @NotNull Class getGuiContainerClass() { + return GuiContainerWrapper.class; } @Nullable @Override - public List getGuiExtraAreas(@NotNull GuiScreenWrapper guiContainer) { + public List getGuiExtraAreas(@NotNull GuiContainerWrapper guiContainer) { return guiContainer.getScreen().getContext().getJeiSettings().getAllJeiExclusionAreas(); } @Nullable @Override - public Object getIngredientUnderMouse(@NotNull GuiScreenWrapper guiContainer, int mouseX, int mouseY) { + public Object getIngredientUnderMouse(@NotNull GuiContainerWrapper guiContainer, int mouseX, int mouseY) { IGuiElement hovered = guiContainer.getScreen().getContext().getHovered(); return hovered instanceof JeiIngredientProvider jip ? jip.getIngredient() : null; } @Override - public @NotNull List> getTargets(GuiScreenWrapper gui, @NotNull I ingredient, boolean doStart) { + public @NotNull List> getTargets(GuiContainerWrapper gui, @NotNull I ingredient, boolean doStart) { return gui.getScreen().getContext().getJeiSettings().getAllGhostIngredientTargets(ingredient); } @@ -48,7 +48,7 @@ public void onComplete() { @Nullable @Override - public IGuiProperties apply(@NotNull GuiScreenWrapper guiScreen) { + public IGuiProperties apply(@NotNull GuiContainerWrapper guiScreen) { return guiScreen.getScreen().getContext().getJeiSettings().isJeiEnabled(guiScreen.getScreen()) ? GuiProperties.create(guiScreen) : null; } diff --git a/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIJeiPlugin.java b/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIJeiPlugin.java index 89efadc2..3fe14471 100644 --- a/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIJeiPlugin.java +++ b/src/main/java/com/cleanroommc/modularui/integration/jei/ModularUIJeiPlugin.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.integration.jei; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; +import com.cleanroommc.modularui.screen.GuiContainerWrapper; import mezz.jei.api.IModPlugin; import mezz.jei.api.IModRegistry; @@ -14,8 +14,8 @@ public class ModularUIJeiPlugin implements IModPlugin { public void register(@NotNull IModRegistry registry) { ModularUIHandler uiHandler = new ModularUIHandler(); registry.addAdvancedGuiHandlers(uiHandler); - registry.addGhostIngredientHandler(GuiScreenWrapper.class, uiHandler); - registry.addGuiScreenHandler(GuiScreenWrapper.class, uiHandler); + registry.addGhostIngredientHandler(GuiContainerWrapper.class, uiHandler); + registry.addGuiScreenHandler(GuiContainerWrapper.class, uiHandler); registry.getRecipeTransferRegistry().addUniversalRecipeTransferHandler(uiHandler); } } diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java new file mode 100644 index 00000000..0b988e74 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java @@ -0,0 +1,42 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.screen.ModularScreen; + +import net.minecraft.client.gui.GuiScreen; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Function; +import java.util.function.Predicate; + +@ApiStatus.Experimental +public class OverlayHandler implements Comparable { + + private final Predicate test; + private final Function overlayFunction; + private final int priority; + + public OverlayHandler(Predicate test, Function overlayFunction) { + this(test, overlayFunction, 1000); + } + + public OverlayHandler(Predicate test, Function overlayFunction, int priority) { + this.test = test; + this.overlayFunction = overlayFunction; + this.priority = priority; + } + + public boolean isValidFor(GuiScreen screen) { + return this.test.test(screen); + } + + public ModularScreen createOverlay(GuiScreen screen) { + return this.overlayFunction.apply(screen); + } + + @Override + public int compareTo(@NotNull OverlayHandler o) { + return Integer.compare(this.priority, o.priority); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java new file mode 100644 index 00000000..97ef1acb --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java @@ -0,0 +1,42 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.screen.ModularScreen; + +import net.minecraft.client.Minecraft; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@ApiStatus.Experimental +public class OverlayManager { + + public static final List overlays = new ArrayList<>(); + + public static void register(OverlayHandler handler) { + if (!overlays.contains(handler)) { + overlays.add(handler); + overlays.sort(OverlayHandler::compareTo); + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onGuiOpen(GuiOpenEvent event) { + if (event.getGui() != Minecraft.getMinecraft().currentScreen) { + OverlayStack.closeAll(); + if (event.getGui() == null) return; + for (OverlayHandler handler : overlays) { + if (handler.isValidFor(event.getGui())) { + ModularScreen overlay = Objects.requireNonNull(handler.createOverlay(event.getGui()), "Overlays must not be null!"); + overlay.constructOverlay(event.getGui()); + OverlayStack.open(overlay); + } + } + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java new file mode 100644 index 00000000..d7a39f61 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java @@ -0,0 +1,113 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.screen.ClientScreenHandler; +import com.cleanroommc.modularui.screen.ModularScreen; + +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +@ApiStatus.Experimental +public class OverlayStack { + + private static final List overlay = new ArrayList<>(); + + public static void foreach(Consumer function, boolean topToBottom) { + if (topToBottom) { + for (int i = overlay.size() - 1; i >= 0; i--) { + function.accept(overlay.get(i)); + } + } else { + for (ModularScreen screen : overlay) { + function.accept(screen); + } + } + } + + public static boolean interact(Predicate function, boolean topToBottom) { + if (topToBottom) { + for (int i = overlay.size() - 1; i >= 0; i--) { + if (function.test(overlay.get(i))) { + return true; + } + } + } else { + for (ModularScreen screen : overlay) { + if (function.test(screen)) { + return true; + } + } + } + return false; + } + + public static void draw(int mouseX, int mouseY, float partialTicks) { + ModularScreen hovered = null; + ModularScreen fallback = null; + for (ModularScreen screen : overlay) { + GlStateManager.disableDepth(); + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + GlStateManager.color(1f, 1f, 1f, 1f); + screen.drawScreen(mouseX, mouseY, partialTicks); + GlStateManager.color(1f, 1f, 1f, 1f); + screen.drawForeground(partialTicks); + if (screen.getContext().getHovered() != null) hovered = screen; + fallback = screen; + } + ClientScreenHandler.drawDebugScreen(hovered, fallback); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + GlStateManager.enableRescaleNormal(); + RenderHelper.enableStandardItemLighting(); + } + + public static void open(ModularScreen screen) { + int i = overlay.indexOf(screen); + if (i >= 0 && i < overlay.size() - 1) { + overlay.remove(i); + } + overlay.add(screen); + screen.onOpen(); + } + + public static void close(ModularScreen screen) { + if (overlay.remove(screen)) { + screen.onCloseParent(); + } + } + + static void closeAll() { + for (int i = overlay.size() - 1; i >= 0; i--) { + ModularScreen screen = overlay.remove(i); + screen.onCloseParent(); + } + } + + public static void onTick() { + foreach(ModularScreen::onUpdate, true); + } + + @Nullable + public static IGuiElement getHoveredElement() { + for (int i = overlay.size() - 1; i >= 0; i--) { + ModularScreen screen = overlay.get(i); + IGuiElement hovered = screen.getContext().getHovered(); + if (hovered == null) continue; + return hovered; + } + return null; + } + + public static boolean isHoveringOverlay() { + return getHoveredElement() != null; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/ScreenWrapper.java b/src/main/java/com/cleanroommc/modularui/overlay/ScreenWrapper.java new file mode 100644 index 00000000..01ac4cbf --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/ScreenWrapper.java @@ -0,0 +1,41 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.api.IMuiScreen; +import com.cleanroommc.modularui.screen.ModularScreen; + +import net.minecraft.client.gui.GuiScreen; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; + +/** + * Wraps the current gui screen and uses it for overlays. + */ +@ApiStatus.Experimental +public class ScreenWrapper implements IMuiScreen { + + private final GuiScreen guiScreen; + private final ModularScreen screen; + + public ScreenWrapper(GuiScreen guiScreen, ModularScreen screen) { + this.guiScreen = guiScreen; + this.screen = screen; + } + + @Override + public @NotNull ModularScreen getScreen() { + return screen; + } + + @Override + public GuiScreen getGuiScreen() { + return guiScreen; + } + + @Override + public void updateGuiArea(Rectangle area) { + // overlay should not modify screen + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java new file mode 100644 index 00000000..2700d2f5 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -0,0 +1,515 @@ +package com.cleanroommc.modularui.screen; + +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.ModularUIConfig; +import com.cleanroommc.modularui.api.IMuiScreen; +import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IVanillaSlot; +import com.cleanroommc.modularui.core.mixin.GuiAccessor; +import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; +import com.cleanroommc.modularui.core.mixin.GuiScreenAccessor; +import com.cleanroommc.modularui.drawable.GuiDraw; +import com.cleanroommc.modularui.drawable.Stencil; +import com.cleanroommc.modularui.overlay.OverlayStack; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.LocatedWidget; +import com.cleanroommc.modularui.utils.Animator; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.utils.FpsCounter; +import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widgets.ItemSlot; +import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import com.cleanroommc.modularui.widgets.slot.SlotGroup; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiLabel; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.client.event.GuiContainerEvent; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import org.jetbrains.annotations.Nullable; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import java.awt.*; +import java.io.IOException; +import java.util.Objects; +import java.util.function.Predicate; + +public class ClientScreenHandler { + + private static ModularScreen currentScreen = null; + private static Character lastChar = null; + private static final FpsCounter fpsCounter = new FpsCounter(); + + @SubscribeEvent + public static void onGuiOpen(GuiOpenEvent event) { + if (event.getGui() instanceof IMuiScreen muiScreen) { + Objects.requireNonNull(muiScreen.getScreen(), "ModularScreen must not be null!"); + if (currentScreen != muiScreen.getScreen()) { + if (hasScreen()) { + currentScreen.onCloseParent(); + currentScreen = null; + lastChar = null; + } + currentScreen = muiScreen.getScreen(); + fpsCounter.reset(); + } + } else if (hasScreen() && getMCScreen() != null && event.getGui() != getMCScreen()) { + currentScreen.onCloseParent(); + currentScreen = null; + lastChar = null; + } + } + + @SubscribeEvent + public static void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + if (checkGui(event.getGui())) { + currentScreen.onResize(event.getGui().width, event.getGui().height); + } + OverlayStack.foreach(ms -> ms.onResize(event.getGui().width, event.getGui().height), false); + } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onGuiInputLow(GuiScreenEvent.KeyboardInputEvent.Pre event) throws IOException { + checkGui(event.getGui()); + if (handleKeyboardInput(currentScreen, event.getGui())) { + event.setCanceled(true); + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onGuiInputLow(GuiScreenEvent.MouseInputEvent.Pre event) throws IOException { + if (checkGui(event.getGui())) { + currentScreen.getContext().updateEventState(); + } + if (handleMouseInput(Mouse.getEventButton(), currentScreen, event.getGui())) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onScroll(GuiScreenEvent.MouseInputEvent.Pre event) { + int w = Mouse.getEventDWheel(); + if (w == 0) return; + ModularScreen.UpOrDown upOrDown = w > 0 ? ModularScreen.UpOrDown.UP : ModularScreen.UpOrDown.DOWN; + checkGui(event.getGui()); + if (doAction(currentScreen, ms -> ms.onMouseScroll(upOrDown, Math.abs(w)))) { + event.setCanceled(true); + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onGuiDraw(GuiScreenEvent.DrawScreenEvent.Pre event) { + if (checkGui(event.getGui())) { + drawScreen(currentScreen, currentScreen.getScreenWrapper().getGuiScreen(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks()); + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onGuiDraw(GuiScreenEvent.DrawScreenEvent.Post event) { + OverlayStack.draw(event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks()); + } + + @SubscribeEvent + public static void onTick(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + OverlayStack.onTick(); + if (checkGui()) { + currentScreen.onUpdate(); + } + } + } + + public static void onFrameUpdate() { + OverlayStack.foreach(ModularScreen::onFrameUpdate, true); + if (currentScreen != null) currentScreen.onFrameUpdate(); + Animator.advance(); + } + + private static boolean doAction(@Nullable ModularScreen muiScreen, Predicate action) { + return OverlayStack.interact(action, true) || (muiScreen != null && action.test(muiScreen)); + } + + private static boolean handleMouseInput(int button, @Nullable ModularScreen muiScreen, GuiScreen mcScreen) throws IOException { + GameSettings gameSettings = Minecraft.getMinecraft().gameSettings; + GuiScreenAccessor acc = (GuiScreenAccessor) mcScreen; + if (Mouse.getEventButtonState()) { + if (gameSettings.touchscreen) { + int val = acc.getTouchValue(); + if (val > 0) { + // we will cancel the event now, so we have to set the value + // otherwise the screen will handle it + acc.setTouchValue(val + 1); + return true; + } + } + acc.setEventButton(button); + acc.setLastMouseEvent(Minecraft.getSystemTime()); + return doAction(muiScreen, ms -> ms.onMousePressed(button)); + } + if (button != -1) { + if (gameSettings.touchscreen) { + int val = acc.getTouchValue(); + if (val - 1 > 0) { + // we will cancel the event now, so we have to set the value + // otherwise the screen will handle it + acc.setTouchValue(val - 1); + return true; + } + } + acc.setEventButton(-1); + return doAction(muiScreen, ms -> ms.onMouseRelease(button)); + } + if (acc.getEventButton() != -1 && acc.getLastMouseEvent() > 0L) { + long l = Minecraft.getSystemTime() - acc.getLastMouseEvent(); + return doAction(muiScreen, ms -> ms.onMouseDrag(acc.getEventButton(), l)); + } + return false; + } + + /** + * This replicates vanilla behavior while also injecting custom behavior for consistency + */ + private static boolean handleKeyboardInput(@Nullable ModularScreen muiScreen, GuiScreen mcScreen) throws IOException { + char c0 = Keyboard.getEventCharacter(); + int key = Keyboard.getEventKey(); + boolean state = Keyboard.getEventKeyState(); + + if (state) { + lastChar = c0; + return doAction(muiScreen, ms -> ms.onKeyPressed(c0, key)) || keyTyped(mcScreen, c0, key); + } else { + // when the key is released, the event char is empty + if (doAction(muiScreen, ms -> ms.onKeyRelease(lastChar, key))) return true; + if (key == 0 && c0 >= ' ') { + return keyTyped(mcScreen, c0, key); + } + } + return false; + } + + private static boolean keyTyped(GuiScreen screen, char typedChar, int keyCode) throws IOException { + if (currentScreen == null) return false; + // debug mode C + CTRL + SHIFT + ALT + if (keyCode == 46 && GuiScreen.isCtrlKeyDown() && GuiScreen.isShiftKeyDown() && GuiScreen.isAltKeyDown()) { + ModularUIConfig.guiDebugMode = !ModularUIConfig.guiDebugMode; + return true; + } + if (keyCode == 1 || Minecraft.getMinecraft().gameSettings.keyBindInventory.isActiveAndMatches(keyCode)) { + if (currentScreen.getContext().hasDraggable()) { + currentScreen.getContext().dropDraggable(); + } else { + currentScreen.getPanelManager().closeTopPanel(true); + } + return true; + } + return false; + } + + public static void dragSlot(long timeSinceLastClick) { + if (hasScreen() && getMCScreen() instanceof GuiScreenAccessor container) { + GuiContext ctx = currentScreen.getContext(); + container.invokeMouseClickMove(ctx.getAbsMouseX(), ctx.getAbsMouseY(), ctx.getMouseButton(), timeSinceLastClick); + } + } + + public static void clickSlot() { + if (hasScreen() && getMCScreen() instanceof GuiScreenAccessor screen) { + GuiContext ctx = currentScreen.getContext(); + try { + screen.invokeMouseClicked(ctx.getAbsMouseX(), ctx.getMouseY(), ctx.getMouseButton()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public static void releaseSlot() { + if (hasScreen() && getMCScreen() instanceof GuiScreenAccessor screen) { + GuiContext ctx = currentScreen.getContext(); + screen.invokeMouseReleased(ctx.getAbsMouseX(), ctx.getAbsMouseY(), ctx.getMouseButton()); + } + } + + public static boolean shouldDrawWorldBackground() { + return ModularUI.isBlurLoaded() || Minecraft.getMinecraft().world == null; + } + + public static void drawDarkBackground(GuiScreen screen, int tint) { + if (hasScreen()) { + float alpha = currentScreen.getMainPanel().getAlpha(); + // vanilla color values as hex + int color = 0x101010; + int startAlpha = 0xc0; + int endAlpha = 0xd0; + GuiDraw.drawVerticalGradientRect(0, 0, screen.width, screen.height, Color.withAlpha(color, (int) (startAlpha * alpha)), Color.withAlpha(color, (int) (endAlpha * alpha))); + } + } + + public static void drawScreen(ModularScreen muiScreen, GuiScreen mcScreen, int mouseX, int mouseY, float partialTicks) { + if (mcScreen instanceof GuiContainer container) { + drawContainer(muiScreen, container, mouseX, mouseY, partialTicks); + } else { + drawScreenInternal(muiScreen, mcScreen, mouseX, mouseY, partialTicks); + } + } + + public static void drawScreenInternal(ModularScreen muiScreen, GuiScreen mcScreen, int mouseX, int mouseY, float partialTicks) { + Stencil.reset(); + Stencil.apply(muiScreen.getScreenArea(), null); + muiScreen.drawScreen(mouseX, mouseY, partialTicks); + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + drawVanillaElements(mcScreen, mouseX, mouseY, partialTicks); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableRescaleNormal(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240.0F, 240.0F); + RenderHelper.disableStandardItemLighting(); + muiScreen.drawForeground(partialTicks); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + GlStateManager.enableRescaleNormal(); + RenderHelper.enableStandardItemLighting(); + Stencil.remove(); + } + + public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, int mouseX, int mouseY, float partialTicks) { + fpsCounter.onDraw(); + GuiContainerAccessor acc = (GuiContainerAccessor) mcScreen; + + Stencil.reset(); + Stencil.apply(muiScreen.getScreenArea(), null); + mcScreen.drawDefaultBackground(); + int x = mcScreen.getGuiLeft(); + int y = mcScreen.getGuiTop(); + + acc.invokeDrawGuiContainerBackgroundLayer(partialTicks, mouseX, mouseY); + muiScreen.drawScreen(mouseX, mouseY, partialTicks); + + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + // mainly for invtweaks compat + drawVanillaElements(mcScreen, mouseX, mouseY, partialTicks); + GlStateManager.pushMatrix(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableRescaleNormal(); + acc.setHoveredSlot(null); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240.0F, 240.0F); + GlStateManager.enableRescaleNormal(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + RenderHelper.disableStandardItemLighting(); + acc.invokeDrawGuiContainerForegroundLayer(mouseX, mouseY); + muiScreen.drawForeground(partialTicks); + RenderHelper.enableGUIStandardItemLighting(); + + acc.setHoveredSlot(null); + IGuiElement hovered = muiScreen.getContext().getHovered(); + if (hovered instanceof IVanillaSlot vanillaSlot) { + acc.setHoveredSlot(vanillaSlot.getVanillaSlot()); + } + + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.pushMatrix(); + GlStateManager.translate(x, y, 0); + MinecraftForge.EVENT_BUS.post(new GuiContainerEvent.DrawForeground(mcScreen, mouseX, mouseY)); + GlStateManager.popMatrix(); + + InventoryPlayer inventoryplayer = Minecraft.getMinecraft().player.inventory; + ItemStack itemstack = acc.getDraggedStack().isEmpty() ? inventoryplayer.getItemStack() : acc.getDraggedStack(); + GlStateManager.translate((float) x, (float) y, 0.0F); + if (!itemstack.isEmpty()) { + int k2 = acc.getDraggedStack().isEmpty() ? 8 : 16; + String s = null; + + if (!acc.getDraggedStack().isEmpty() && acc.getIsRightMouseClick()) { + itemstack = itemstack.copy(); + itemstack.setCount(MathHelper.ceil((float) itemstack.getCount() / 2.0F)); + } else if (acc.getDragSplitting() && acc.getDragSplittingSlots().size() > 1) { + itemstack = itemstack.copy(); + itemstack.setCount(acc.getDragSplittingRemnant()); + + if (itemstack.isEmpty()) { + s = TextFormatting.YELLOW + "0"; + } + } + + drawItemStack(mcScreen, itemstack, mouseX - x - 8, mouseY - y - k2, s); + } + + if (!acc.getReturningStack().isEmpty()) { + float f = (float) (Minecraft.getSystemTime() - acc.getReturningStackTime()) / 100.0F; + + if (f >= 1.0F) { + f = 1.0F; + acc.setReturningStack(ItemStack.EMPTY); + } + + int l2 = acc.getReturningStackDestSlot().xPos - acc.getTouchUpX(); + int i3 = acc.getReturningStackDestSlot().yPos - acc.getTouchUpY(); + int l1 = acc.getTouchUpX() + (int) ((float) l2 * f); + int i2 = acc.getTouchUpY() + (int) ((float) i3 * f); + drawItemStack(mcScreen, acc.getReturningStack(), l1, i2, null); + } + GlStateManager.popMatrix(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + GlStateManager.enableRescaleNormal(); + RenderHelper.enableStandardItemLighting(); + Stencil.remove(); + } + + private static void drawItemStack(GuiContainer mcScreen, ItemStack stack, int x, int y, String altText) { + GlStateManager.translate(0.0F, 0.0F, 32.0F); + ((GuiAccessor) mcScreen).setZLevel(200f); + ((GuiScreenAccessor) mcScreen).getItemRender().zLevel = 200.0F; + FontRenderer font = stack.getItem().getFontRenderer(stack); + if (font == null) font = ((GuiScreenAccessor) mcScreen).getFontRenderer(); + GlStateManager.enableDepth(); + ((GuiScreenAccessor) mcScreen).getItemRender().renderItemAndEffectIntoGUI(stack, x, y); + ((GuiScreenAccessor) mcScreen).getItemRender().renderItemOverlayIntoGUI(font, stack, x, y - (((GuiContainerAccessor) mcScreen).getDraggedStack().isEmpty() ? 0 : 8), altText); + GlStateManager.disableDepth(); + ((GuiAccessor) mcScreen).setZLevel(0f); + ((GuiScreenAccessor) mcScreen).getItemRender().zLevel = 0.0F; + } + + private static void drawVanillaElements(GuiScreen mcScreen, int mouseX, int mouseY, float partialTicks) { + for (GuiButton guiButton : ((GuiScreenAccessor) mcScreen).getButtonList()) { + guiButton.drawButton(Minecraft.getMinecraft(), mouseX, mouseY, partialTicks); + } + for (GuiLabel guiLabel : ((GuiScreenAccessor) mcScreen).getLabelList()) { + guiLabel.drawLabel(Minecraft.getMinecraft(), mouseX, mouseY); + } + } + + public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable ModularScreen fallback) { + if (!ModularUIConfig.guiDebugMode) return; + if (muiScreen == null) { + if (checkGui()) { + muiScreen = currentScreen; + } else { + if (fallback == null) return; + muiScreen = fallback; + } + } + GlStateManager.disableDepth(); + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + + GuiContext context = muiScreen.getContext(); + int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); + int screenH = muiScreen.getScreenArea().height; + int color = Color.rgb(180, 40, 115); + int lineY = screenH - 13; + Minecraft.getMinecraft().fontRenderer.drawStringWithShadow("Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, color); + lineY -= 11; + Minecraft.getMinecraft().fontRenderer.drawStringWithShadow("FPS: " + fpsCounter.getFps(), 5, screenH - 24, color); + LocatedWidget locatedHovered = muiScreen.getPanelManager().getTopWidgetLocated(true); + if (locatedHovered != null) { + drawSegmentLine(lineY -= 4, color); + lineY -= 10; + + IGuiElement hovered = locatedHovered.getElement(); + locatedHovered.applyMatrix(context); + GlStateManager.pushMatrix(); + context.applyToOpenGl(); + + Area area = hovered.getArea(); + IGuiElement parent = hovered.getParent(); + + GuiDraw.drawBorder(0, 0, area.width, area.height, color, 1f); + if (hovered.hasParent()) { + GuiDraw.drawBorder(-area.rx, -area.ry, parent.getArea().width, parent.getArea().height, Color.withAlpha(color, 0.3f), 1f); + } + GlStateManager.popMatrix(); + locatedHovered.unapplyMatrix(context); + GuiDraw.drawText("Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, lineY, 1, color, false); + lineY -= 11; + GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, 1, color, false); + lineY -= 11; + GuiDraw.drawText("Class: " + hovered, 5, lineY, 1, color, false); + if (hovered.hasParent()) { + drawSegmentLine(lineY -= 4, color); + lineY -= 10; + area = parent.getArea(); + GuiDraw.drawText("Parent size: " + area.width + ", " + area.height, 5, lineY, 1, color, false); + lineY -= 11; + GuiDraw.drawText("Parent: " + parent, 5, lineY, 1, color, false); + } + if (hovered instanceof ItemSlot slotWidget) { + drawSegmentLine(lineY -= 4, color); + lineY -= 10; + ModularSlot slot = slotWidget.getSlot(); + GuiDraw.drawText("Slot Index: " + slot.getSlotIndex(), 5, lineY, 1, color, false); + lineY -= 11; + GuiDraw.drawText("Slot Number: " + slot.slotNumber, 5, lineY, 1, color, false); + lineY -= 11; + if (slotWidget.isSynced()) { + SlotGroup slotGroup = slot.getSlotGroup(); + boolean allowShiftTransfer = slotGroup != null && slotGroup.allowShiftTransfer(); + GuiDraw.drawText("Shift-Click Priority: " + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), 5, lineY, 1, color, false); + } + } + } + // dot at mouse pos + GuiDraw.drawRect(mouseX, mouseY, 1, 1, Color.withAlpha(Color.GREEN.main, 0.8f)); + GlStateManager.color(1f, 1f, 1f, 1f); + } + + private static void drawSegmentLine(int y, int color) { + GuiDraw.drawRect(5, y, 140, 1, color); + } + + public static void updateGuiArea(GuiContainer container, Rectangle area) { + GuiContainerAccessor acc = (GuiContainerAccessor) container; + acc.setGuiLeft(area.x); + acc.setGuiTop(area.y); + acc.setXSize(area.width); + acc.setYSize(area.height); + } + + public static boolean hasScreen() { + return currentScreen != null; + } + + @Nullable + public static GuiScreen getMCScreen() { + return Minecraft.getMinecraft().currentScreen; + } + + @Nullable + public static ModularScreen getMuiScreen() { + return currentScreen; + } + + private static boolean checkGui() { + return checkGui(Minecraft.getMinecraft().currentScreen); + } + + private static boolean checkGui(GuiScreen screen) { + if (currentScreen == null || !(screen instanceof IMuiScreen muiScreen)) return false; + if (screen != Minecraft.getMinecraft().currentScreen || muiScreen.getScreen() != currentScreen) { + currentScreen = null; + lastChar = null; + return false; + } + return true; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/GuiContainerWrapper.java b/src/main/java/com/cleanroommc/modularui/screen/GuiContainerWrapper.java new file mode 100644 index 00000000..1765c968 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/screen/GuiContainerWrapper.java @@ -0,0 +1,36 @@ +package com.cleanroommc.modularui.screen; + +import com.cleanroommc.modularui.api.IMuiScreen; + +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import org.jetbrains.annotations.NotNull; + +@SideOnly(Side.CLIENT) +public class GuiContainerWrapper extends GuiContainer implements IMuiScreen { + + private final ModularScreen screen; + + public GuiContainerWrapper(ModularContainer container, ModularScreen screen) { + super(container); + this.screen = screen; + this.screen.construct(this); + container.setScreen(this.screen); + } + + @Override + public void drawWorldBackground(int tint) { + handleDrawBackground(tint, super::drawWorldBackground); + } + + @Override + protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) { + } + + @Override + public @NotNull ModularScreen getScreen() { + return this.screen; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/GuiScreenWrapper.java b/src/main/java/com/cleanroommc/modularui/screen/GuiScreenWrapper.java index 2d5542a2..fb59dc2f 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/GuiScreenWrapper.java +++ b/src/main/java/com/cleanroommc/modularui/screen/GuiScreenWrapper.java @@ -1,416 +1,31 @@ package com.cleanroommc.modularui.screen; -import com.cleanroommc.modularui.GuiErrorHandler; -import com.cleanroommc.modularui.ModularUI; -import com.cleanroommc.modularui.ModularUIConfig; -import com.cleanroommc.modularui.api.widget.IGuiElement; -import com.cleanroommc.modularui.api.widget.IVanillaSlot; -import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; -import com.cleanroommc.modularui.drawable.GuiDraw; -import com.cleanroommc.modularui.drawable.Stencil; -import com.cleanroommc.modularui.screen.viewport.GuiContext; -import com.cleanroommc.modularui.screen.viewport.LocatedWidget; -import com.cleanroommc.modularui.utils.Color; -import com.cleanroommc.modularui.widget.sizer.Area; -import com.cleanroommc.modularui.widgets.ItemSlot; -import com.cleanroommc.modularui.widgets.slot.ModularSlot; -import com.cleanroommc.modularui.widgets.slot.SlotGroup; +import com.cleanroommc.modularui.api.IMuiScreen; + +import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiLabel; -import net.minecraft.client.gui.inventory.GuiContainer; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.RenderItem; -import net.minecraft.entity.player.InventoryPlayer; -import net.minecraft.inventory.ClickType; -import net.minecraft.inventory.Slot; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.text.TextFormatting; -import net.minecraftforge.client.event.GuiContainerEvent; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -import org.lwjgl.input.Keyboard; -import org.lwjgl.opengl.GL11; - -import java.io.IOException; -import java.util.Set; +import org.jetbrains.annotations.NotNull; @SideOnly(Side.CLIENT) -public class GuiScreenWrapper extends GuiContainer { +public class GuiScreenWrapper extends GuiScreen implements IMuiScreen { private final ModularScreen screen; - private boolean init = true; - private char lastChar; - - private int fps, frameCount = 0; - private long timer = Minecraft.getSystemTime(); - public GuiScreenWrapper(ModularContainer container, ModularScreen screen) { - super(container); + public GuiScreenWrapper(ModularScreen screen) { this.screen = screen; this.screen.construct(this); - container.setScreen(this.screen); - } - - @Override - public void initGui() { - GuiErrorHandler.INSTANCE.clear(); - super.initGui(); - if (this.init) { - this.screen.onOpen(); - this.init = false; - } - this.screen.onResize(this.width, this.height); - } - - public void updateArea(Area mainViewport) { - this.guiLeft = mainViewport.x; - this.guiTop = mainViewport.y; - this.xSize = mainViewport.width; - this.ySize = mainViewport.height; - } - - public GuiContainerAccessor getAccessor() { - return (GuiContainerAccessor) this; - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - this.frameCount++; - long time = Minecraft.getSystemTime(); - if (time - this.timer >= 1000) { - this.fps = this.frameCount; - this.frameCount = 0; - this.timer += 1000; - } - - Stencil.reset(); - Stencil.apply(this.screen.getScreenArea(), null); - drawDefaultBackground(); - int i = this.guiLeft; - int j = this.guiTop; - - this.drawGuiContainerBackgroundLayer(partialTicks, mouseX, mouseY); - this.screen.drawScreen(mouseX, mouseY, partialTicks); - - GlStateManager.disableLighting(); - GlStateManager.disableDepth(); - // mainly for invtweaks compat - drawVanillaElements(mouseX, mouseY, partialTicks); - GlStateManager.pushMatrix(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.enableRescaleNormal(); - getAccessor().setHoveredSlot(null); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240.0F, 240.0F); - GlStateManager.enableRescaleNormal(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - RenderHelper.disableStandardItemLighting(); - this.drawGuiContainerForegroundLayer(mouseX, mouseY); - this.screen.drawForeground(partialTicks); - RenderHelper.enableGUIStandardItemLighting(); - - getAccessor().setHoveredSlot(null); - IGuiElement hovered = this.screen.getContext().getHovered(); - if (hovered instanceof IVanillaSlot vanillaSlot) { - getAccessor().setHoveredSlot(vanillaSlot.getVanillaSlot()); - } - - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.pushMatrix(); - GlStateManager.translate(i, j, 0); - MinecraftForge.EVENT_BUS.post(new GuiContainerEvent.DrawForeground(this, mouseX, mouseY)); - GlStateManager.popMatrix(); - - InventoryPlayer inventoryplayer = this.mc.player.inventory; - ItemStack itemstack = getAccessor().getDraggedStack().isEmpty() ? inventoryplayer.getItemStack() : getAccessor().getDraggedStack(); - GlStateManager.translate((float) i, (float) j, 0.0F); - if (!itemstack.isEmpty()) { - int k2 = getAccessor().getDraggedStack().isEmpty() ? 8 : 16; - String s = null; - - if (!getAccessor().getDraggedStack().isEmpty() && getAccessor().getIsRightMouseClick()) { - itemstack = itemstack.copy(); - itemstack.setCount(MathHelper.ceil((float) itemstack.getCount() / 2.0F)); - } else if (this.dragSplitting && this.dragSplittingSlots.size() > 1) { - itemstack = itemstack.copy(); - itemstack.setCount(getAccessor().getDragSplittingRemnant()); - - if (itemstack.isEmpty()) { - s = TextFormatting.YELLOW + "0"; - } - } - - this.drawItemStack(itemstack, mouseX - i - 8, mouseY - j - k2, s); - } - - if (!getAccessor().getReturningStack().isEmpty()) { - float f = (float) (Minecraft.getSystemTime() - getAccessor().getReturningStackTime()) / 100.0F; - - if (f >= 1.0F) { - f = 1.0F; - getAccessor().setReturningStack(ItemStack.EMPTY); - } - - int l2 = getAccessor().getReturningStackDestSlot().xPos - getAccessor().getTouchUpX(); - int i3 = getAccessor().getReturningStackDestSlot().yPos - getAccessor().getTouchUpY(); - int l1 = getAccessor().getTouchUpX() + (int) ((float) l2 * f); - int i2 = getAccessor().getTouchUpY() + (int) ((float) i3 * f); - this.drawItemStack(getAccessor().getReturningStack(), l1, i2, null); - } - - GlStateManager.popMatrix(); - - if (ModularUIConfig.guiDebugMode) { - GlStateManager.disableDepth(); - GlStateManager.disableLighting(); - GlStateManager.enableBlend(); - drawDebugScreen(); - GlStateManager.color(1f, 1f, 1f, 1f); - } - GuiErrorHandler.INSTANCE.drawErrors(0, 0); - - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - GlStateManager.enableRescaleNormal(); - RenderHelper.enableStandardItemLighting(); - - Stencil.remove(); } @Override public void drawWorldBackground(int tint) { - if (ModularUI.isBlurLoaded() || this.mc.world == null) { - super.drawWorldBackground(tint); - return; - } - float alpha = this.screen.getMainPanel().getAlpha(); - // vanilla color values as hex - int color = 0x101010; - int startAlpha = 0xc0; - int endAlpha = 0xd0; - this.drawGradientRect(0, 0, this.width, this.height, Color.withAlpha(color, (int) (startAlpha * alpha)), Color.withAlpha(color, (int) (endAlpha * alpha))); - } - - private void drawItemStack(ItemStack stack, int x, int y, String altText) { - GlStateManager.translate(0.0F, 0.0F, 32.0F); - this.zLevel = 200.0F; - this.itemRender.zLevel = 200.0F; - FontRenderer font = stack.getItem().getFontRenderer(stack); - if (font == null) font = this.fontRenderer; - GlStateManager.enableDepth(); - this.itemRender.renderItemAndEffectIntoGUI(stack, x, y); - this.itemRender.renderItemOverlayIntoGUI(font, stack, x, y - (getAccessor().getDraggedStack().isEmpty() ? 0 : 8), altText); - GlStateManager.disableDepth(); - this.zLevel = 0.0F; - this.itemRender.zLevel = 0.0F; - } - - protected void drawVanillaElements(int mouseX, int mouseY, float partialTicks) { - for (GuiButton guiButton : this.buttonList) { - guiButton.drawButton(this.mc, mouseX, mouseY, partialTicks); - } - for (GuiLabel guiLabel : this.labelList) { - guiLabel.drawLabel(this.mc, mouseX, mouseY); - } - } - - public void drawDebugScreen() { - GuiContext context = this.screen.getContext(); - int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); - int screenH = this.screen.getScreenArea().height; - int color = Color.rgb(180, 40, 115); - int lineY = screenH - 13; - drawString(this.fontRenderer, "Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, color); - lineY -= 11; - drawString(this.fontRenderer, "FPS: " + this.fps, 5, screenH - 24, color); - LocatedWidget locatedHovered = this.screen.getPanelManager().getTopWidgetLocated(true); - if (locatedHovered != null) { - drawSegmentLine(lineY -= 4, color); - lineY -= 10; - - IGuiElement hovered = locatedHovered.getElement(); - locatedHovered.applyMatrix(context); - GlStateManager.pushMatrix(); - context.applyToOpenGl(); - - Area area = hovered.getArea(); - IGuiElement parent = hovered.getParent(); - - GuiDraw.drawBorder(0, 0, area.width, area.height, color, 1f); - if (hovered.hasParent()) { - GuiDraw.drawBorder(-area.rx, -area.ry, parent.getArea().width, parent.getArea().height, Color.withAlpha(color, 0.3f), 1f); - } - GlStateManager.popMatrix(); - locatedHovered.unapplyMatrix(context); - GuiDraw.drawText("Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, lineY, 1, color, false); - lineY -= 11; - GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, 1, color, false); - lineY -= 11; - GuiDraw.drawText("Class: " + hovered, 5, lineY, 1, color, false); - if (hovered.hasParent()) { - drawSegmentLine(lineY -= 4, color); - lineY -= 10; - area = parent.getArea(); - GuiDraw.drawText("Parent size: " + area.width + ", " + area.height, 5, lineY, 1, color, false); - lineY -= 11; - GuiDraw.drawText("Parent: " + parent, 5, lineY, 1, color, false); - } - if (hovered instanceof ItemSlot slotWidget) { - drawSegmentLine(lineY -= 4, color); - lineY -= 10; - ModularSlot slot = slotWidget.getSlot(); - GuiDraw.drawText("Slot Index: " + slot.getSlotIndex(), 5, lineY, 1, color, false); - lineY -= 11; - GuiDraw.drawText("Slot Number: " + slot.slotNumber, 5, lineY, 1, color, false); - lineY -= 11; - if (slotWidget.isSynced()) { - SlotGroup slotGroup = slot.getSlotGroup(); - boolean allowShiftTransfer = slotGroup != null && slotGroup.allowShiftTransfer(); - GuiDraw.drawText("Shift-Click Priority: " + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), 5, lineY, 1, color, false); - } - } - } - // dot at mouse pos - drawRect(mouseX, mouseY, mouseX + 1, mouseY + 1, Color.withAlpha(Color.GREEN.main, 0.8f)); - } - - private void drawSegmentLine(int y, int color) { - GuiDraw.drawRect(5, y, 140, 1, color); - } - - @Override - protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) { - } - - @Override - public void updateScreen() { - super.updateScreen(); - this.screen.onUpdate(); + handleDrawBackground(tint, super::drawWorldBackground); } @Override - public void onGuiClosed() { - super.onGuiClosed(); - this.screen.onClose(); - this.init = true; - } - - public ModularScreen getScreen() { + public @NotNull ModularScreen getScreen() { return this.screen; } - - @Override - protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { - if (this.screen.onMousePressed(mouseButton)) return; - super.mouseClicked(mouseX, mouseY, mouseButton); - } - - public void clickSlot() { - try { - super.mouseClicked(this.screen.getContext().getAbsMouseX(), this.screen.getContext().getAbsMouseY(), this.screen.getContext().getMouseButton()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - protected void mouseReleased(int mouseX, int mouseY, int state) { - if (this.screen.onMouseRelease(state)) return; - super.mouseReleased(mouseX, mouseY, state); - } - - public void releaseSlot() { - super.mouseReleased(this.screen.getContext().getAbsMouseX(), this.screen.getContext().getAbsMouseY(), this.screen.getContext().getMouseButton()); - } - - @Override - protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { - if (this.screen.onMouseDrag(clickedMouseButton, timeSinceLastClick)) return; - super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); - } - - public void dragSlot(long timeSinceLastClick) { - super.mouseClickMove(this.screen.getContext().getAbsMouseX(), this.screen.getContext().getAbsMouseY(), this.screen.getContext().getMouseButton(), timeSinceLastClick); - } - - /** - * This replicates vanilla behavior while also injecting custom behavior for consistency - */ - @Override - public void handleKeyboardInput() { - char c0 = Keyboard.getEventCharacter(); - int key = Keyboard.getEventKey(); - boolean state = Keyboard.getEventKeyState(); - - if (state) { - this.lastChar = c0; - if (this.screen.onKeyPressed(c0, key)) return; - keyTyped(c0, key); - } else { - // when the key is released, the event char is empty - if (this.screen.onKeyRelease(this.lastChar, key)) return; - if (key == 0 && c0 >= ' ') { - keyTyped(c0, key); - } - } - - this.mc.dispatchKeypresses(); - } - - @Override - protected void keyTyped(char typedChar, int keyCode) { - // debug mode C + CTRL + SHIFT + ALT - if (keyCode == 46 && isCtrlKeyDown() && isShiftKeyDown() && isAltKeyDown()) { - ModularUIConfig.guiDebugMode = !ModularUIConfig.guiDebugMode; - return; - } - if (keyCode == 1 || this.mc.gameSettings.keyBindInventory.isActiveAndMatches(keyCode)) { - if (this.screen.getContext().hasDraggable()) { - this.screen.getContext().dropDraggable(); - } else { - this.screen.getPanelManager().closeTopPanel(true); - } - } - - this.checkHotbarKeys(keyCode); - Slot hoveredSlot = getAccessor().getHoveredSlot(); - if (hoveredSlot != null && hoveredSlot.getHasStack()) { - if (this.mc.gameSettings.keyBindPickBlock.isActiveAndMatches(keyCode)) { - this.handleMouseClick(hoveredSlot, hoveredSlot.slotNumber, 0, ClickType.CLONE); - } else if (this.mc.gameSettings.keyBindDrop.isActiveAndMatches(keyCode)) { - this.handleMouseClick(hoveredSlot, hoveredSlot.slotNumber, isCtrlKeyDown() ? 1 : 0, ClickType.THROW); - } - } - } - - public boolean isDragSplitting() { - return this.dragSplitting; - } - - public Set getDragSlots() { - return this.dragSplittingSlots; - } - - public RenderItem getItemRenderer() { - return this.itemRender; - } - - public float getZ() { - return this.zLevel; - } - - public void setZ(float z) { - this.zLevel = z; - } - - public FontRenderer getFontRenderer() { - return this.fontRenderer; - } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index c65c7dc0..6437162c 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -2,6 +2,7 @@ import com.cleanroommc.modularui.api.IPanelHandler; import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.layout.IViewport; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IFocusedWidget; @@ -58,6 +59,7 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int hei private final Input keyboard = new Input(); private final Input mouse = new Input(); + private boolean invisible = false; private Animator animator; private float scale = 1f; private float alpha = 1f; @@ -177,6 +179,11 @@ private void findHoveredWidgets() { stack.popViewport(null); } + @Override + public boolean canHover() { + return !this.invisible && super.canHover(); + } + @MustBeInvokedByOverriders public void onOpen(ModularScreen screen) { this.screen = screen; @@ -566,7 +573,9 @@ public int getDefaultWidth() { final void setPanelGuiContext(@NotNull GuiContext context) { setContext(context); - context.getJeiSettings().addJeiExclusionArea(this); + if (!context.getScreen().isOverlay()) { + context.getJeiSettings().addJeiExclusionArea(this); + } } public boolean isOpening() { @@ -609,7 +618,7 @@ protected Animator getAnimator() { } public boolean shouldAnimate() { - return getScreen().getCurrentTheme().getOpenCloseAnimationOverride() > 0; + return !getScreen().isOverlay() && getScreen().getCurrentTheme().getOpenCloseAnimationOverride() > 0; } public ModularPanel bindPlayerInventory() { @@ -620,6 +629,11 @@ public ModularPanel bindPlayerInventory(int bottom) { return child(SlotGroupWidget.playerInventory(bottom)); } + public ModularPanel invisible() { + this.invisible = true; + return background(IDrawable.EMPTY); + } + @Override public String toString() { return super.toString() + "#" + getName(); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index dc181d12..1fafaf24 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -1,11 +1,14 @@ package com.cleanroommc.modularui.screen; import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.IMuiScreen; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.widget.IGuiAction; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.drawable.GuiDraw; +import com.cleanroommc.modularui.overlay.ScreenWrapper; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.value.sync.ModularSyncManager; @@ -14,6 +17,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderHelper; import net.minecraft.util.ResourceLocation; @@ -40,7 +44,7 @@ public class ModularScreen { public static boolean isScreen(@Nullable GuiScreen guiScreen, String owner, String name) { - if (guiScreen instanceof GuiScreenWrapper screenWrapper) { + if (guiScreen instanceof GuiContainerWrapper screenWrapper) { ModularScreen screen = screenWrapper.getScreen(); return screen.getOwner().equals(owner) && screen.getName().equals(name); } @@ -53,7 +57,7 @@ public static boolean isActive(String owner, String name) { @Nullable public static ModularScreen getCurrent() { - if (Minecraft.getMinecraft().currentScreen instanceof GuiScreenWrapper screenWrapper) { + if (MCHelper.getCurrentScreen() instanceof GuiContainerWrapper screenWrapper) { return screenWrapper.getScreen(); } return null; @@ -68,7 +72,8 @@ public static ModularScreen getCurrent() { private final Object2ObjectArrayMap frameUpdates = new Object2ObjectArrayMap<>(); private ITheme currentTheme; - private GuiScreenWrapper screenWrapper; + private IMuiScreen screenWrapper; + private boolean overlay = false; /** * Creates a new screen with a ModularUI as its owner and a given {@link ModularPanel}. @@ -124,14 +129,28 @@ ModularPanel buildUI(GuiContext context) { } @MustBeInvokedByOverriders - void construct(GuiScreenWrapper wrapper) { + public void construct(IMuiScreen wrapper) { if (this.screenWrapper != null) throw new IllegalStateException("ModularScreen is already constructed!"); if (wrapper == null) throw new NullPointerException("GuiScreenWrapper must not be null!"); this.screenWrapper = wrapper; - this.screenWrapper.updateArea(this.panelManager.getMainPanel().getArea()); + this.screenWrapper.updateGuiArea(this.panelManager.getMainPanel().getArea()); + this.overlay = false; } + @ApiStatus.Internal + @MustBeInvokedByOverriders + public void constructOverlay(GuiScreen screen) { + if (this.screenWrapper != null) throw new IllegalStateException("ModularScreen is already constructed!"); + if (screen == null) throw new NullPointerException("GuiScreenWrapper must not be null!"); + this.screenWrapper = new ScreenWrapper(screen, this); + this.overlay = true; + } + + @MustBeInvokedByOverriders public void onResize(int width, int height) { + if (this.panelManager.tryInit()) { + onOpen(); + } this.screenArea.set(0, 0, width, height); this.screenArea.z(0); @@ -141,16 +160,23 @@ public void onResize(int width, int height) { } this.context.popViewport(null); - this.screenWrapper.updateArea(this.panelManager.getMainPanel().getArea()); + if (!isOverlay()) { + this.screenWrapper.updateGuiArea(this.panelManager.getMainPanel().getArea()); + } } + public final void onCloseParent() { + if (this.panelManager.closeAll()) { + onClose(); + } + } + + @ApiStatus.OverrideOnly public void onOpen() { - this.panelManager.init(); } - @MustBeInvokedByOverriders + @ApiStatus.OverrideOnly public void onClose() { - this.panelManager.closeAll(); } public void close() { @@ -160,7 +186,7 @@ public void close() { public void close(boolean force) { if (isActive()) { if (force) { - this.context.mc.player.closeScreen(); + MCHelper.closeScreen(); return; } getMainPanel().closeIfOpen(true); @@ -383,6 +409,10 @@ public ResourceLocation getResourceLocation() { return new ResourceLocation(this.owner, this.name); } + public boolean isOverlay() { + return overlay; + } + public GuiContext getContext() { return this.context; } @@ -399,7 +429,7 @@ public ModularPanel getMainPanel() { return this.panelManager.getMainPanel(); } - public GuiScreenWrapper getScreenWrapper() { + public IMuiScreen getScreenWrapper() { return this.screenWrapper; } @@ -408,11 +438,17 @@ public Area getScreenArea() { } public boolean isClientOnly() { - return getContainer().isClientOnly(); + return isOverlay() || !this.screenWrapper.isGuiContainer() || getContainer().isClientOnly(); } public ModularContainer getContainer() { - return (ModularContainer) this.screenWrapper.inventorySlots; + if (isOverlay()) { + throw new IllegalStateException("Can't get ModularContainer for overlay"); + } + if (this.screenWrapper.getGuiScreen() instanceof GuiContainer container) { + return (ModularContainer) container.inventorySlots; + } + throw new IllegalStateException("Screen does not extend GuiContainer!"); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java b/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java index eb6b4988..7d43c123 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java +++ b/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java @@ -44,13 +44,15 @@ public PanelManager(ModularScreen screen, ModularPanel panel) { this.mainPanel = Objects.requireNonNull(panel, "Main panel must not be null!"); } - void init() { + boolean tryInit() { if (this.state == State.CLOSED) throw new IllegalStateException("Can't init in closed state!"); if (this.state == State.INIT || this.state == State.DISPOSED) { setState(State.OPEN); openPanel(this.mainPanel, false); checkDirty(); + return true; } + return false; } public boolean isMainPanel(ModularPanel panel) { @@ -170,11 +172,13 @@ public void closeTopPanel(boolean animate) { getTopMostPanel().closeIfOpen(animate); } - public void closeAll() { + public boolean closeAll() { if (this.state.isOpen) { this.panels.forEach(this::finalizePanel); setState(State.CLOSED); + return true; } + return false; } private void finalizePanel(ModularPanel panel) { diff --git a/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java b/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java index 2501065d..0d6ad48c 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java +++ b/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java @@ -81,7 +81,7 @@ public void draw(GuiContext context, @Nullable ItemStack stack) { this.maxWidth = Integer.MAX_VALUE; } if (stack == null) stack = ItemStack.EMPTY; - Area screen = context.screen.getScreenArea(); + Area screen = context.getScreen().getScreenArea(); int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); IconRenderer renderer = IconRenderer.SHARED; List textLines = lines.stream().filter(drawable -> drawable instanceof IKey).map(key -> ((IKey) key).get()).collect(Collectors.toList()); @@ -133,7 +133,7 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, int height = (int) renderer.getLastHeight(); if (!this.customPos) { - this.pos = context.screen.getCurrentTheme().getTooltipPosOverride(); + this.pos = context.getScreen().getCurrentTheme().getTooltipPosOverride(); } if (this.pos == null) { @@ -211,7 +211,7 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, if (this.pos == Pos.HORIZONTAL) { if (area.x > screenWidth - area.ex()) { pos = Pos.LEFT; - x = 0; + // x = 0; } else { pos = Pos.RIGHT; x = screenWidth - area.ex() + padding; diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java index 93f9416f..dd14df79 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java @@ -1,13 +1,14 @@ package com.cleanroommc.modularui.screen.viewport; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.widget.*; -import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; import com.cleanroommc.modularui.screen.*; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.entity.EntityPlayerSP; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -26,10 +27,8 @@ */ public class GuiContext extends GuiViewportStack { - public final Minecraft mc; - public final FontRenderer font; - /* GUI elements */ + @Deprecated public final ModularScreen screen; private LocatedWidget focusedWidget = LocatedWidget.EMPTY; @Nullable @@ -62,9 +61,11 @@ public class GuiContext extends GuiViewportStack { public GuiContext(ModularScreen screen) { this.screen = screen; - this.hoveredWidgets = new HoveredIterable(this.screen.getPanelManager()); - this.mc = Minecraft.getMinecraft(); - this.font = this.mc.fontRenderer; + this.hoveredWidgets = new HoveredIterable(this.screen.getPanelManager());; + } + + public ModularScreen getScreen() { + return screen; } /** @@ -255,7 +256,8 @@ public boolean hasDraggable() { } public boolean isMouseItemEmpty() { - return this.mc.player.inventory.getItemStack().isEmpty(); + EntityPlayerSP player = MCHelper.getPlayer(); + return player == null || player.inventory.getItemStack().isEmpty(); } @ApiStatus.Internal @@ -364,9 +366,9 @@ public void onFrameUpdate() { if (this.hovered != null) { this.hovered.onMouseStartHover(); if (this.hovered instanceof IVanillaSlot vanillaSlot) { - ((GuiContainerAccessor) this.screen.getScreenWrapper()).setHoveredSlot(vanillaSlot.getVanillaSlot()); + this.screen.getScreenWrapper().setHoveredSlot(vanillaSlot.getVanillaSlot()); } else { - ((GuiContainerAccessor) this.screen.getScreenWrapper()).setHoveredSlot(null); + this.screen.getScreenWrapper().setHoveredSlot(null); } } } else { @@ -449,6 +451,9 @@ public ITheme getTheme() { } public JeiSettingsImpl getJeiSettings() { + if (this.screen.isOverlay()) { + throw new IllegalStateException("Overlays don't have JEI settings!"); + } if (this.jeiSettings == null) { throw new IllegalStateException("The screen is not yet initialised!"); } diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java index 485e9152..1be5ff15 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java @@ -3,13 +3,12 @@ import com.cleanroommc.modularui.api.layout.IViewport; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.utils.GuiUtils; +import com.cleanroommc.modularui.utils.Matrix4f; +import com.cleanroommc.modularui.utils.Vector3f; import com.cleanroommc.modularui.widget.sizer.Area; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.jetbrains.annotations.Nullable; -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector2f; -import org.lwjgl.util.vector.Vector3f; import java.util.ArrayList; import java.util.List; @@ -133,7 +132,7 @@ public void popUntilViewport(IViewport viewport) { public void translate(float x, float y) { checkViewport(); - this.top.getMatrix().translate(new Vector2f(x, y)); + this.top.getMatrix().translate(x, y); this.top.markDirty(); } diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/TransformationMatrix.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/TransformationMatrix.java index 301d23be..dfbc5d13 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/TransformationMatrix.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/TransformationMatrix.java @@ -1,11 +1,11 @@ package com.cleanroommc.modularui.screen.viewport; import com.cleanroommc.modularui.api.layout.IViewport; +import com.cleanroommc.modularui.utils.Matrix4f; +import com.cleanroommc.modularui.utils.Vector3f; import com.cleanroommc.modularui.widget.sizer.Area; import org.jetbrains.annotations.Nullable; -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector3f; /** * A single matrix in a matrix stack. Also has some other information. diff --git a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java new file mode 100644 index 00000000..55def5c3 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java @@ -0,0 +1,73 @@ +package com.cleanroommc.modularui.test; + +import com.cleanroommc.modularui.api.drawable.IDrawable; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.overlay.OverlayHandler; +import com.cleanroommc.modularui.overlay.OverlayManager; +import com.cleanroommc.modularui.screen.CustomModularScreen; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.TextWidget; + +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.inventory.GuiContainer; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.atomic.AtomicInteger; + +public class OverlayTest { + + public static void init() { + + OverlayManager.register(new OverlayHandler(screen -> screen instanceof GuiMainMenu, screen -> { + GuiMainMenu gui = (GuiMainMenu) screen; + TextWidget title = new TextWidget(IKey.str("ModularUI")); + int[] colors = {Color.WHITE.main, Color.AMBER.main, Color.BLUE.main, Color.GREEN.main, Color.DEEP_PURPLE.main, Color.RED.main}; + AtomicInteger k = new AtomicInteger(); + return new ModularScreen(ModularPanel.defaultPanel("overlay").sizeRel(1f) + .background(IDrawable.EMPTY) + .child(title.scale(5f) + .shadow(true) + .color(colors[k.get()]) + .leftRel(0.5f).topRel(0.07f)) + .child(new ButtonWidget<>() // test button overlapping + .topRel(0.25f, 59, 0f) + .leftRelOffset(0.5f, 91) + .size(44) + .overlay(IKey.str("Fun Button")) + .onMousePressed(mouseButton -> { + k.set((k.get() + 1) % colors.length); + title.color(colors[k.get()]); + return true; + }))); + })); + + OverlayManager.register(new OverlayHandler(screen -> screen instanceof GuiContainer, screen -> { + GuiContainer gui = (GuiContainer) screen; + return new CustomModularScreen() { + + @Override + public @NotNull ModularPanel buildUI(GuiContext context) { + return ModularPanel.defaultPanel("watermark_overlay", gui.getXSize(), gui.getYSize()) + .pos(gui.getGuiLeft(), gui.getGuiTop()) + .background(IDrawable.EMPTY) + .child(GuiTextures.MUI_LOGO.asIcon().asWidget() + .top(5).right(5) + .size(18)); + } + + @Override + public void onResize(int width, int height) { + getMainPanel().pos(gui.getGuiLeft(), gui.getGuiTop()) + .size(gui.getXSize(), gui.getYSize()); + super.onResize(width, height); + } + }; + })); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/test/TestBlock.java b/src/main/java/com/cleanroommc/modularui/test/TestBlock.java index 9d69d668..9b9c742f 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestBlock.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestBlock.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.test; import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.factory.GuiFactories; import com.cleanroommc.modularui.factory.TileEntityGuiFactory; import net.minecraft.block.Block; @@ -73,7 +74,7 @@ public TileEntity createNewTileEntity(@NotNull World worldIn, int meta) { @Override public boolean onBlockActivated(World worldIn, @NotNull BlockPos pos, @NotNull IBlockState state, @NotNull EntityPlayer playerIn, @NotNull EnumHand hand, @NotNull EnumFacing facing, float hitX, float hitY, float hitZ) { if (!worldIn.isRemote) { - TileEntityGuiFactory.open(playerIn, pos); + GuiFactories.tileEntity().open(playerIn, pos); } return true; } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGui.java b/src/main/java/com/cleanroommc/modularui/test/TestGui.java index 28b24b06..88ad5833 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGui.java @@ -35,7 +35,6 @@ public class TestGui extends CustomModularScreen { @Override public void onClose() { - super.onClose(); ModularUI.LOGGER.info("New values: {}", this.configuredOptions); } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestItem.java b/src/main/java/com/cleanroommc/modularui/test/TestItem.java index 04d3b2db..2b720743 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestItem.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestItem.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.test; import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.GuiFactories; import com.cleanroommc.modularui.factory.HandGuiData; import com.cleanroommc.modularui.factory.ItemGuiFactory; import com.cleanroommc.modularui.screen.ModularPanel; @@ -62,7 +63,7 @@ public ModularPanel buildUI(HandGuiData guiData, PanelSyncManager guiSyncManager @Nonnull public ActionResult onItemRightClick(World world, @NotNull EntityPlayer player, @Nonnull EnumHand hand) { if (!world.isRemote) { - ItemGuiFactory.open((EntityPlayerMP) player, hand); + GuiFactories.item().open(player, hand); } return new ActionResult<>(EnumActionResult.SUCCESS, player.getHeldItem(hand)); } diff --git a/src/main/java/com/cleanroommc/modularui/theme/WidgetTheme.java b/src/main/java/com/cleanroommc/modularui/theme/WidgetTheme.java index da30c29f..b77588ea 100644 --- a/src/main/java/com/cleanroommc/modularui/theme/WidgetTheme.java +++ b/src/main/java/com/cleanroommc/modularui/theme/WidgetTheme.java @@ -56,4 +56,9 @@ public int getTextColor() { public boolean getTextShadow() { return this.textShadow; } + + public WidgetTheme withColor(int color) { + // TODO it is currently somewhat difficult to color drawable with a custom color. This is a dirty solution. + return new WidgetTheme(this.background, this.hoverBackground, color, this.textColor, this.textShadow); + } } diff --git a/src/main/java/com/cleanroommc/modularui/utils/FpsCounter.java b/src/main/java/com/cleanroommc/modularui/utils/FpsCounter.java new file mode 100644 index 00000000..6fe929a2 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/utils/FpsCounter.java @@ -0,0 +1,29 @@ +package com.cleanroommc.modularui.utils; + +import net.minecraft.client.Minecraft; + +public class FpsCounter { + + private int fps = 0, frameCount = 0; + private long timer = Minecraft.getSystemTime(); + + public void reset() { + this.fps = 0; + this.frameCount = 0; + this.timer = Minecraft.getSystemTime(); + } + + public void onDraw() { + frameCount++; + long time = Minecraft.getSystemTime(); + if (time - timer >= 1000) { + fps = frameCount; + frameCount = 0; + timer += 1000; + } + } + + public int getFps() { + return fps; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/utils/GuiUtils.java b/src/main/java/com/cleanroommc/modularui/utils/GuiUtils.java index f600bf91..5d1ccf0d 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/GuiUtils.java +++ b/src/main/java/com/cleanroommc/modularui/utils/GuiUtils.java @@ -2,7 +2,6 @@ import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; -import org.lwjgl.util.vector.Matrix4f; import java.nio.FloatBuffer; diff --git a/src/main/java/com/cleanroommc/modularui/utils/Matrix4f.java b/src/main/java/com/cleanroommc/modularui/utils/Matrix4f.java new file mode 100644 index 00000000..c30b42d3 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/utils/Matrix4f.java @@ -0,0 +1,846 @@ +package com.cleanroommc.modularui.utils; + +import org.lwjgl.util.vector.Vector4f; + +import java.nio.FloatBuffer; + +public class Matrix4f { + + public float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; + + /** + * Construct a new matrix, initialized to the identity. + */ + public Matrix4f() { + setIdentity(); + } + + public Matrix4f(final Matrix4f src) { + load(src); + } + + /** + * Returns a string representation of this matrix + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(m00).append(' ').append(m10).append(' ').append(m20).append(' ').append(m30).append('\n'); + buf.append(m01).append(' ').append(m11).append(' ').append(m21).append(' ').append(m31).append('\n'); + buf.append(m02).append(' ').append(m12).append(' ').append(m22).append(' ').append(m32).append('\n'); + buf.append(m03).append(' ').append(m13).append(' ').append(m23).append(' ').append(m33).append('\n'); + return buf.toString(); + } + + /** + * Set this matrix to be the identity matrix. + * + * @return this + */ + public Matrix4f setIdentity() { + return setIdentity(this); + } + + /** + * Set the given matrix to be the identity matrix. + * + * @param m The matrix to set to the identity + * @return m + */ + public static Matrix4f setIdentity(Matrix4f m) { + m.m00 = 1.0f; + m.m01 = 0.0f; + m.m02 = 0.0f; + m.m03 = 0.0f; + m.m10 = 0.0f; + m.m11 = 1.0f; + m.m12 = 0.0f; + m.m13 = 0.0f; + m.m20 = 0.0f; + m.m21 = 0.0f; + m.m22 = 1.0f; + m.m23 = 0.0f; + m.m30 = 0.0f; + m.m31 = 0.0f; + m.m32 = 0.0f; + m.m33 = 1.0f; + + return m; + } + + /** + * Set this matrix to 0. + * + * @return this + */ + public Matrix4f setZero() { + return setZero(this); + } + + /** + * Set the given matrix to 0. + * + * @param m The matrix to set to 0 + * @return m + */ + public static Matrix4f setZero(Matrix4f m) { + m.m00 = 0.0f; + m.m01 = 0.0f; + m.m02 = 0.0f; + m.m03 = 0.0f; + m.m10 = 0.0f; + m.m11 = 0.0f; + m.m12 = 0.0f; + m.m13 = 0.0f; + m.m20 = 0.0f; + m.m21 = 0.0f; + m.m22 = 0.0f; + m.m23 = 0.0f; + m.m30 = 0.0f; + m.m31 = 0.0f; + m.m32 = 0.0f; + m.m33 = 0.0f; + + return m; + } + + /** + * Load from another matrix4f + * + * @param src The source matrix + * @return this + */ + public Matrix4f load(Matrix4f src) { + return load(src, this); + } + + /** + * Copy the source matrix to the destination matrix + * + * @param src The source matrix + * @param dest The destination matrix, or null of a new one is to be created + * @return The copied matrix + */ + public static Matrix4f load(Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + dest.m00 = src.m00; + dest.m01 = src.m01; + dest.m02 = src.m02; + dest.m03 = src.m03; + dest.m10 = src.m10; + dest.m11 = src.m11; + dest.m12 = src.m12; + dest.m13 = src.m13; + dest.m20 = src.m20; + dest.m21 = src.m21; + dest.m22 = src.m22; + dest.m23 = src.m23; + dest.m30 = src.m30; + dest.m31 = src.m31; + dest.m32 = src.m32; + dest.m33 = src.m33; + + return dest; + } + + /** + * Load from a float buffer. The buffer stores the matrix in column major + * (OpenGL) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix4f load(FloatBuffer buf) { + + m00 = buf.get(); + m01 = buf.get(); + m02 = buf.get(); + m03 = buf.get(); + m10 = buf.get(); + m11 = buf.get(); + m12 = buf.get(); + m13 = buf.get(); + m20 = buf.get(); + m21 = buf.get(); + m22 = buf.get(); + m23 = buf.get(); + m30 = buf.get(); + m31 = buf.get(); + m32 = buf.get(); + m33 = buf.get(); + + return this; + } + + /** + * Load from a float buffer. The buffer stores the matrix in row major + * (maths) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix4f loadTranspose(FloatBuffer buf) { + + m00 = buf.get(); + m10 = buf.get(); + m20 = buf.get(); + m30 = buf.get(); + m01 = buf.get(); + m11 = buf.get(); + m21 = buf.get(); + m31 = buf.get(); + m02 = buf.get(); + m12 = buf.get(); + m22 = buf.get(); + m32 = buf.get(); + m03 = buf.get(); + m13 = buf.get(); + m23 = buf.get(); + m33 = buf.get(); + + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in column + * major (openGL) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix4f store(FloatBuffer buf) { + buf.put(m00); + buf.put(m01); + buf.put(m02); + buf.put(m03); + buf.put(m10); + buf.put(m11); + buf.put(m12); + buf.put(m13); + buf.put(m20); + buf.put(m21); + buf.put(m22); + buf.put(m23); + buf.put(m30); + buf.put(m31); + buf.put(m32); + buf.put(m33); + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in row + * major (maths) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix4f storeTranspose(FloatBuffer buf) { + buf.put(m00); + buf.put(m10); + buf.put(m20); + buf.put(m30); + buf.put(m01); + buf.put(m11); + buf.put(m21); + buf.put(m31); + buf.put(m02); + buf.put(m12); + buf.put(m22); + buf.put(m32); + buf.put(m03); + buf.put(m13); + buf.put(m23); + buf.put(m33); + return this; + } + + /** + * Store the rotation portion of this matrix in a float buffer. The matrix is stored in column + * major (openGL) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix4f store3f(FloatBuffer buf) { + buf.put(m00); + buf.put(m01); + buf.put(m02); + buf.put(m10); + buf.put(m11); + buf.put(m12); + buf.put(m20); + buf.put(m21); + buf.put(m22); + return this; + } + + /** + * Add two matrices together and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix4f add(Matrix4f left, Matrix4f right, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m00 = left.m00 + right.m00; + dest.m01 = left.m01 + right.m01; + dest.m02 = left.m02 + right.m02; + dest.m03 = left.m03 + right.m03; + dest.m10 = left.m10 + right.m10; + dest.m11 = left.m11 + right.m11; + dest.m12 = left.m12 + right.m12; + dest.m13 = left.m13 + right.m13; + dest.m20 = left.m20 + right.m20; + dest.m21 = left.m21 + right.m21; + dest.m22 = left.m22 + right.m22; + dest.m23 = left.m23 + right.m23; + dest.m30 = left.m30 + right.m30; + dest.m31 = left.m31 + right.m31; + dest.m32 = left.m32 + right.m32; + dest.m33 = left.m33 + right.m33; + + return dest; + } + + /** + * Subtract the right matrix from the left and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix4f sub(Matrix4f left, Matrix4f right, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m00 = left.m00 - right.m00; + dest.m01 = left.m01 - right.m01; + dest.m02 = left.m02 - right.m02; + dest.m03 = left.m03 - right.m03; + dest.m10 = left.m10 - right.m10; + dest.m11 = left.m11 - right.m11; + dest.m12 = left.m12 - right.m12; + dest.m13 = left.m13 - right.m13; + dest.m20 = left.m20 - right.m20; + dest.m21 = left.m21 - right.m21; + dest.m22 = left.m22 - right.m22; + dest.m23 = left.m23 - right.m23; + dest.m30 = left.m30 - right.m30; + dest.m31 = left.m31 - right.m31; + dest.m32 = left.m32 - right.m32; + dest.m33 = left.m33 - right.m33; + + return dest; + } + + /** + * Multiply the right matrix by the left and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix4f mul(Matrix4f left, Matrix4f right, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + float m00 = left.m00 * right.m00 + left.m10 * right.m01 + left.m20 * right.m02 + left.m30 * right.m03; + float m01 = left.m01 * right.m00 + left.m11 * right.m01 + left.m21 * right.m02 + left.m31 * right.m03; + float m02 = left.m02 * right.m00 + left.m12 * right.m01 + left.m22 * right.m02 + left.m32 * right.m03; + float m03 = left.m03 * right.m00 + left.m13 * right.m01 + left.m23 * right.m02 + left.m33 * right.m03; + float m10 = left.m00 * right.m10 + left.m10 * right.m11 + left.m20 * right.m12 + left.m30 * right.m13; + float m11 = left.m01 * right.m10 + left.m11 * right.m11 + left.m21 * right.m12 + left.m31 * right.m13; + float m12 = left.m02 * right.m10 + left.m12 * right.m11 + left.m22 * right.m12 + left.m32 * right.m13; + float m13 = left.m03 * right.m10 + left.m13 * right.m11 + left.m23 * right.m12 + left.m33 * right.m13; + float m20 = left.m00 * right.m20 + left.m10 * right.m21 + left.m20 * right.m22 + left.m30 * right.m23; + float m21 = left.m01 * right.m20 + left.m11 * right.m21 + left.m21 * right.m22 + left.m31 * right.m23; + float m22 = left.m02 * right.m20 + left.m12 * right.m21 + left.m22 * right.m22 + left.m32 * right.m23; + float m23 = left.m03 * right.m20 + left.m13 * right.m21 + left.m23 * right.m22 + left.m33 * right.m23; + float m30 = left.m00 * right.m30 + left.m10 * right.m31 + left.m20 * right.m32 + left.m30 * right.m33; + float m31 = left.m01 * right.m30 + left.m11 * right.m31 + left.m21 * right.m32 + left.m31 * right.m33; + float m32 = left.m02 * right.m30 + left.m12 * right.m31 + left.m22 * right.m32 + left.m32 * right.m33; + float m33 = left.m03 * right.m30 + left.m13 * right.m31 + left.m23 * right.m32 + left.m33 * right.m33; + + dest.m00 = m00; + dest.m01 = m01; + dest.m02 = m02; + dest.m03 = m03; + dest.m10 = m10; + dest.m11 = m11; + dest.m12 = m12; + dest.m13 = m13; + dest.m20 = m20; + dest.m21 = m21; + dest.m22 = m22; + dest.m23 = m23; + dest.m30 = m30; + dest.m31 = m31; + dest.m32 = m32; + dest.m33 = m33; + + return dest; + } + + /** + * Transform a Vector by a matrix and return the result in a destination + * vector. + * + * @param left The left matrix + * @param right The right vector + * @param dest The destination vector, or null if a new one is to be created + * @return the destination vector + */ + public static Vector4f transform(Matrix4f left, Vector4f right, Vector4f dest) { + if (dest == null) + dest = new Vector4f(); + + float x = left.m00 * right.x + left.m10 * right.y + left.m20 * right.z + left.m30 * right.w; + float y = left.m01 * right.x + left.m11 * right.y + left.m21 * right.z + left.m31 * right.w; + float z = left.m02 * right.x + left.m12 * right.y + left.m22 * right.z + left.m32 * right.w; + float w = left.m03 * right.x + left.m13 * right.y + left.m23 * right.z + left.m33 * right.w; + + dest.x = x; + dest.y = y; + dest.z = z; + dest.w = w; + + return dest; + } + + /** + * Transpose this matrix + * + * @return this + */ + public Matrix4f transpose() { + return transpose(this); + } + + /** + * Translate this matrix + * + * @param x x to translate by + * @param y y to translate by + * @return this + */ + public Matrix4f translate(float x, float y) { + return translate(x, y, this); + } + + /** + * Translate this matrix + * + * @param vec The vector to translate by + * @return this + */ + public Matrix4f translate(Vector3f vec) { + return translate(vec, this); + } + + /** + * Scales this matrix + * + * @param vec The vector to scale by + * @return this + */ + public Matrix4f scale(Vector3f vec) { + return scale(vec, this, this); + } + + /** + * Scales the source matrix and put the result in the destination matrix + * + * @param vec The vector to scale by + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return The scaled matrix + */ + public static Matrix4f scale(Vector3f vec, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + dest.m00 = src.m00 * vec.x; + dest.m01 = src.m01 * vec.x; + dest.m02 = src.m02 * vec.x; + dest.m03 = src.m03 * vec.x; + dest.m10 = src.m10 * vec.y; + dest.m11 = src.m11 * vec.y; + dest.m12 = src.m12 * vec.y; + dest.m13 = src.m13 * vec.y; + dest.m20 = src.m20 * vec.z; + dest.m21 = src.m21 * vec.z; + dest.m22 = src.m22 * vec.z; + dest.m23 = src.m23 * vec.z; + return dest; + } + + /** + * Rotates the matrix around the given axis the specified angle + * + * @param angle the angle, in radians. + * @param axis The vector representing the rotation axis. Must be normalized. + * @return this + */ + public Matrix4f rotate(float angle, Vector3f axis) { + return rotate(angle, axis, this); + } + + /** + * Rotates the matrix around the given axis the specified angle + * + * @param angle the angle, in radians. + * @param axis The vector representing the rotation axis. Must be normalized. + * @param dest The matrix to put the result, or null if a new matrix is to be created + * @return The rotated matrix + */ + public Matrix4f rotate(float angle, Vector3f axis, Matrix4f dest) { + return rotate(angle, axis, this, dest); + } + + /** + * Rotates the source matrix around the given axis the specified angle and + * put the result in the destination matrix. + * + * @param angle the angle, in radians. + * @param axis The vector representing the rotation axis. Must be normalized. + * @param src The matrix to rotate + * @param dest The matrix to put the result, or null if a new matrix is to be created + * @return The rotated matrix + */ + public static Matrix4f rotate(float angle, Vector3f axis, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + float c = (float) Math.cos(angle); + float s = (float) Math.sin(angle); + float oneminusc = 1.0f - c; + float xy = axis.x * axis.y; + float yz = axis.y * axis.z; + float xz = axis.x * axis.z; + float xs = axis.x * s; + float ys = axis.y * s; + float zs = axis.z * s; + + float f00 = axis.x * axis.x * oneminusc + c; + float f01 = xy * oneminusc + zs; + float f02 = xz * oneminusc - ys; + // n[3] not used + float f10 = xy * oneminusc - zs; + float f11 = axis.y * axis.y * oneminusc + c; + float f12 = yz * oneminusc + xs; + // n[7] not used + float f20 = xz * oneminusc + ys; + float f21 = yz * oneminusc - xs; + float f22 = axis.z * axis.z * oneminusc + c; + + float t00 = src.m00 * f00 + src.m10 * f01 + src.m20 * f02; + float t01 = src.m01 * f00 + src.m11 * f01 + src.m21 * f02; + float t02 = src.m02 * f00 + src.m12 * f01 + src.m22 * f02; + float t03 = src.m03 * f00 + src.m13 * f01 + src.m23 * f02; + float t10 = src.m00 * f10 + src.m10 * f11 + src.m20 * f12; + float t11 = src.m01 * f10 + src.m11 * f11 + src.m21 * f12; + float t12 = src.m02 * f10 + src.m12 * f11 + src.m22 * f12; + float t13 = src.m03 * f10 + src.m13 * f11 + src.m23 * f12; + dest.m20 = src.m00 * f20 + src.m10 * f21 + src.m20 * f22; + dest.m21 = src.m01 * f20 + src.m11 * f21 + src.m21 * f22; + dest.m22 = src.m02 * f20 + src.m12 * f21 + src.m22 * f22; + dest.m23 = src.m03 * f20 + src.m13 * f21 + src.m23 * f22; + dest.m00 = t00; + dest.m01 = t01; + dest.m02 = t02; + dest.m03 = t03; + dest.m10 = t10; + dest.m11 = t11; + dest.m12 = t12; + dest.m13 = t13; + return dest; + } + + /** + * Translate this matrix and stash the result in another matrix + * + * @param vec The vector to translate by + * @param dest The destination matrix or null if a new matrix is to be created + * @return the translated matrix + */ + public Matrix4f translate(Vector3f vec, Matrix4f dest) { + return translate(vec, this, dest); + } + + /** + * Translate the source matrix and stash the result in the destination matrix + * + * @param vec The vector to translate by + * @param src The source matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return The translated matrix + */ + public static Matrix4f translate(Vector3f vec, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m30 += src.m00 * vec.x + src.m10 * vec.y + src.m20 * vec.z; + dest.m31 += src.m01 * vec.x + src.m11 * vec.y + src.m21 * vec.z; + dest.m32 += src.m02 * vec.x + src.m12 * vec.y + src.m22 * vec.z; + dest.m33 += src.m03 * vec.x + src.m13 * vec.y + src.m23 * vec.z; + + return dest; + } + + /** + * Translate this matrix and stash the result in another matrix + * + * @param x x to translate by + * @param y y to translate by + * @param dest The destination matrix or null if a new matrix is to be created + * @return the translated matrix + */ + public Matrix4f translate(float x, float y, Matrix4f dest) { + return translate(x, y, this, dest); + } + + /** + * Translate the source matrix and stash the result in the destination matrix + * + * @param x x to translate by + * @param y y to translate by + * @param src The source matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return The translated matrix + */ + public static Matrix4f translate(float x, float y, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m30 += src.m00 * x + src.m10 * y; + dest.m31 += src.m01 * x + src.m11 * y; + dest.m32 += src.m02 * x + src.m12 * y; + dest.m33 += src.m03 * x + src.m13 * y; + + return dest; + } + + /** + * Transpose this matrix and place the result in another matrix + * + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public Matrix4f transpose(Matrix4f dest) { + return transpose(this, dest); + } + + /** + * Transpose the source matrix and place the result in the destination matrix + * + * @param src The source matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public static Matrix4f transpose(Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + float m00 = src.m00; + float m01 = src.m10; + float m02 = src.m20; + float m03 = src.m30; + float m10 = src.m01; + float m11 = src.m11; + float m12 = src.m21; + float m13 = src.m31; + float m20 = src.m02; + float m21 = src.m12; + float m22 = src.m22; + float m23 = src.m32; + float m30 = src.m03; + float m31 = src.m13; + float m32 = src.m23; + float m33 = src.m33; + + dest.m00 = m00; + dest.m01 = m01; + dest.m02 = m02; + dest.m03 = m03; + dest.m10 = m10; + dest.m11 = m11; + dest.m12 = m12; + dest.m13 = m13; + dest.m20 = m20; + dest.m21 = m21; + dest.m22 = m22; + dest.m23 = m23; + dest.m30 = m30; + dest.m31 = m31; + dest.m32 = m32; + dest.m33 = m33; + + return dest; + } + + /** + * @return the determinant of the matrix + */ + public float determinant() { + float f = + m00 + * ((m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32) + - m13 * m22 * m31 + - m11 * m23 * m32 + - m12 * m21 * m33); + f -= m01 + * ((m10 * m22 * m33 + m12 * m23 * m30 + m13 * m20 * m32) + - m13 * m22 * m30 + - m10 * m23 * m32 + - m12 * m20 * m33); + f += m02 + * ((m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31) + - m13 * m21 * m30 + - m10 * m23 * m31 + - m11 * m20 * m33); + f -= m03 + * ((m10 * m21 * m32 + m11 * m22 * m30 + m12 * m20 * m31) + - m12 * m21 * m30 + - m10 * m22 * m31 + - m11 * m20 * m32); + return f; + } + + /** + * Calculate the determinant of a 3x3 matrix + * + * @return result + */ + + private static float determinant3x3(float t00, float t01, float t02, + float t10, float t11, float t12, + float t20, float t21, float t22) { + return t00 * (t11 * t22 - t12 * t21) + + t01 * (t12 * t20 - t10 * t22) + + t02 * (t10 * t21 - t11 * t20); + } + + /** + * Invert this matrix + * + * @return this if successful, null otherwise + */ + public Matrix4f invert() { + return invert(this, this); + } + + /** + * Invert the source matrix and put the result in the destination + * + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return The inverted matrix if successful, null otherwise + */ + public static Matrix4f invert(Matrix4f src, Matrix4f dest) { + float determinant = src.determinant(); + + if (determinant != 0) { + /* + * m00 m01 m02 m03 + * m10 m11 m12 m13 + * m20 m21 m22 m23 + * m30 m31 m32 m33 + */ + if (dest == null) + dest = new Matrix4f(); + float determinant_inv = 1f / determinant; + + // first row + float t00 = determinant3x3(src.m11, src.m12, src.m13, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33); + float t01 = -determinant3x3(src.m10, src.m12, src.m13, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33); + float t02 = determinant3x3(src.m10, src.m11, src.m13, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33); + float t03 = -determinant3x3(src.m10, src.m11, src.m12, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32); + // second row + float t10 = -determinant3x3(src.m01, src.m02, src.m03, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33); + float t11 = determinant3x3(src.m00, src.m02, src.m03, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33); + float t12 = -determinant3x3(src.m00, src.m01, src.m03, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33); + float t13 = determinant3x3(src.m00, src.m01, src.m02, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32); + // third row + float t20 = determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m31, src.m32, src.m33); + float t21 = -determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m30, src.m32, src.m33); + float t22 = determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m30, src.m31, src.m33); + float t23 = -determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m30, src.m31, src.m32); + // fourth row + float t30 = -determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m21, src.m22, src.m23); + float t31 = determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m20, src.m22, src.m23); + float t32 = -determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m20, src.m21, src.m23); + float t33 = determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m20, src.m21, src.m22); + + // transpose and divide by the determinant + dest.m00 = t00 * determinant_inv; + dest.m11 = t11 * determinant_inv; + dest.m22 = t22 * determinant_inv; + dest.m33 = t33 * determinant_inv; + dest.m01 = t10 * determinant_inv; + dest.m10 = t01 * determinant_inv; + dest.m20 = t02 * determinant_inv; + dest.m02 = t20 * determinant_inv; + dest.m12 = t21 * determinant_inv; + dest.m21 = t12 * determinant_inv; + dest.m03 = t30 * determinant_inv; + dest.m30 = t03 * determinant_inv; + dest.m13 = t31 * determinant_inv; + dest.m31 = t13 * determinant_inv; + dest.m32 = t23 * determinant_inv; + dest.m23 = t32 * determinant_inv; + return dest; + } else + return null; + } + + /** + * Negate this matrix + * + * @return this + */ + public Matrix4f negate() { + return negate(this); + } + + /** + * Negate this matrix and place the result in a destination matrix. + * + * @param dest The destination matrix, or null if a new matrix is to be created + * @return the negated matrix + */ + public Matrix4f negate(Matrix4f dest) { + return negate(this, dest); + } + + /** + * Negate this matrix and place the result in a destination matrix. + * + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return The negated matrix + */ + public static Matrix4f negate(Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m00 = -src.m00; + dest.m01 = -src.m01; + dest.m02 = -src.m02; + dest.m03 = -src.m03; + dest.m10 = -src.m10; + dest.m11 = -src.m11; + dest.m12 = -src.m12; + dest.m13 = -src.m13; + dest.m20 = -src.m20; + dest.m21 = -src.m21; + dest.m22 = -src.m22; + dest.m23 = -src.m23; + dest.m30 = -src.m30; + dest.m31 = -src.m31; + dest.m32 = -src.m32; + dest.m33 = -src.m33; + + return dest; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/utils/Vector3f.java b/src/main/java/com/cleanroommc/modularui/utils/Vector3f.java new file mode 100644 index 00000000..1153caeb --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/utils/Vector3f.java @@ -0,0 +1,321 @@ +package com.cleanroommc.modularui.utils; + +import java.nio.FloatBuffer; + +public class Vector3f { + + public float x, y, z; + + /** + * Constructor for Vec3f. + */ + public Vector3f() { + super(); + } + + /** + * Constructor + */ + public Vector3f(Vector3f src) { + set(src); + } + + /** + * Constructor + */ + public Vector3f(float x, float y, float z) { + set(x, y, z); + } + + /* (non-Javadoc) + * @see org.lwjgl.util.vector.WritableVec2f#set(float, float) + */ + public void set(float x, float y) { + this.x = x; + this.y = y; + } + + /* (non-Javadoc) + * @see org.lwjgl.util.vector.WritableVec3f#set(float, float, float) + */ + public void set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Load from another Vec3f + * + * @param src The source vector + * @return this + */ + public Vector3f set(Vector3f src) { + x = src.getX(); + y = src.getY(); + z = src.getZ(); + return this; + } + + /** + * @return the length squared of the vector + */ + public float lengthSquared() { + return x * x + y * y + z * z; + } + + public float length() { + return (float) Math.sqrt(lengthSquared()); + } + + /** + * Translate a vector + * + * @param x The translation in x + * @param y the translation in y + * @return this + */ + public Vector3f translate(float x, float y, float z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + /** + * Add a vector to another vector and place the result in a destination + * vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return the sum of left and right in dest + */ + public static Vector3f add(Vector3f left, Vector3f right, Vector3f dest) { + if (dest == null) + return new Vector3f(left.x + right.x, left.y + right.y, left.z + right.z); + else { + dest.set(left.x + right.x, left.y + right.y, left.z + right.z); + return dest; + } + } + + /** + * Subtract a vector from another vector and place the result in a destination + * vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return left minus right in dest + */ + public static Vector3f sub(Vector3f left, Vector3f right, Vector3f dest) { + if (dest == null) + return new Vector3f(left.x - right.x, left.y - right.y, left.z - right.z); + else { + dest.set(left.x - right.x, left.y - right.y, left.z - right.z); + return dest; + } + } + + /** + * The cross product of two vectors. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination result, or null if a new vector is to be created + * @return left cross right + */ + public static Vector3f cross(Vector3f left, Vector3f right, Vector3f dest) { + if (dest == null) dest = new Vector3f(); + dest.set(left.y * right.z - left.z * right.y, + right.x * left.z - right.z * left.x, + left.x * right.y - left.y * right.x); + return dest; + } + + + /** + * Negate a vector + * + * @return this + */ + public Vector3f negate() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * Negate a vector and place the result in a destination vector. + * + * @param dest The destination vector or null if a new vector is to be created + * @return the negated vector + */ + public Vector3f negate(Vector3f dest) { + if (dest == null) + dest = new Vector3f(); + dest.x = -x; + dest.y = -y; + dest.z = -z; + return dest; + } + + + /** + * Normalise this vector and place the result in another vector. + * + * @param dest The destination vector, or null if a new vector is to be created + * @return the normalised vector + */ + public Vector3f normalise(Vector3f dest) { + float l = length(); + + if (dest == null) + dest = new Vector3f(x / l, y / l, z / l); + else + dest.set(x / l, y / l, z / l); + + return dest; + } + + /** + * The dot product of two vectors is calculated as + * v1.x * v2.x + v1.y * v2.y + v1.z * v2.z + * + * @param left The LHS vector + * @param right The RHS vector + * @return left dot right + */ + public static float dot(Vector3f left, Vector3f right) { + return left.x * right.x + left.y * right.y + left.z * right.z; + } + + /** + * Calculate the angle between two vectors, in radians + * + * @param a A vector + * @param b The other vector + * @return the angle between the two vectors, in radians + */ + public static float angle(Vector3f a, Vector3f b) { + float dls = dot(a, b) / (a.length() * b.length()); + if (dls < -1f) + dls = -1f; + else if (dls > 1.0f) + dls = 1.0f; + return (float) Math.acos(dls); + } + + /* (non-Javadoc) + * @see org.lwjgl.vector.Vec#load(FloatBuffer) + */ + public Vector3f load(FloatBuffer buf) { + x = buf.get(); + y = buf.get(); + z = buf.get(); + return this; + } + + /* (non-Javadoc) + * @see org.lwjgl.vector.Vec#scale(float) + */ + public Vector3f scale(float scale) { + + x *= scale; + y *= scale; + z *= scale; + + return this; + + } + + /* (non-Javadoc) + * @see org.lwjgl.vector.Vec#store(FloatBuffer) + */ + public Vector3f store(FloatBuffer buf) { + + buf.put(x); + buf.put(y); + buf.put(z); + + return this; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuilder sb = new StringBuilder(64); + + sb.append("Vec3f["); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(", "); + sb.append(z); + sb.append(']'); + return sb.toString(); + } + + /** + * @return x + */ + public final float getX() { + return x; + } + + /** + * @return y + */ + public final float getY() { + return y; + } + + /** + * Set X + * + * @param x + */ + public final void setX(float x) { + this.x = x; + } + + /** + * Set Y + * + * @param y + */ + public final void setY(float y) { + this.y = y; + } + + /** + * Set Z + * + * @param z + */ + public void setZ(float z) { + this.z = z; + } + + /* (Overrides) + * @see org.lwjgl.vector.ReadableVec3f#getZ() + */ + public float getZ() { + return z; + } + + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Vector3f other = (Vector3f) obj; + + if (x == other.x && y == other.y && z == other.z) return true; + + return false; + } + +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java b/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java index 329e5796..ab3c70a4 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java @@ -1,8 +1,8 @@ package com.cleanroommc.modularui.widget; +import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; - import com.cleanroommc.modularui.theme.WidgetTheme; import org.jetbrains.annotations.NotNull; @@ -24,16 +24,13 @@ public List getChildren() { @Override public boolean canHover() { - return getBackground() != null || - getHoverBackground() != null || - getHoverOverlay() != null || - getTooltip() != null || - hasThemeBackground(); - } - - protected boolean hasThemeBackground() { + if (IDrawable.isVisible(getBackground()) || + IDrawable.isVisible(getHoverBackground()) || + IDrawable.isVisible(getHoverOverlay()) || + getTooltip() != null) return true; WidgetTheme widgetTheme = getWidgetTheme(getContext().getTheme()); - return widgetTheme.getBackground() != null || widgetTheme.getHoverBackground() != null; + if (getBackground() == null && IDrawable.isVisible(widgetTheme.getBackground())) return true; + return getHoverBackground() == null && IDrawable.isVisible(widgetTheme.getHoverBackground()); } public boolean addChild(IWidget child, int index) { diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 3392c6b5..a29ab37c 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -73,7 +73,7 @@ public void initialise(@NotNull IWidget parent) { getArea().z(parent.getArea().z() + 1); if (this.guiActionListeners != null) { for (IGuiAction action : this.guiActionListeners) { - this.context.screen.registerGuiActionListener(action); + this.context.getScreen().registerGuiActionListener(action); } } } @@ -120,7 +120,7 @@ public void dispose() { if (isValid()) { if (this.guiActionListeners != null) { for (IGuiAction action : this.guiActionListeners) { - this.context.screen.removeGuiActionListener(action); + this.context.getScreen().removeGuiActionListener(action); } } @@ -297,7 +297,7 @@ public W listenGuiAction(IGuiAction action) { } this.guiActionListeners.add(action); if (isValid()) { - this.context.screen.registerGuiActionListener(action); + this.context.getScreen().registerGuiActionListener(action); } return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java index e9877661..b8a3ba46 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java @@ -1,14 +1,16 @@ package com.cleanroommc.modularui.widgets; +import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.core.mixin.GuiAccessor; import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; +import com.cleanroommc.modularui.core.mixin.GuiScreenAccessor; import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.drawable.TextRenderer; import com.cleanroommc.modularui.integration.jei.JeiGhostIngredientSlot; import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; -import com.cleanroommc.modularui.screen.GuiScreenWrapper; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.Tooltip; import com.cleanroommc.modularui.screen.viewport.GuiContext; @@ -23,15 +25,17 @@ import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.RenderItem; import net.minecraft.inventory.Container; import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; - import net.minecraftforge.items.IItemHandlerModifiable; import org.jetbrains.annotations.NotNull; @@ -58,6 +62,9 @@ public ItemSlot() { @Override public void onInit() { + if (getScreen().isOverlay()) { + throw new IllegalStateException("Overlays can't have slots!"); + } size(SIZE, SIZE); getContext().getJeiSettings().addJeiGhostIngredientSlot(this); } @@ -110,7 +117,8 @@ public WidgetSlotTheme getWidgetTheme(ITheme theme) { MouseData mouseData = MouseData.create(mouseButton); this.syncHandler.syncToServer(2, mouseData::writeToPacket); } else { - getScreen().getScreenWrapper().clickSlot(); + ClientScreenHandler.clickSlot(); + //getScreen().getScreenWrapper().clickSlot(); } return Result.SUCCESS; } @@ -118,7 +126,8 @@ public WidgetSlotTheme getWidgetTheme(ITheme theme) { @Override public boolean onMouseRelease(int mouseButton) { if (!this.syncHandler.isPhantom()) { - getScreen().getScreenWrapper().releaseSlot(); + ClientScreenHandler.releaseSlot(); + //getScreen().getScreenWrapper().releaseSlot(); } return true; } @@ -135,7 +144,8 @@ public boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) @Override public void onMouseDrag(int mouseButton, long timeSinceClick) { - getScreen().getScreenWrapper().dragSlot(timeSinceClick); + //getScreen().getScreenWrapper().dragSlot(timeSinceClick); + ClientScreenHandler.dragSlot(timeSinceClick); } public ModularSlot getSlot() { @@ -157,7 +167,7 @@ public Slot getVanillaSlot() { protected List getItemTooltip(ItemStack stack) { // todo: JEI seems to be getting tooltip from IngredientRenderer#getTooltip - return getScreen().getScreenWrapper().getItemToolTip(stack); + return getScreen().getScreenWrapper().getGuiScreen().getItemToolTip(stack); } @@ -173,27 +183,30 @@ public ItemSlot slot(IItemHandlerModifiable itemHandler, int index) { @SideOnly(Side.CLIENT) private void drawSlot(Slot slotIn) { - GuiScreenWrapper guiScreen = getScreen().getScreenWrapper(); - GuiContainerAccessor accessor = guiScreen.getAccessor(); + GuiScreen guiScreen = getScreen().getScreenWrapper().getGuiScreen(); + if (!(guiScreen instanceof GuiContainer)) + throw new IllegalStateException("The gui must be an instance of GuiContainer if it contains slots!"); + GuiContainerAccessor acc = (GuiContainerAccessor) guiScreen; + RenderItem renderItem = ((GuiScreenAccessor) guiScreen).getItemRender(); ItemStack itemstack = slotIn.getStack(); boolean flag = false; - boolean flag1 = slotIn == accessor.getClickedSlot() && !accessor.getDraggedStack().isEmpty() && !accessor.getIsRightMouseClick(); + boolean flag1 = slotIn == acc.getClickedSlot() && !acc.getDraggedStack().isEmpty() && !acc.getIsRightMouseClick(); ItemStack itemstack1 = guiScreen.mc.player.inventory.getItemStack(); int amount = -1; String format = null; - if (slotIn == accessor.getClickedSlot() && !accessor.getDraggedStack().isEmpty() && accessor.getIsRightMouseClick() && !itemstack.isEmpty()) { + if (slotIn == acc.getClickedSlot() && !acc.getDraggedStack().isEmpty() && acc.getIsRightMouseClick() && !itemstack.isEmpty()) { itemstack = itemstack.copy(); itemstack.setCount(itemstack.getCount() / 2); - } else if (guiScreen.isDragSplitting() && guiScreen.getDragSlots().contains(slotIn) && !itemstack1.isEmpty()) { - if (guiScreen.getDragSlots().size() == 1) { + } else if (acc.getDragSplitting() && acc.getDragSplittingSlots().contains(slotIn) && !itemstack1.isEmpty()) { + if (acc.getDragSplittingSlots().size() == 1) { return; } - if (Container.canAddItemToSlot(slotIn, itemstack1, true) && guiScreen.inventorySlots.canDragIntoSlot(slotIn)) { + if (Container.canAddItemToSlot(slotIn, itemstack1, true) && getScreen().getContainer().canDragIntoSlot(slotIn)) { itemstack = itemstack1.copy(); flag = true; - Container.computeStackSize(guiScreen.getDragSlots(), accessor.getDragSplittingLimit(), itemstack, slotIn.getStack().isEmpty() ? 0 : slotIn.getStack().getCount()); + Container.computeStackSize(acc.getDragSplittingSlots(), acc.getDragSplittingLimit(), itemstack, slotIn.getStack().isEmpty() ? 0 : slotIn.getStack().getCount()); int k = Math.min(itemstack.getMaxStackSize(), slotIn.getItemStackLimit(itemstack)); if (itemstack.getCount() > k) { @@ -202,13 +215,13 @@ private void drawSlot(Slot slotIn) { itemstack.setCount(k); } } else { - guiScreen.getDragSlots().remove(slotIn); - accessor.invokeUpdateDragSplitting(); + acc.getDragSplittingSlots().remove(slotIn); + acc.invokeUpdateDragSplitting(); } } - guiScreen.setZ(100f); - guiScreen.getItemRenderer().zLevel = 100.0F; + ((GuiAccessor) guiScreen).setZLevel(100f); + renderItem.zLevel = 100.0F; if (!flag1) { if (flag) { @@ -218,7 +231,7 @@ private void drawSlot(Slot slotIn) { if (!itemstack.isEmpty()) { GlStateManager.enableDepth(); // render the item itself - guiScreen.getItemRenderer().renderItemAndEffectIntoGUI(guiScreen.mc.player, itemstack, 1, 1); + renderItem.renderItemAndEffectIntoGUI(guiScreen.mc.player, itemstack, 1, 1); if (amount < 0) { amount = itemstack.getCount(); } @@ -253,14 +266,14 @@ private void drawSlot(Slot slotIn) { int cachedCount = itemstack.getCount(); itemstack.setCount(1); // required to not render the amount overlay // render other overlays like durability bar - guiScreen.getItemRenderer().renderItemOverlayIntoGUI(guiScreen.getFontRenderer(), itemstack, 1, 1, null); + renderItem.renderItemOverlayIntoGUI(((GuiScreenAccessor) guiScreen).getFontRenderer(), itemstack, 1, 1, null); itemstack.setCount(cachedCount); GlStateManager.disableDepth(); } } - guiScreen.getItemRenderer().zLevel = 0.0F; - guiScreen.setZ(0f); + ((GuiAccessor) guiScreen).setZLevel(0f); + renderItem.zLevel = 0f; } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java index 58772471..4cb9e821 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java @@ -29,7 +29,7 @@ public TextWidget(IKey key) { public void draw(GuiContext context, WidgetTheme widgetTheme) { TextRenderer renderer = TextRenderer.SHARED; renderer.setColor(this.colorChanged ? this.color : widgetTheme.getTextColor()); - renderer.setAlignment(this.alignment, getArea().w() + 1, getArea().h()); + renderer.setAlignment(this.alignment, getArea().w() + this.scale, getArea().h()); renderer.setShadow(this.shadowChanged ? this.shadow : widgetTheme.getTextShadow()); renderer.setPos(getArea().getPadding().left, getArea().getPadding().top); renderer.setScale(this.scale); @@ -55,11 +55,13 @@ private TextRenderer simulate(float maxWidth) { @Override public int getDefaultHeight() { - float maxWidth = getScreen().getScreenArea().width; + float maxWidth; if (resizer() != null && resizer().isWidthCalculated()) { - maxWidth = getArea().width + 1; + maxWidth = getArea().width + this.scale; } else if (getParent().resizer() != null && getParent().resizer().isWidthCalculated()) { - maxWidth = getParent().getArea().width + 1; + maxWidth = getParent().getArea().width + this.scale; + } else { + maxWidth = getScreen().getScreenArea().width; } TextRenderer renderer = simulate(maxWidth); Box padding = getArea().getPadding(); diff --git a/src/main/resources/mixin.modularui.json b/src/main/resources/mixin.modularui.json index 8c86b770..22bab54c 100644 --- a/src/main/resources/mixin.modularui.json +++ b/src/main/resources/mixin.modularui.json @@ -5,8 +5,11 @@ "minVersion": "0.8", "compatibilityLevel": "JAVA_8", "client": [ + "GuiAccessor", + "GuiButtonMixin", "GuiContainerAccessor", "GuiContainerMixin", + "GuiScreenAccessor", "KeyBindAccess", "MinecraftMixin" ], diff --git a/src/test/java/com/cleanroommc/modularui/Bootstrap.java b/src/test/java/com/cleanroommc/modularui/Bootstrap.java new file mode 100644 index 00000000..06303033 --- /dev/null +++ b/src/test/java/com/cleanroommc/modularui/Bootstrap.java @@ -0,0 +1,175 @@ +package com.cleanroommc.modularui; + +import net.minecraft.client.resources.I18n; +import net.minecraft.client.resources.Locale; +import net.minecraft.network.INetHandler; +import net.minecraft.network.NetworkManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.IThreadListener; +import net.minecraftforge.common.util.CompoundDataFixer; +import net.minecraftforge.fml.common.*; +import net.minecraftforge.fml.common.eventhandler.EventBus; +import net.minecraftforge.fml.relauncher.CoreModManager; +import net.minecraftforge.fml.relauncher.Side; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class Bootstrap { + + private static boolean bootstrapped = false; + + private Bootstrap() {} + + public static void perform() { + if (bootstrapped) { + return; + } + try { + Field deobfuscatedEnvironment = CoreModManager.class.getDeclaredField("deobfuscatedEnvironment"); + deobfuscatedEnvironment.setAccessible(true); + deobfuscatedEnvironment.setBoolean(null, true); + + Method setLocale = I18n.class.getDeclaredMethod("setLocale", Locale.class); // No need to care about + // obfuscation + setLocale.setAccessible(true); + setLocale.invoke(null, new Locale()); + + // set FMLCommonHandler#sidedDelegate, since MaterialIconType and LocalizationUtils uses it + Field sidedDelegate = FMLCommonHandler.class.getDeclaredField("sidedDelegate"); + sidedDelegate.setAccessible(true); + sidedDelegate.set(FMLCommonHandler.instance(), new TestSidedHandler()); + } catch (NoSuchFieldException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException("Unexpected exception on test bootstrap", e); + } + net.minecraft.init.Bootstrap.register(); + ModMetadata meta = new ModMetadata(); + meta.modId = MuiTags.MODID; + Loader.instance().setupTestHarness(new DummyModContainer(meta)); + + bootstrapped = true; + } + + private static final class TestSidedHandler implements IFMLSidedHandler { + + @Override + public List getAdditionalBrandingInformation() { + return Collections.emptyList(); + } + + @Override + public Side getSide() { + return Side.SERVER; + } + + @Override + public void haltGame(String message, Throwable exception) { + throw new RuntimeException(message, exception); + } + + @Override + public void showGuiScreen(Object clientGuiElement) {} + + @Override + public void queryUser(StartupQuery query) { + throw new UnsupportedOperationException(); + } + + @Override + public void beginServerLoading(MinecraftServer server) {} + + @Override + public void finishServerLoading() {} + + @Override + public File getSavesDirectory() { + throw new UnsupportedOperationException(); + } + + @Override + public MinecraftServer getServer() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDisplayCloseRequested() { + return false; + } + + @Override + public boolean shouldServerShouldBeKilledQuietly() { + return false; + } + + @Override + public void addModAsResource(ModContainer container) { + throw new UnsupportedOperationException(); + } + + @Override + public String getCurrentLanguage() { + return "en_US"; + } + + @Override + public void serverStopped() {} + + @Override + public NetworkManager getClientToServerNetworkManager() { + throw new UnsupportedOperationException(); + } + + @Override + public INetHandler getClientPlayHandler() { + return null; + } + + @Override + public void fireNetRegistrationEvent(EventBus bus, NetworkManager manager, Set channelSet, + String channel, Side side) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean shouldAllowPlayerLogins() { + return false; + } + + @Override + public void allowLogins() {} + + @Override + public IThreadListener getWorldThread(INetHandler net) { + throw new UnsupportedOperationException(); + } + + @Override + public void processWindowMessages() {} + + @Override + public String stripSpecialChars(String message) { + return message; + } + + @Override + public void reloadRenderers() {} + + @Override + public void fireSidedRegistryEvents() {} + + @Override + public CompoundDataFixer getDataFixer() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDisplayVSyncForced() { + return false; + } + } +} diff --git a/src/test/java/com/cleanroommc/modularui/SizerTest.java b/src/test/java/com/cleanroommc/modularui/SizerTest.java new file mode 100644 index 00000000..fac47709 --- /dev/null +++ b/src/test/java/com/cleanroommc/modularui/SizerTest.java @@ -0,0 +1,46 @@ +package com.cleanroommc.modularui; + +import com.cleanroommc.modularui.overlay.ScreenWrapper; +import com.cleanroommc.modularui.screen.JeiSettingsImpl; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widgets.ButtonWidget; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SizerTest { + + static final int W = 800, H = 450; + + @Test + void test() { + ModularPanel panel = panel().child(new ButtonWidget<>().center()); + testPanel(panel); + assertArea(panel.getArea(), W / 2 - 176 / 2, H / 2 - 166 / 2, 176, 166); + } + + ModularPanel panel(int w, int h) { + return ModularPanel.defaultPanel("main", w, h); + } + + ModularPanel panel() { + return ModularPanel.defaultPanel("main"); + } + + void assertArea(Area area, int x, int y, int w, int h) { + Area.SHARED.set(x, y, w, h); + assertEquals(Area.SHARED, area); + } + + ModularScreen testPanel(ModularPanel panel) { + ModularScreen screen = new ModularScreen(panel); + screen.getContext().setJeiSettings(new JeiSettingsImpl()); + ScreenWrapper wrapper = new ScreenWrapper(null, screen); + screen.construct(wrapper); + screen.onResize(W, H); + return screen; + } +}