diff --git a/src/main/java/nl/requios/effortlessbuilding/create/AllKeys.java b/src/main/java/nl/requios/effortlessbuilding/create/AllKeys.java new file mode 100644 index 0000000..80c6b91 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/AllKeys.java @@ -0,0 +1,39 @@ +package nl.requios.effortlessbuilding.create; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterKeyMappingsEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import org.lwjgl.glfw.GLFW; + +public class AllKeys { + + public static boolean isKeyDown(int key) { + return InputConstants.isKeyDown(Minecraft.getInstance() + .getWindow() + .getWindow(), key); + } + + public static boolean isMouseButtonDown(int button) { + return GLFW.glfwGetMouseButton(Minecraft.getInstance() + .getWindow() + .getWindow(), button) == 1; + } + + public static boolean ctrlDown() { + return Screen.hasControlDown(); + } + + public static boolean shiftDown() { + return Screen.hasShiftDown(); + } + + public static boolean altDown() { + return Screen.hasAltDown(); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AbstractSimiScreen.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AbstractSimiScreen.java new file mode 100644 index 0000000..cc71dfb --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AbstractSimiScreen.java @@ -0,0 +1,168 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.widget.AbstractSimiWidget; +import nl.requios.effortlessbuilding.create.foundation.utility.Components; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.Collection; +import java.util.List; + +@OnlyIn(Dist.CLIENT) +public abstract class AbstractSimiScreen extends Screen { + + protected int windowWidth, windowHeight; + protected int windowXOffset, windowYOffset; + protected int guiLeft, guiTop; + + protected AbstractSimiScreen(Component title) { + super(title); + } + + protected AbstractSimiScreen() { + this(Components.immutableEmpty()); + } + + /** + * This method must be called before {@code super.init()}! + */ + protected void setWindowSize(int width, int height) { + windowWidth = width; + windowHeight = height; + } + + /** + * This method must be called before {@code super.init()}! + */ + protected void setWindowOffset(int xOffset, int yOffset) { + windowXOffset = xOffset; + windowYOffset = yOffset; + } + + @Override + protected void init() { + guiLeft = (width - windowWidth) / 2; + guiTop = (height - windowHeight) / 2; + guiLeft += windowXOffset; + guiTop += windowYOffset; + } + + @Override + public void tick() { + for (GuiEventListener listener : children()) { + if (listener instanceof TickableGuiEventListener tickable) { + tickable.tick(); + } + } + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @SuppressWarnings("unchecked") + protected void addRenderableWidgets(W... widgets) { + for (W widget : widgets) { + addRenderableWidget(widget); + } + } + + protected void addRenderableWidgets(Collection widgets) { + for (W widget : widgets) { + addRenderableWidget(widget); + } + } + + protected void removeWidgets(GuiEventListener... widgets) { + for (GuiEventListener widget : widgets) { + removeWidget(widget); + } + } + + protected void removeWidgets(Collection widgets) { + for (GuiEventListener widget : widgets) { + removeWidget(widget); + } + } + + @Override + public void render(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + partialTicks = minecraft.getFrameTime(); + + ms.pushPose(); + + prepareFrame(); + + renderWindowBackground(ms, mouseX, mouseY, partialTicks); + renderWindow(ms, mouseX, mouseY, partialTicks); + super.render(ms, mouseX, mouseY, partialTicks); + renderWindowForeground(ms, mouseX, mouseY, partialTicks); + + endFrame(); + + ms.popPose(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + boolean keyPressed = super.keyPressed(keyCode, scanCode, modifiers); + if (keyPressed || getFocused() != null) + return keyPressed; + + InputConstants.Key mouseKey = InputConstants.getKey(keyCode, scanCode); + if (this.minecraft.options.keyInventory.isActiveAndMatches(mouseKey)) { + this.onClose(); + return true; + } + + return false; + } + + protected void prepareFrame() {} + + protected void renderWindowBackground(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + renderBackground(ms); + } + + protected abstract void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks); + + protected void renderWindowForeground(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + for (Widget widget : renderables) { + if (widget instanceof AbstractSimiWidget simiWidget && simiWidget.isHoveredOrFocused() + && simiWidget.visible) { + List tooltip = simiWidget.getToolTip(); + if (tooltip.isEmpty()) + continue; + int ttx = simiWidget.lockedTooltipX == -1 ? mouseX : simiWidget.lockedTooltipX + simiWidget.x; + int tty = simiWidget.lockedTooltipY == -1 ? mouseY : simiWidget.lockedTooltipY + simiWidget.y; + renderComponentTooltip(ms, tooltip, ttx, tty); + } + } + } + + protected void endFrame() {} + + @Deprecated + protected void debugWindowArea(PoseStack matrixStack) { + fill(matrixStack, guiLeft + windowWidth, guiTop + windowHeight, guiLeft, guiTop, 0xD3D3D3D3); + } + + @Override + public GuiEventListener getFocused() { + GuiEventListener focused = super.getFocused(); + if (focused instanceof AbstractWidget && !((AbstractWidget) focused).isFocused()) + focused = null; + setFocused(focused); + return focused; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AllGuiTextures.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AllGuiTextures.java new file mode 100644 index 0000000..85d6b01 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AllGuiTextures.java @@ -0,0 +1,95 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.Create; +import nl.requios.effortlessbuilding.create.foundation.gui.element.ScreenElement; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public enum AllGuiTextures implements ScreenElement { + + // Widgets + BUTTON("widgets", 18, 18), + BUTTON_HOVER("widgets", 18, 0, 18, 18), + BUTTON_DOWN("widgets", 36, 0, 18, 18), + INDICATOR("widgets", 0, 18, 18, 6), + INDICATOR_WHITE("widgets", 18, 18, 18, 6), + INDICATOR_GREEN("widgets", 36, 18, 18, 6), + INDICATOR_YELLOW("widgets", 54, 18, 18, 6), + INDICATOR_RED("widgets", 72, 18, 18, 6), + + HOTSLOT_ARROW("widgets", 24, 51, 20, 12), + HOTSLOT("widgets", 0, 68, 22, 22), + HOTSLOT_ACTIVE("widgets", 0, 46, 22, 22), + HOTSLOT_SUPER_ACTIVE("widgets", 27, 67, 24, 24), + + SPEECH_TOOLTIP_BACKGROUND("widgets", 0, 24, 8, 8), + SPEECH_TOOLTIP_COLOR("widgets", 8, 24, 8, 8), + + TRAIN_HUD_SPEED_BG("widgets", 0, 190, 182, 5), + TRAIN_HUD_SPEED("widgets", 0, 185, 182, 5), + TRAIN_HUD_THROTTLE("widgets", 0, 195, 182, 5), + TRAIN_HUD_THROTTLE_POINTER("widgets", 0, 209, 6, 9), + TRAIN_HUD_FRAME("widgets", 0, 200, 186, 7), + TRAIN_HUD_DIRECTION("widgets", 77, 165, 28, 20), + TRAIN_PROMPT_L("widgets", 8, 209, 3, 16), + TRAIN_PROMPT_R("widgets", 11, 209, 3, 16), + TRAIN_PROMPT("widgets", 0, 230, 256, 16), + + ; + + public static final int FONT_COLOR = 0x575F7A; + + public final ResourceLocation location; + public int width, height; + public int startX, startY; + + private AllGuiTextures(String location, int width, int height) { + this(location, 0, 0, width, height); + } + + private AllGuiTextures(int startX, int startY) { + this("icons", startX * 16, startY * 16, 16, 16); + } + + private AllGuiTextures(String location, int startX, int startY, int width, int height) { + this(Create.ID, location, startX, startY, width, height); + } + + private AllGuiTextures(String namespace, String location, int startX, int startY, int width, int height) { + this.location = new ResourceLocation(namespace, "textures/gui/" + location + ".png"); + this.width = width; + this.height = height; + this.startX = startX; + this.startY = startY; + } + + @OnlyIn(Dist.CLIENT) + public void bind() { + RenderSystem.setShaderTexture(0, location); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void render(PoseStack ms, int x, int y) { + bind(); + GuiComponent.blit(ms, x, y, 0, startX, startY, width, height, 256, 256); + } + + @OnlyIn(Dist.CLIENT) + public void render(PoseStack ms, int x, int y, GuiComponent component) { + bind(); + component.blit(ms, x, y, startX, startY, width, height); + } + + @OnlyIn(Dist.CLIENT) + public void render(PoseStack ms, int x, int y, Color c) { + bind(); + UIRenderHelper.drawColoredTexture(ms, c, x, y, startX, startY, width, height); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AllIcons.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AllIcons.java new file mode 100644 index 0000000..f97f8fa --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/AllIcons.java @@ -0,0 +1,224 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Matrix4f; +import nl.requios.effortlessbuilding.create.Create; +import nl.requios.effortlessbuilding.create.foundation.gui.element.DelegatedStencilElement; +import nl.requios.effortlessbuilding.create.foundation.gui.element.ScreenElement; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public class AllIcons implements ScreenElement { + + public static final ResourceLocation ICON_ATLAS = Create.asResource("textures/gui/icons.png"); + public static final int ICON_ATLAS_SIZE = 256; + + private static int x = 0, y = -1; + private int iconX; + private int iconY; + + public static final AllIcons + I_ADD = newRow(), + I_TRASH = next(), + I_3x3 = next(), + I_TARGET = next(), + I_PRIORITY_VERY_LOW = next(), + I_PRIORITY_LOW = next(), + I_PRIORITY_HIGH = next(), + I_PRIORITY_VERY_HIGH = next(), + I_BLACKLIST = next(), + I_WHITELIST = next(), + I_WHITELIST_OR = next(), + I_WHITELIST_AND = next(), + I_WHITELIST_NOT = next(), + I_RESPECT_NBT = next(), + I_IGNORE_NBT = next(); + + public static final AllIcons + I_CONFIRM = newRow(), + I_NONE = next(), + I_OPEN_FOLDER = next(), + I_REFRESH = next(), + I_ACTIVE = next(), + I_PASSIVE = next(), + I_ROTATE_PLACE = next(), + I_ROTATE_PLACE_RETURNED = next(), + I_ROTATE_NEVER_PLACE = next(), + I_MOVE_PLACE = next(), + I_MOVE_PLACE_RETURNED = next(), + I_MOVE_NEVER_PLACE = next(), + I_CART_ROTATE = next(), + I_CART_ROTATE_PAUSED = next(), + I_CART_ROTATE_LOCKED = next(); + + public static final AllIcons + I_DONT_REPLACE = newRow(), + I_REPLACE_SOLID = next(), + I_REPLACE_ANY = next(), + I_REPLACE_EMPTY = next(), + I_CENTERED = next(), + I_ATTACHED = next(), + I_INSERTED = next(), + I_FILL = next(), + I_PLACE = next(), + I_REPLACE = next(), + I_CLEAR = next(), + I_OVERLAY = next(), + I_FLATTEN = next(), + I_LMB = next(), + I_SCROLL = next(), + I_RMB = next(); + + public static final AllIcons + I_TOOL_DEPLOY = newRow(), + I_SKIP_MISSING = next(), + I_SKIP_TILES = next(), + I_DICE = next(), + I_TUNNEL_SPLIT = next(), + I_TUNNEL_FORCED_SPLIT = next(), + I_TUNNEL_ROUND_ROBIN = next(), + I_TUNNEL_FORCED_ROUND_ROBIN = next(), + I_TUNNEL_PREFER_NEAREST = next(), + I_TUNNEL_RANDOMIZE = next(), + I_TUNNEL_SYNCHRONIZE = next(), + I_TOOLBOX = next(), + I_VIEW_SCHEDULE = next(), + + I_TOOL_MOVE_XZ = newRow(), + I_TOOL_MOVE_Y = next(), + I_TOOL_ROTATE = next(), + I_TOOL_MIRROR = next(), + I_ARM_ROUND_ROBIN = next(), + I_ARM_FORCED_ROUND_ROBIN = next(), + I_ARM_PREFER_FIRST = next(), + + I_ADD_INVERTED_ATTRIBUTE = next(), + I_FLIP = next(), + + I_PLAY = newRow(), + I_PAUSE = next(), + I_STOP = next(), + I_PLACEMENT_SETTINGS = next(), + I_ROTATE_CCW = next(), + I_HOUR_HAND_FIRST = next(), + I_MINUTE_HAND_FIRST = next(), + I_HOUR_HAND_FIRST_24 = next(), + + I_PATTERN_SOLID = newRow(), + I_PATTERN_CHECKERED = next(), + I_PATTERN_CHECKERED_INVERSED = next(), + I_PATTERN_CHANCE_25 = next(), + + I_PATTERN_CHANCE_50 = newRow(), + I_PATTERN_CHANCE_75 = next(), + I_FOLLOW_DIAGONAL = next(), + I_FOLLOW_MATERIAL = next(), + + I_SCHEMATIC = newRow(), + I_SEQ_REPEAT = next(), + + I_MTD_LEFT = newRow(), + I_MTD_CLOSE = next(), + I_MTD_RIGHT = next(), + I_MTD_SCAN = next(), + I_MTD_REPLAY = next(), + I_MTD_USER_MODE = next(), + I_MTD_SLOW_MODE = next(), + + I_CONFIG_UNLOCKED = newRow(), + I_CONFIG_LOCKED = next(), + I_CONFIG_DISCARD = next(), + I_CONFIG_SAVE = next(), + I_CONFIG_RESET = next(), + I_CONFIG_BACK = next(), + I_CONFIG_PREV = next(), + I_CONFIG_NEXT = next(), + I_DISABLE = next(), + I_CONFIG_OPEN = next(), + + I_FX_SURFACE_OFF = newRow(), + I_FX_SURFACE_ON = next(), + I_FX_FIELD_OFF = next(), + I_FX_FIELD_ON = next(), + I_FX_BLEND = next(), + I_FX_BLEND_OFF = next(); + ; + + public AllIcons(int x, int y) { + iconX = x * 16; + iconY = y * 16; + } + + private static AllIcons next() { + return new AllIcons(++x, y); + } + + private static AllIcons newRow() { + return new AllIcons(x = 0, ++y); + } + + @OnlyIn(Dist.CLIENT) + public void bind() { + RenderSystem.setShaderTexture(0, ICON_ATLAS); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void render(PoseStack matrixStack, int x, int y) { + bind(); + GuiComponent.blit(matrixStack, x, y, 0, iconX, iconY, 16, 16, 256, 256); + } + + @OnlyIn(Dist.CLIENT) + public void render(PoseStack matrixStack, int x, int y, GuiComponent component) { + bind(); + component.blit(matrixStack, x, y, iconX, iconY, 16, 16); + } + + @OnlyIn(Dist.CLIENT) + public void render(PoseStack ms, MultiBufferSource buffer, int color) { + VertexConsumer builder = buffer.getBuffer(RenderType.textSeeThrough(ICON_ATLAS)); + Matrix4f matrix = ms.last().pose(); + Color rgb = new Color(color); + int light = LightTexture.FULL_BRIGHT; + + Vec3 vec1 = new Vec3(0, 0, 0); + Vec3 vec2 = new Vec3(0, 1, 0); + Vec3 vec3 = new Vec3(1, 1, 0); + Vec3 vec4 = new Vec3(1, 0, 0); + + float u1 = iconX * 1f / ICON_ATLAS_SIZE; + float u2 = (iconX + 16) * 1f / ICON_ATLAS_SIZE; + float v1 = iconY * 1f / ICON_ATLAS_SIZE; + float v2 = (iconY + 16) * 1f / ICON_ATLAS_SIZE; + + vertex(builder, matrix, vec1, rgb, u1, v1, light); + vertex(builder, matrix, vec2, rgb, u1, v2, light); + vertex(builder, matrix, vec3, rgb, u2, v2, light); + vertex(builder, matrix, vec4, rgb, u2, v1, light); + } + + @OnlyIn(Dist.CLIENT) + private void vertex(VertexConsumer builder, Matrix4f matrix, Vec3 vec, Color rgb, float u, float v, int light) { + builder.vertex(matrix, (float) vec.x, (float) vec.y, (float) vec.z) + .color(rgb.getRed(), rgb.getGreen(), rgb.getBlue(), 255) + .uv(u, v) + .uv2(light) + .endVertex(); + } + + @OnlyIn(Dist.CLIENT) + public DelegatedStencilElement asStencil() { + return new DelegatedStencilElement().withStencilRenderer((ms, w, h, alpha) -> this.render(ms, 0, 0)).withBounds(16, 16); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/ConfirmationScreen.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/ConfirmationScreen.java new file mode 100644 index 0000000..0175434 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/ConfirmationScreen.java @@ -0,0 +1,227 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.element.BoxElement; +import nl.requios.effortlessbuilding.create.foundation.gui.element.TextStencilElement; +import nl.requios.effortlessbuilding.create.foundation.gui.widget.BoxWidget; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.Style; +import org.lwjgl.opengl.GL30; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class ConfirmationScreen extends AbstractSimiScreen { + + private Screen source; + private Consumer action = _success -> { + }; + private List text = new ArrayList<>(); + private boolean centered = false; + private int x; + private int y; + private int textWidth; + private int textHeight; + private boolean tristate; + + private BoxWidget confirm; + private BoxWidget confirmDontSave; + private BoxWidget cancel; + private BoxElement textBackground; + + public enum Response { + Confirm, ConfirmDontSave, Cancel + } + + /* + * Removes text lines from the back of the list + * */ + public ConfirmationScreen removeTextLines(int amount) { + if (amount > text.size()) + return clearText(); + + text.subList(text.size() - amount, text.size()).clear(); + return this; + } + + public ConfirmationScreen clearText() { + this.text.clear(); + return this; + } + + public ConfirmationScreen addText(FormattedText text) { + this.text.add(text); + return this; + } + + public ConfirmationScreen withText(FormattedText text) { + return clearText().addText(text); + } + + public ConfirmationScreen at(int x, int y) { + this.x = Math.max(x, 0); + this.y = Math.max(y, 0); + this.centered = false; + return this; + } + + public ConfirmationScreen centered() { + this.centered = true; + return this; + } + + public ConfirmationScreen withAction(Consumer action) { + this.action = r -> action.accept(r == Response.Confirm); + return this; + } + + public ConfirmationScreen withThreeActions(Consumer action) { + this.action = action; + this.tristate = true; + return this; + } + + public void open(@Nonnull Screen source) { + this.source = source; + Minecraft client = source.getMinecraft(); + this.init(client, client.getWindow().getGuiScaledWidth(), client.getWindow().getGuiScaledHeight()); + this.minecraft.screen = this; + } + + @Override + public void tick() { + super.tick(); + source.tick(); + } + + @Override + protected void init() { + super.init(); + + ArrayList copy = new ArrayList<>(text); + text.clear(); + copy.forEach(t -> text.addAll(font.getSplitter().splitLines(t, 300, Style.EMPTY))); + + textHeight = text.size() * (font.lineHeight + 1) + 4; + textWidth = 300; + + if (centered) { + x = width/2 - textWidth/2 - 2; + y = height/2 - textHeight/2 - 16; + } else { + x = Math.max(0, x - textWidth / 2); + y = Math.max(0, y -= textHeight); + } + + if (x + textWidth > width) { + x = width - textWidth; + } + + if (y + textHeight + 30 > height) { + y = height - textHeight - 30; + } + + int buttonX = x + textWidth / 2 - 6 - (int) (70 * (tristate ? 1.5f : 1)); + + TextStencilElement confirmText = + new TextStencilElement(font, tristate ? "Save" : "Confirm").centered(true, true); + confirm = new BoxWidget(buttonX, y + textHeight + 6, 70, 16).withCallback(() -> accept(Response.Confirm)); + confirm.showingElement(confirmText.withElementRenderer(BoxWidget.gradientFactory.apply(confirm))); + addRenderableWidget(confirm); + + buttonX += 12 + 70; + + if (tristate) { + TextStencilElement confirmDontSaveText = + new TextStencilElement(font, "Don't Save").centered(true, true); + confirmDontSave = + new BoxWidget(buttonX, y + textHeight + 6, 70, 16).withCallback(() -> accept(Response.ConfirmDontSave)); + confirmDontSave.showingElement( + confirmDontSaveText.withElementRenderer(BoxWidget.gradientFactory.apply(confirmDontSave))); + addRenderableWidget(confirmDontSave); + buttonX += 12 + 70; + } + + TextStencilElement cancelText = new TextStencilElement(font, "Cancel").centered(true, true); + cancel = new BoxWidget(buttonX, y + textHeight + 6, 70, 16) + .withCallback(() -> accept(Response.Cancel)); + cancel.showingElement(cancelText.withElementRenderer(BoxWidget.gradientFactory.apply(cancel))); + addRenderableWidget(cancel); + + textBackground = new BoxElement() + .gradientBorder(Theme.p(Theme.Key.BUTTON_DISABLE)) + .withBounds(width + 10, textHeight + 35) + .at(-5, y - 5); + + if (text.size() == 1) + x = (width - font.width(text.get(0))) / 2; + } + + @Override + public void onClose() { + accept(Response.Cancel); + } + + private void accept(Response success) { + minecraft.screen = source; + action.accept(success); + } + + @Override + protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + textBackground.render(ms); + int offset = font.lineHeight + 1; + int lineY = y - offset; + + ms.pushPose(); + ms.translate(0, 0, 200); + + for (FormattedText line : text) { + lineY += offset; + if (line == null) + continue; + font.draw(ms, line.getString(), x, lineY, 0xeaeaea); + } + + ms.popPose(); + } + + @Override + protected void renderWindowBackground(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + endFrame(); + + source.render(ms, 0, 0, 10); // zero mouse coords to prevent further tooltips + + prepareFrame(); + + this.fillGradient(ms, 0, 0, this.width, this.height, 0x70101010, 0x80101010); + } + + + @Override + protected void prepareFrame() { + UIRenderHelper.swapAndBlitColor(minecraft.getMainRenderTarget(), UIRenderHelper.framebuffer); + RenderSystem.clear(GL30.GL_STENCIL_BUFFER_BIT | GL30.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX); + } + + @Override + protected void endFrame() { + UIRenderHelper.swapAndBlitColor(UIRenderHelper.framebuffer, minecraft.getMainRenderTarget()); + } + + @Override + public void resize(@Nonnull Minecraft client, int width, int height) { + super.resize(client, width, height); + source.resize(client, width, height); + } + + @Override + public boolean isPauseScreen() { + return true; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/CustomLightingSettings.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/CustomLightingSettings.java new file mode 100644 index 0000000..7bd3e7d --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/CustomLightingSettings.java @@ -0,0 +1,81 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector3f; + +public class CustomLightingSettings implements ILightingSettings { + + private Vector3f light1; + private Vector3f light2; + private Matrix4f lightMatrix; + + protected CustomLightingSettings(float yRot, float xRot) { + init(yRot, xRot, 0, 0, false); + } + + protected CustomLightingSettings(float yRot1, float xRot1, float yRot2, float xRot2) { + init(yRot1, xRot1, yRot2, xRot2, true); + } + + protected void init(float yRot1, float xRot1, float yRot2, float xRot2, boolean doubleLight) { + light1 = Vector3f.ZP.copy(); + light1.transform(Vector3f.YP.rotationDegrees(yRot1)); + light1.transform(Vector3f.XN.rotationDegrees(xRot1)); + + if (doubleLight) { + light2 = Vector3f.ZP.copy(); + light2.transform(Vector3f.YP.rotationDegrees(yRot2)); + light2.transform(Vector3f.XN.rotationDegrees(xRot2)); + } else { + light2 = Vector3f.ZERO; + } + + lightMatrix = new Matrix4f(); + lightMatrix.setIdentity(); + } + + @Override + public void applyLighting() { + RenderSystem.setupLevelDiffuseLighting(light1, light2, lightMatrix); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private float yRot1, xRot1; + private float yRot2, xRot2; + private boolean doubleLight; + + public Builder firstLightRotation(float yRot, float xRot) { + yRot1 = yRot; + xRot1 = xRot; + return this; + } + + public Builder secondLightRotation(float yRot, float xRot) { + yRot2 = yRot; + xRot2 = xRot; + doubleLight = true; + return this; + } + + public Builder doubleLight() { + doubleLight = true; + return this; + } + + public CustomLightingSettings build() { + if (doubleLight) { + return new CustomLightingSettings(yRot1, xRot1, yRot2, xRot2); + } else { + return new CustomLightingSettings(yRot1, xRot1); + } + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/ILightingSettings.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/ILightingSettings.java new file mode 100644 index 0000000..a349850 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/ILightingSettings.java @@ -0,0 +1,12 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.platform.Lighting; + +public interface ILightingSettings { + + void applyLighting(); + + static final ILightingSettings DEFAULT_3D = () -> Lighting.setupFor3DItems(); + static final ILightingSettings DEFAULT_FLAT = () -> Lighting.setupForFlatItems(); + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/RemovedGuiUtils.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/RemovedGuiUtils.java new file mode 100644 index 0000000..63da8af --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/RemovedGuiUtils.java @@ -0,0 +1,197 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.math.Matrix4f; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.Style; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.event.RenderTooltipEvent; +import net.minecraftforge.client.gui.ScreenUtils; +import net.minecraftforge.common.MinecraftForge; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +public class RemovedGuiUtils { + @Nonnull + private static ItemStack cachedTooltipStack = ItemStack.EMPTY; + + public static void preItemToolTip(@Nonnull ItemStack stack) { + cachedTooltipStack = stack; + } + + public static void postItemToolTip() { + cachedTooltipStack = ItemStack.EMPTY; + } + + public static void drawHoveringText(PoseStack mStack, List textLines, int mouseX, + int mouseY, int screenWidth, int screenHeight, int maxTextWidth, Font font) { + drawHoveringText(mStack, textLines, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, + ScreenUtils.DEFAULT_BACKGROUND_COLOR, ScreenUtils.DEFAULT_BORDER_COLOR_START, ScreenUtils.DEFAULT_BORDER_COLOR_END, + font); + } + + public static void drawHoveringText(PoseStack mStack, List textLines, int mouseX, + int mouseY, int screenWidth, int screenHeight, int maxTextWidth, int backgroundColor, int borderColorStart, + int borderColorEnd, Font font) { + drawHoveringText(cachedTooltipStack, mStack, textLines, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, + backgroundColor, borderColorStart, borderColorEnd, font); + } + + public static void drawHoveringText(@Nonnull final ItemStack stack, PoseStack mStack, + List textLines, int mouseX, int mouseY, int screenWidth, int screenHeight, + int maxTextWidth, Font font) { + drawHoveringText(stack, mStack, textLines, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, + ScreenUtils.DEFAULT_BACKGROUND_COLOR, ScreenUtils.DEFAULT_BORDER_COLOR_START, ScreenUtils.DEFAULT_BORDER_COLOR_END, + font); + } + + public static void drawHoveringText(@Nonnull final ItemStack stack, PoseStack pStack, + List textLines, int mouseX, int mouseY, int screenWidth, int screenHeight, + int maxTextWidth, int backgroundColor, int borderColorStart, int borderColorEnd, Font font) { + if (textLines.isEmpty()) + return; + + List list = ForgeHooksClient.gatherTooltipComponents(stack, textLines, + stack.getTooltipImage(), mouseX, screenWidth, screenHeight, font, font); + RenderTooltipEvent.Pre event = + new RenderTooltipEvent.Pre(stack, pStack, mouseX, mouseY, screenWidth, screenHeight, font, list); + if (MinecraftForge.EVENT_BUS.post(event)) + return; + + mouseX = event.getX(); + mouseY = event.getY(); + screenWidth = event.getScreenWidth(); + screenHeight = event.getScreenHeight(); + font = event.getFont(); + + // RenderSystem.disableRescaleNormal(); + RenderSystem.disableDepthTest(); + int tooltipTextWidth = 0; + + for (FormattedText textLine : textLines) { + int textLineWidth = font.width(textLine); + if (textLineWidth > tooltipTextWidth) + tooltipTextWidth = textLineWidth; + } + + boolean needsWrap = false; + + int titleLinesCount = 1; + int tooltipX = mouseX + 12; + if (tooltipX + tooltipTextWidth + 4 > screenWidth) { + tooltipX = mouseX - 16 - tooltipTextWidth; + if (tooltipX < 4) // if the tooltip doesn't fit on the screen + { + if (mouseX > screenWidth / 2) + tooltipTextWidth = mouseX - 12 - 8; + else + tooltipTextWidth = screenWidth - 16 - mouseX; + needsWrap = true; + } + } + + if (maxTextWidth > 0 && tooltipTextWidth > maxTextWidth) { + tooltipTextWidth = maxTextWidth; + needsWrap = true; + } + + if (needsWrap) { + int wrappedTooltipWidth = 0; + List wrappedTextLines = new ArrayList<>(); + for (int i = 0; i < textLines.size(); i++) { + FormattedText textLine = textLines.get(i); + List wrappedLine = font.getSplitter() + .splitLines(textLine, tooltipTextWidth, Style.EMPTY); + if (i == 0) + titleLinesCount = wrappedLine.size(); + + for (FormattedText line : wrappedLine) { + int lineWidth = font.width(line); + if (lineWidth > wrappedTooltipWidth) + wrappedTooltipWidth = lineWidth; + wrappedTextLines.add(line); + } + } + tooltipTextWidth = wrappedTooltipWidth; + textLines = wrappedTextLines; + + if (mouseX > screenWidth / 2) + tooltipX = mouseX - 16 - tooltipTextWidth; + else + tooltipX = mouseX + 12; + } + + int tooltipY = mouseY - 12; + int tooltipHeight = 8; + + if (textLines.size() > 1) { + tooltipHeight += (textLines.size() - 1) * 10; + if (textLines.size() > titleLinesCount) + tooltipHeight += 2; // gap between title lines and next lines + } + + if (tooltipY < 4) + tooltipY = 4; + else if (tooltipY + tooltipHeight + 4 > screenHeight) + tooltipY = screenHeight - tooltipHeight - 4; + + final int zLevel = 400; + RenderTooltipEvent.Color colorEvent = new RenderTooltipEvent.Color(stack, pStack, tooltipX, tooltipY, + font, backgroundColor, borderColorStart, borderColorEnd, list); + MinecraftForge.EVENT_BUS.post(colorEvent); + backgroundColor = colorEvent.getBackgroundStart(); + borderColorStart = colorEvent.getBorderStart(); + borderColorEnd = colorEvent.getBorderEnd(); + + pStack.pushPose(); + Matrix4f mat = pStack.last() + .pose(); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 3, tooltipY - 4, tooltipX + tooltipTextWidth + 3, + tooltipY - 3, backgroundColor, backgroundColor); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 3, tooltipY + tooltipHeight + 3, + tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 4, backgroundColor, backgroundColor); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, + tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 4, tooltipY - 3, tooltipX - 3, tooltipY + tooltipHeight + 3, + backgroundColor, backgroundColor); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX + tooltipTextWidth + 3, tooltipY - 3, + tooltipX + tooltipTextWidth + 4, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, + tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX + tooltipTextWidth + 2, tooltipY - 3 + 1, + tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, + tooltipY - 3 + 1, borderColorStart, borderColorStart); + ScreenUtils.drawGradientRect(mat, zLevel, tooltipX - 3, tooltipY + tooltipHeight + 2, + tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, borderColorEnd, borderColorEnd); + + MultiBufferSource.BufferSource renderType = MultiBufferSource.immediate(Tesselator.getInstance() + .getBuilder()); + pStack.translate(0.0D, 0.0D, zLevel); + + for (int lineNumber = 0; lineNumber < list.size(); ++lineNumber) { + ClientTooltipComponent line = list.get(lineNumber); + + if (line != null) + line.renderText(font, tooltipX, tooltipY, mat, renderType); + + if (lineNumber + 1 == titleLinesCount) + tooltipY += 2; + + tooltipY += 10; + } + + renderType.endBatch(); + pStack.popPose(); + + RenderSystem.enableDepthTest(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/Theme.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/Theme.java new file mode 100644 index 0000000..38d1ae6 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/Theme.java @@ -0,0 +1,229 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.Couple; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; + +public class Theme { + + private static final List THEMES = new ArrayList<>(); + public static final Theme BASE = addTheme(new Theme()); + + public static Theme addTheme(@Nonnull Theme theme) { + THEMES.add(theme); + THEMES.sort(Comparator.comparingInt(Theme::getPriority).reversed()); + return theme; + } + + public static void removeTheme(Theme theme) { + THEMES.remove(theme); + } + + public static void reload() { + THEMES.forEach(Theme::init); + } + + private static ColorHolder resolve(String key) { + return THEMES + .stream() + .map(theme -> theme.get(key)) + .filter(Objects::nonNull) + .findFirst() + .map(holder -> holder.lookupKey == null ? holder : resolve(holder.lookupKey)) + .orElse(ColorHolder.MISSING); + } + + @Nonnull public static Couple p(@Nonnull Key key) {return p(key.get());} + @Nonnull public static Couple p(String key) {return resolve(key).asPair();} + + @Nonnull public static Color c(@Nonnull Key key, boolean first) {return c(key.get(), first);} + @Nonnull public static Color c(String key, boolean first) {return p(key).get(first);} + + public static int i(@Nonnull Key key, boolean first) {return i(key.get(), first);} + public static int i(String key, boolean first) {return p(key).get(first).getRGB();} + + @Nonnull public static Color c(@Nonnull Key key) {return c(key.get());} + @Nonnull public static Color c(String key) {return resolve(key).get();} + + public static int i(@Nonnull Key key) {return i(key.get());} + public static int i(String key) {return resolve(key).get().getRGB();} + + //-----------// + + protected final Map colors; + private int priority = 0; + + protected Theme() { + colors = new HashMap<>(); + init(); + } + + /* + * Small note to addons: if you also want to make use of Theme, + * and add new Keys, please do not use mixins. Instead make a Theme + * subclass, override init and apply it via the static #addTheme + * + **/ + + protected void init() { + put(Key.BUTTON_IDLE, new Color(0xdd_8ab6d6, true), new Color(0x90_8ab6d6, true)); + put(Key.BUTTON_HOVER, new Color(0xff_9ABBD3, true), new Color(0xd0_9ABBD3, true)); + put(Key.BUTTON_CLICK, new Color(0xff_ffffff), new Color(0xee_ffffff)); + put(Key.BUTTON_DISABLE, new Color(0x80_909090, true), new Color(0x60_909090, true)); + put(Key.BUTTON_SUCCESS, new Color(0xcc_88f788, true), new Color(0xcc_20cc20, true)); + put(Key.BUTTON_FAIL, new Color(0xcc_f78888, true), new Color(0xcc_cc2020, true)); + put(Key.TEXT, new Color(0xff_eeeeee), new Color(0xff_a3a3a3)); + put(Key.TEXT_DARKER, new Color(0xff_a3a3a3), new Color(0xff_808080)); + put(Key.TEXT_ACCENT_STRONG, new Color(0xff_8ab6d6), new Color(0xff_8ab6d6)); + put(Key.TEXT_ACCENT_SLIGHT, new Color(0xff_ddeeff), new Color(0xff_a0b0c0)); + put(Key.STREAK, new Color(0x101010, false)); + put(Key.VANILLA_TOOLTIP_BORDER, new Color(0x50_5000ff, true), new Color(0x50_28007f, true)); + put(Key.VANILLA_TOOLTIP_BACKGROUND, new Color(0xf0_100010, true)); + + put(Key.PONDER_BUTTON_IDLE, new Color(0x60_c0c0ff, true), new Color(0x30_c0c0ff, true)); + put(Key.PONDER_BUTTON_HOVER, new Color(0xf0_c0c0ff, true), new Color(0xa0_c0c0ff, true)); + put(Key.PONDER_BUTTON_CLICK, new Color(0xff_ffffff), new Color(0xdd_ffffff)); + put(Key.PONDER_BUTTON_DISABLE, new Color(0x80_909090, true), new Color(0x20_909090, true)); + put(Key.PONDER_BACKGROUND_TRANSPARENT, new Color(0xdd_000000, true)); + put(Key.PONDER_BACKGROUND_FLAT, new Color(0xff_000000, false)); + put(Key.PONDER_BACKGROUND_IMPORTANT, new Color(0xdd_0e0e20, true)); + put(Key.PONDER_IDLE, new Color(0x40ffeedd, true), new Color(0x20ffeedd, true)); + put(Key.PONDER_HOVER, new Color(0x70ffffff, true), new Color(0x30ffffff, true)); + put(Key.PONDER_HIGHLIGHT, new Color(0xf0ffeedd, true), new Color(0x60ffeedd, true)); + put(Key.TEXT_WINDOW_BORDER, new Color(0x607a6000, true), new Color(0x207a6000, true)); + put(Key.PONDER_BACK_ARROW, new Color(0xf0aa9999, true), new Color(0x30aa9999, true)); + put(Key.PONDER_PROGRESSBAR, new Color(0x80ffeedd, true), new Color(0x50ffeedd, true)); + put(Key.PONDER_MISSING_CREATE, new Color(0x70_984500, true), new Color(0x70_692400, true)); + //put(Key.PONDER_MISSING_VANILLA, new Color(0x50_5000ff, true), new Color(0x50_300077, true)); + lookup(Key.PONDER_MISSING_VANILLA, Key.VANILLA_TOOLTIP_BORDER); + put(Key.CONFIG_TITLE_A, new Color(0xffc69fbc, true), new Color(0xfff6b8bb, true)); + put(Key.CONFIG_TITLE_B, new Color(0xfff6b8bb, true), new Color(0xfffbf994, true)); + //put(Key., new Color(0x, true), new Color(0x, true)); + } + + protected void put(String key, Color c) { + colors.put(key, ColorHolder.single(c)); + } + + protected void put(Key key, Color c) { + put(key.get(), c); + } + + protected void put(String key, Color c1, Color c2) { + colors.put(key, ColorHolder.pair(c1, c2)); + } + + protected void put(Key key, Color c1, Color c2) { + put(key.get(), c1, c2); + } + + protected void lookup(Key key, Key source) { + colors.put(key.get(), ColorHolder.lookup(source.get())); + } + + @Nullable protected ColorHolder get(String key) { + return colors.get(key); + } + + public int getPriority() { + return priority; + } + + public Theme setPriority(int priority) { + this.priority = priority; + return this; + } + + public static class Key { + + public static final Key BUTTON_IDLE = new Key(); + public static final Key BUTTON_HOVER = new Key(); + public static final Key BUTTON_CLICK = new Key(); + public static final Key BUTTON_DISABLE = new Key(); + public static final Key BUTTON_SUCCESS = new Key(); + public static final Key BUTTON_FAIL = new Key(); + + public static final Key TEXT = new Key(); + public static final Key TEXT_DARKER = new Key(); + public static final Key TEXT_ACCENT_STRONG = new Key(); + public static final Key TEXT_ACCENT_SLIGHT = new Key(); + + public static final Key STREAK = new Key(); + public static final Key VANILLA_TOOLTIP_BORDER = new Key(); + public static final Key VANILLA_TOOLTIP_BACKGROUND = new Key(); + + public static final Key PONDER_BACKGROUND_TRANSPARENT = new Key(); + public static final Key PONDER_BACKGROUND_FLAT = new Key(); + public static final Key PONDER_BACKGROUND_IMPORTANT = new Key(); + public static final Key PONDER_IDLE = new Key(); + public static final Key PONDER_HOVER = new Key(); + public static final Key PONDER_HIGHLIGHT = new Key(); + public static final Key TEXT_WINDOW_BORDER = new Key(); + public static final Key PONDER_BACK_ARROW = new Key(); + public static final Key PONDER_PROGRESSBAR = new Key(); + public static final Key PONDER_MISSING_CREATE = new Key(); + public static final Key PONDER_MISSING_VANILLA = new Key(); + + public static final Key PONDER_BUTTON_IDLE = new Key(); + public static final Key PONDER_BUTTON_HOVER = new Key(); + public static final Key PONDER_BUTTON_CLICK = new Key(); + public static final Key PONDER_BUTTON_DISABLE = new Key(); + + public static final Key CONFIG_TITLE_A = new Key(); + public static final Key CONFIG_TITLE_B = new Key(); + + private static int index = 0; + + private final String s; + + protected Key() { + this.s = "_" + index++; + } + + protected Key(String s) { + this.s = s; + } + + public String get() { + return s; + } + } + + private static class ColorHolder { + + private static final ColorHolder MISSING = ColorHolder.single(Color.BLACK); + + private Couple colors; + private String lookupKey; + + private static ColorHolder single(Color c) { + ColorHolder h = new ColorHolder(); + h.colors = Couple.create(c.setImmutable(), c.setImmutable()); + return h; + } + + private static ColorHolder pair(Color first, Color second) { + ColorHolder h = new ColorHolder(); + h.colors = Couple.create(first.setImmutable(), second.setImmutable()); + return h; + } + + private static ColorHolder lookup(String key) { + ColorHolder h = new ColorHolder(); + h.lookupKey = key; + return h; + } + + private Color get() { + return colors.getFirst(); + } + + private Couple asPair() { + return colors; + } + + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/TickableGuiEventListener.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/TickableGuiEventListener.java new file mode 100644 index 0000000..e4bed7d --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/TickableGuiEventListener.java @@ -0,0 +1,7 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import net.minecraft.client.gui.components.events.GuiEventListener; + +public interface TickableGuiEventListener extends GuiEventListener { + void tick(); +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/UIRenderHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/UIRenderHelper.java new file mode 100644 index 0000000..773b680 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/UIRenderHelper.java @@ -0,0 +1,316 @@ +package nl.requios.effortlessbuilding.create.foundation.gui; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlConst; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector3f; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.Couple; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraftforge.client.gui.ScreenUtils; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL30; + +import javax.annotation.Nonnull; + +public class UIRenderHelper { + + /** + * An FBO that has a stencil buffer for use wherever stencil are necessary. Forcing the main FBO to have a stencil + * buffer will cause GL error spam when using fabulous graphics. + */ + public static CustomRenderTarget framebuffer; + + public static void init() { + RenderSystem.recordRenderCall(() -> { + Window mainWindow = Minecraft.getInstance().getWindow(); + framebuffer = CustomRenderTarget.create(mainWindow); + }); + } + + public static void updateWindowSize(Window mainWindow) { + if (framebuffer != null) + framebuffer.resize(mainWindow.getWidth(), mainWindow.getHeight(), Minecraft.ON_OSX); + } + + public static void drawFramebuffer(float alpha) { + framebuffer.renderWithAlpha(alpha); + } + + /** + * Switch from src to dst, after copying the contents of src to dst. + */ + public static void swapAndBlitColor(RenderTarget src, RenderTarget dst) { + GlStateManager._glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, src.frameBufferId); + GlStateManager._glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dst.frameBufferId); + GlStateManager._glBlitFrameBuffer(0, 0, src.viewWidth, src.viewHeight, 0, 0, dst.viewWidth, dst.viewHeight, GL30.GL_COLOR_BUFFER_BIT, GL20.GL_LINEAR); + + GlStateManager._glBindFramebuffer(GlConst.GL_FRAMEBUFFER, dst.frameBufferId); + } + + public static void streak(PoseStack ms, float angle, int x, int y, int breadth, int length) { + streak(ms, angle, x, y, breadth, length, Theme.i(Theme.Key.STREAK)); + } + // angle in degrees; 0° -> fading to the right + // x and y specify the middle point of the starting edge + // breadth is the total width of the streak + + public static void streak(PoseStack ms, float angle, int x, int y, int breadth, int length, int color) { + int a1 = 0xa0 << 24; + int a2 = 0x80 << 24; + int a3 = 0x10 << 24; + int a4 = 0x00 << 24; + + color &= 0x00FFFFFF; + int c1 = a1 | color; + int c2 = a2 | color; + int c3 = a3 | color; + int c4 = a4 | color; + + ms.pushPose(); + ms.translate(x, y, 0); + ms.mulPose(Vector3f.ZP.rotationDegrees(angle - 90)); + + streak(ms, breadth / 2, length, c1, c2, c3, c4); + + ms.popPose(); + } + + public static void streak(PoseStack ms, float angle, int x, int y, int breadth, int length, Color c) { + Color color = c.copy().setImmutable(); + int c1 = color.scaleAlpha(0.625f).getRGB(); + int c2 = color.scaleAlpha(0.5f).getRGB(); + int c3 = color.scaleAlpha(0.0625f).getRGB(); + int c4 = color.scaleAlpha(0f).getRGB(); + + ms.pushPose(); + ms.translate(x, y, 0); + ms.mulPose(Vector3f.ZP.rotationDegrees(angle - 90)); + + streak(ms, breadth / 2, length, c1, c2, c3, c4); + + ms.popPose(); + } + + private static void streak(PoseStack ms, int width, int height, int c1, int c2, int c3, int c4) { + double split1 = .5; + double split2 = .75; + Matrix4f model = ms.last().pose(); + ScreenUtils.drawGradientRect(model, 0, -width, 0, width, (int) (split1 * height), c1, c2); + ScreenUtils.drawGradientRect(model, 0, -width, (int) (split1 * height), width, (int) (split2 * height), c2, c3); + ScreenUtils.drawGradientRect(model, 0, -width, (int) (split2 * height), width, height, c3, c4); + } + + /** + * @see #angledGradient(MatrixStack, float, int, int, int, int, int, Color, Color) + */ + public static void angledGradient(@Nonnull PoseStack ms, float angle, int x, int y, int breadth, int length, Couple c) { + angledGradient(ms, angle, x, y, 0, breadth, length, c); + } + + /** + * @see #angledGradient(MatrixStack, float, int, int, int, int, int, Color, Color) + */ + public static void angledGradient(@Nonnull PoseStack ms, float angle, int x, int y, int z, int breadth, int length, Couple c) { + angledGradient(ms, angle, x, y, z, breadth, length, c.getFirst(), c.getSecond()); + } + + /** + * @see #angledGradient(MatrixStack, float, int, int, int, int, int, Color, Color) + */ + public static void angledGradient(@Nonnull PoseStack ms, float angle, int x, int y, int breadth, int length, Color color1, Color color2) { + angledGradient(ms, angle, x, y, 0, breadth, length, color1, color2); + } + + /** + * x and y specify the middle point of the starting edge + * + * @param angle the angle of the gradient in degrees; 0° means from left to right + * @param color1 the color at the starting edge + * @param color2 the color at the ending edge + * @param breadth the total width of the gradient + */ + public static void angledGradient(@Nonnull PoseStack ms, float angle, int x, int y, int z, int breadth, int length, Color color1, Color color2) { + ms.pushPose(); + ms.translate(x, y, z); + ms.mulPose(Vector3f.ZP.rotationDegrees(angle - 90)); + + Matrix4f model = ms.last().pose(); + int w = breadth / 2; + ScreenUtils.drawGradientRect(model, 0, -w, 0, w, length, color1.getRGB(), color2.getRGB()); + + ms.popPose(); + } + + public static void breadcrumbArrow(PoseStack matrixStack, int x, int y, int z, int width, int height, int indent, Couple colors) {breadcrumbArrow(matrixStack, x, y, z, width, height, indent, colors.getFirst(), colors.getSecond());} + + // draws a wide chevron-style breadcrumb arrow pointing left + public static void breadcrumbArrow(PoseStack matrixStack, int x, int y, int z, int width, int height, int indent, Color startColor, Color endColor) { + matrixStack.pushPose(); + matrixStack.translate(x - indent, y, z); + + breadcrumbArrow(matrixStack, width, height, indent, startColor, endColor); + + matrixStack.popPose(); + } + + private static void breadcrumbArrow(PoseStack ms, int width, int height, int indent, Color c1, Color c2) { + + /* + * 0,0 x1,y1 ********************* x4,y4 ***** x7,y7 + * **** **** + * **** **** + * x0,y0 x2,y2 x5,y5 + * **** **** + * **** **** + * x3,y3 ********************* x6,y6 ***** x8,y8 + * + */ + + float x0 = 0, y0 = height / 2f; + float x1 = indent, y1 = 0; + float x2 = indent, y2 = height / 2f; + float x3 = indent, y3 = height; + float x4 = width, y4 = 0; + float x5 = width, y5 = height / 2f; + float x6 = width, y6 = height; + float x7 = indent + width, y7 = 0; + float x8 = indent + width, y8 = height; + + indent = Math.abs(indent); + width = Math.abs(width); + Color fc1 = Color.mixColors(c1, c2, 0); + Color fc2 = Color.mixColors(c1, c2, (indent) / (width + 2f * indent)); + Color fc3 = Color.mixColors(c1, c2, (indent + width) / (width + 2f * indent)); + Color fc4 = Color.mixColors(c1, c2, 1); + + RenderSystem.disableTexture(); + RenderSystem.enableBlend(); + RenderSystem.disableCull(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + + Tesselator tessellator = Tesselator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuilder(); + Matrix4f model = ms.last().pose(); + bufferbuilder.begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_COLOR); + + bufferbuilder.vertex(model, x0, y0, 0).color(fc1.getRed(), fc1.getGreen(), fc1.getBlue(), fc1.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x1, y1, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x2, y2, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + + bufferbuilder.vertex(model, x0, y0, 0).color(fc1.getRed(), fc1.getGreen(), fc1.getBlue(), fc1.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x2, y2, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x3, y3, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + + bufferbuilder.vertex(model, x3, y3, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x1, y1, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x4, y4, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + + bufferbuilder.vertex(model, x3, y3, 0).color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), fc2.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x4, y4, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x6, y6, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + + bufferbuilder.vertex(model, x5, y5, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x4, y4, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x7, y7, 0).color(fc4.getRed(), fc4.getGreen(), fc4.getBlue(), fc4.getAlpha()).endVertex(); + + bufferbuilder.vertex(model, x6, y6, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x5, y5, 0).color(fc3.getRed(), fc3.getGreen(), fc3.getBlue(), fc3.getAlpha()).endVertex(); + bufferbuilder.vertex(model, x8, y8, 0).color(fc4.getRed(), fc4.getGreen(), fc4.getBlue(), fc4.getAlpha()).endVertex(); + + tessellator.end(); + RenderSystem.enableCull(); + RenderSystem.disableBlend(); + RenderSystem.enableTexture(); + } + + //just like AbstractGui#drawTexture, but with a color at every vertex + public static void drawColoredTexture(PoseStack ms, Color c, int x, int y, int tex_left, int tex_top, int width, int height) { + drawColoredTexture(ms, c, x, y, 0, (float) tex_left, (float) tex_top, width, height, 256, 256); + } + + public static void drawColoredTexture(PoseStack ms, Color c, int x, int y, int z, float tex_left, float tex_top, int width, int height, int sheet_width, int sheet_height) { + drawColoredTexture(ms, c, x, x + width, y, y + height, z, width, height, tex_left, tex_top, sheet_width, sheet_height); + } + + public static void drawStretched(PoseStack ms, int left, int top, int w, int h, int z, AllGuiTextures tex) { + tex.bind(); + drawTexturedQuad(ms.last() + .pose(), Color.WHITE, left, left + w, top, top + h, z, tex.startX / 256f, (tex.startX + tex.width) / 256f, + tex.startY / 256f, (tex.startY + tex.height) / 256f); + } + + private static void drawColoredTexture(PoseStack ms, Color c, int left, int right, int top, int bot, int z, int tex_width, int tex_height, float tex_left, float tex_top, int sheet_width, int sheet_height) { + drawTexturedQuad(ms.last().pose(), c, left, right, top, bot, z, (tex_left + 0.0F) / (float) sheet_width, (tex_left + (float) tex_width) / (float) sheet_width, (tex_top + 0.0F) / (float) sheet_height, (tex_top + (float) tex_height) / (float) sheet_height); + } + + private static void drawTexturedQuad(Matrix4f m, Color c, int left, int right, int top, int bot, int z, float u1, float u2, float v1, float v2) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferbuilder = tesselator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShader(GameRenderer::getPositionColorTexShader); + bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR_TEX); + bufferbuilder.vertex(m, (float) left , (float) bot, (float) z).color(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()).uv(u1, v2).endVertex(); + bufferbuilder.vertex(m, (float) right, (float) bot, (float) z).color(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()).uv(u2, v2).endVertex(); + bufferbuilder.vertex(m, (float) right, (float) top, (float) z).color(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()).uv(u2, v1).endVertex(); + bufferbuilder.vertex(m, (float) left , (float) top, (float) z).color(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()).uv(u1, v1).endVertex(); + tesselator.end(); + RenderSystem.disableBlend(); + } + + public static void flipForGuiRender(PoseStack poseStack) { + poseStack.mulPoseMatrix(Matrix4f.createScaleMatrix(1, -1, 1)); + } + + public static class CustomRenderTarget extends RenderTarget { + + public CustomRenderTarget(boolean useDepth) { + super(useDepth); + } + + public static CustomRenderTarget create(Window mainWindow) { + CustomRenderTarget framebuffer = new CustomRenderTarget(true); + framebuffer.resize(mainWindow.getWidth(), mainWindow.getHeight(), Minecraft.ON_OSX); + framebuffer.setClearColor(0, 0, 0, 0); + framebuffer.enableStencil(); + return framebuffer; + } + + public void renderWithAlpha(float alpha) { + Window window = Minecraft.getInstance().getWindow(); + + float vx = (float) window.getGuiScaledWidth(); + float vy = (float) window.getGuiScaledHeight(); + float tx = (float) viewWidth / (float) width; + float ty = (float) viewHeight / (float) height; + + RenderSystem.enableTexture(); + RenderSystem.enableDepthTest(); + RenderSystem.setShader(() -> Minecraft.getInstance().gameRenderer.blitShader); + RenderSystem.getShader().setSampler("DiffuseSampler", colorTextureId); + + bindRead(); + + Tesselator tessellator = Tesselator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuilder(); + bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR_TEX); + + bufferbuilder.vertex(0, vy, 0).color(1, 1, 1, alpha).uv(0, 0).endVertex(); + bufferbuilder.vertex(vx, vy, 0).color(1, 1, 1, alpha).uv(tx, 0).endVertex(); + bufferbuilder.vertex(vx, 0, 0).color(1, 1, 1, alpha).uv(tx, ty).endVertex(); + bufferbuilder.vertex(0, 0, 0).color(1, 1, 1, alpha).uv(0, ty).endVertex(); + + tessellator.end(); + unbindRead(); + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/AbstractSimiContainerScreen.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/AbstractSimiContainerScreen.java new file mode 100644 index 0000000..8a2c440 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/AbstractSimiContainerScreen.java @@ -0,0 +1,175 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.container; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.AllGuiTextures; +import nl.requios.effortlessbuilding.create.foundation.gui.TickableGuiEventListener; +import nl.requios.effortlessbuilding.create.foundation.gui.widget.AbstractSimiWidget; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.inventory.ContainerScreen; +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@OnlyIn(Dist.CLIENT) +@ParametersAreNonnullByDefault +public abstract class AbstractSimiContainerScreen extends AbstractContainerScreen { + + protected int windowXOffset, windowYOffset; + + public AbstractSimiContainerScreen(T container, Inventory inv, Component title) { + super(container, inv, title); + } + + /** + * This method must be called before {@code super.init()}! + */ + protected void setWindowSize(int width, int height) { + imageWidth = width; + imageHeight = height; + } + + /** + * This method must be called before {@code super.init()}! + */ + protected void setWindowOffset(int xOffset, int yOffset) { + windowXOffset = xOffset; + windowYOffset = yOffset; + } + + @Override + protected void init() { + super.init(); + leftPos += windowXOffset; + topPos += windowYOffset; + } + + @Override + protected void containerTick() { + for (GuiEventListener listener : children()) { + if (listener instanceof TickableGuiEventListener tickable) { + tickable.tick(); + } + } + } + + @SuppressWarnings("unchecked") + protected void addRenderableWidgets(W... widgets) { + for (W widget : widgets) { + addRenderableWidget(widget); + } + } + + protected void addRenderableWidgets(Collection widgets) { + for (W widget : widgets) { + addRenderableWidget(widget); + } + } + + protected void removeWidgets(GuiEventListener... widgets) { + for (GuiEventListener widget : widgets) { + removeWidget(widget); + } + } + + protected void removeWidgets(Collection widgets) { + for (GuiEventListener widget : widgets) { + removeWidget(widget); + } + } + + @Override + public void render(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) { + partialTicks = minecraft.getFrameTime(); + + renderBackground(matrixStack); + + super.render(matrixStack, mouseX, mouseY, partialTicks); + + renderForeground(matrixStack, mouseX, mouseY, partialTicks); + } + + @Override + protected void renderLabels(PoseStack poseStack, int mouseX, int mouseY) { + // no-op to prevent screen- and inventory-title from being rendered at incorrect + // location + // could also set this.titleX/Y and this.playerInventoryTitleX/Y to the proper + // values instead + } + + protected void renderForeground(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + renderTooltip(ms, mouseX, mouseY); + for (Widget widget : renderables) { + if (widget instanceof AbstractSimiWidget simiWidget && simiWidget.isHoveredOrFocused()) { + List tooltip = simiWidget.getToolTip(); + if (tooltip.isEmpty()) + continue; + int ttx = simiWidget.lockedTooltipX == -1 ? mouseX : simiWidget.lockedTooltipX + simiWidget.x; + int tty = simiWidget.lockedTooltipY == -1 ? mouseY : simiWidget.lockedTooltipY + simiWidget.y; + renderComponentTooltip(ms, tooltip, ttx, tty); + } + } + } + + public int getLeftOfCentered(int textureWidth) { + return leftPos - windowXOffset + (imageWidth - textureWidth) / 2; + } + + public void renderPlayerInventory(PoseStack ms, int x, int y) { + AllGuiTextures.PLAYER_INVENTORY.render(ms, x, y, this); + font.draw(ms, playerInventoryTitle, x + 8, y + 6, 0x404040); + } + + @Override + public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { + InputConstants.Key mouseKey = InputConstants.getKey(pKeyCode, pScanCode); + if (getFocused() != null && this.minecraft.options.keyInventory.isActiveAndMatches(mouseKey)) + return false; + return super.keyPressed(pKeyCode, pScanCode, pModifiers); + } + + @Override + public GuiEventListener getFocused() { + GuiEventListener focused = super.getFocused(); + if (focused instanceof AbstractWidget && !((AbstractWidget) focused).isFocused()) + focused = null; + setFocused(focused); + return focused; + } + + /** + * Used for moving JEI out of the way of extra things like block renders. + * + * @return the space that the GUI takes up outside the normal rectangle defined + * by {@link ContainerScreen}. + */ + public List getExtraAreas() { + return Collections.emptyList(); + } + + @Deprecated + protected void debugWindowArea(PoseStack matrixStack) { + fill(matrixStack, leftPos + imageWidth, topPos + imageHeight, leftPos, topPos, 0xD3D3D3D3); + } + + @Deprecated + protected void debugExtraAreas(PoseStack matrixStack) { + for (Rect2i area : getExtraAreas()) { + fill(matrixStack, area.getX() + area.getWidth(), area.getY() + area.getHeight(), area.getX(), area.getY(), + 0xD3D3D3D3); + } + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/ClearContainerPacket.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/ClearContainerPacket.java new file mode 100644 index 0000000..6c7d786 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/ClearContainerPacket.java @@ -0,0 +1,35 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.container; + +import nl.requios.effortlessbuilding.create.foundation.networking.SimplePacketBase; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent.Context; + +import java.util.function.Supplier; + +public class ClearContainerPacket extends SimplePacketBase { + + public ClearContainerPacket() {} + + public ClearContainerPacket(FriendlyByteBuf buffer) {} + + @Override + public void write(FriendlyByteBuf buffer) {} + + @Override + public void handle(Supplier context) { + context.get() + .enqueueWork(() -> { + ServerPlayer player = context.get() + .getSender(); + if (player == null) + return; + if (!(player.containerMenu instanceof IClearableContainer)) + return; + ((IClearableContainer) player.containerMenu).clearContents(); + }); + context.get() + .setPacketHandled(true); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/ContainerBase.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/ContainerBase.java new file mode 100644 index 0000000..7ebd776 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/ContainerBase.java @@ -0,0 +1,70 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.container; + +import nl.requios.effortlessbuilding.create.foundation.utility.IInteractionChecker; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public abstract class ContainerBase extends AbstractContainerMenu { + + public Player player; + public Inventory playerInventory; + public T contentHolder; + + protected ContainerBase(MenuType type, int id, Inventory inv, FriendlyByteBuf extraData) { + super(type, id); + init(inv, createOnClient(extraData)); + } + + protected ContainerBase(MenuType type, int id, Inventory inv, T contentHolder) { + super(type, id); + init(inv, contentHolder); + } + + protected void init(Inventory inv, T contentHolderIn) { + player = inv.player; + playerInventory = inv; + contentHolder = contentHolderIn; + initAndReadInventory(contentHolder); + addSlots(); + broadcastChanges(); + } + + @OnlyIn(Dist.CLIENT) + protected abstract T createOnClient(FriendlyByteBuf extraData); + + protected abstract void initAndReadInventory(T contentHolder); + + protected abstract void addSlots(); + + protected abstract void saveData(T contentHolder); + + protected void addPlayerSlots(int x, int y) { + for (int hotbarSlot = 0; hotbarSlot < 9; ++hotbarSlot) + this.addSlot(new Slot(playerInventory, hotbarSlot, x + hotbarSlot * 18, y + 58)); + for (int row = 0; row < 3; ++row) + for (int col = 0; col < 9; ++col) + this.addSlot(new Slot(playerInventory, col + row * 9 + 9, x + col * 18, y + row * 18)); + } + + @Override + public void removed(Player playerIn) { + super.removed(playerIn); + saveData(contentHolder); + } + + @Override + public boolean stillValid(Player player) { + if (contentHolder == null) + return false; + if (contentHolder instanceof IInteractionChecker) + return ((IInteractionChecker) contentHolder).canPlayerUse(player); + return true; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/GhostItemContainer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/GhostItemContainer.java new file mode 100644 index 0000000..8e55be9 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/GhostItemContainer.java @@ -0,0 +1,108 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.container; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.items.ItemStackHandler; + +public abstract class GhostItemContainer extends ContainerBase implements IClearableContainer { + + public ItemStackHandler ghostInventory; + + protected GhostItemContainer(MenuType type, int id, Inventory inv, FriendlyByteBuf extraData) { + super(type, id, inv, extraData); + } + + protected GhostItemContainer(MenuType type, int id, Inventory inv, T contentHolder) { + super(type, id, inv, contentHolder); + } + + protected abstract ItemStackHandler createGhostInventory(); + + protected abstract boolean allowRepeats(); + + @Override + protected void initAndReadInventory(T contentHolder) { + ghostInventory = createGhostInventory(); + } + + @Override + public void clearContents() { + for (int i = 0; i < ghostInventory.getSlots(); i++) + ghostInventory.setStackInSlot(i, ItemStack.EMPTY); + } + + @Override + public boolean canTakeItemForPickAll(ItemStack stack, Slot slotIn) { + return slotIn.container == playerInventory; + } + + @Override + public boolean canDragTo(Slot slotIn) { + if (allowRepeats()) + return true; + return slotIn.container == playerInventory; + } + + @Override + public void clicked(int slotId, int dragType, ClickType clickTypeIn, Player player) { + if (slotId < 36) { + super.clicked(slotId, dragType, clickTypeIn, player); + return; + } + if (clickTypeIn == ClickType.THROW) + return; + + ItemStack held = getCarried(); + int slot = slotId - 36; + if (clickTypeIn == ClickType.CLONE) { + if (player.isCreative() && held.isEmpty()) { + ItemStack stackInSlot = ghostInventory.getStackInSlot(slot) + .copy(); + stackInSlot.setCount(stackInSlot.getMaxStackSize()); + setCarried(stackInSlot); + return; + } + return; + } + + ItemStack insert; + if (held.isEmpty()) { + insert = ItemStack.EMPTY; + } else { + insert = held.copy(); + insert.setCount(1); + } + ghostInventory.setStackInSlot(slot, insert); + getSlot(slotId).setChanged(); + } + + @Override + public ItemStack quickMoveStack(Player playerIn, int index) { + if (index < 36) { + ItemStack stackToInsert = playerInventory.getItem(index); + for (int i = 0; i < ghostInventory.getSlots(); i++) { + ItemStack stack = ghostInventory.getStackInSlot(i); + if (!allowRepeats() && ItemHandlerHelper.canItemStacksStack(stack, stackToInsert)) + break; + if (stack.isEmpty()) { + ItemStack copy = stackToInsert.copy(); + copy.setCount(1); + ghostInventory.insertItem(i, copy, false); + getSlot(i + 36).setChanged(); + break; + } + } + } else { + ghostInventory.extractItem(index - 36, 1, false); + getSlot(index).setChanged(); + } + return ItemStack.EMPTY; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/GhostItemSubmitPacket.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/GhostItemSubmitPacket.java new file mode 100644 index 0000000..001cab0 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/GhostItemSubmitPacket.java @@ -0,0 +1,52 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.container; + +import nl.requios.effortlessbuilding.create.foundation.networking.SimplePacketBase; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkEvent.Context; + +import java.util.function.Supplier; + +public class GhostItemSubmitPacket extends SimplePacketBase { + + private final ItemStack item; + private final int slot; + + public GhostItemSubmitPacket(ItemStack item, int slot) { + this.item = item; + this.slot = slot; + } + + public GhostItemSubmitPacket(FriendlyByteBuf buffer) { + item = buffer.readItem(); + slot = buffer.readInt(); + } + + @Override + public void write(FriendlyByteBuf buffer) { + buffer.writeItem(item); + buffer.writeInt(slot); + } + + @Override + public void handle(Supplier context) { + context.get() + .enqueueWork(() -> { + ServerPlayer player = context.get() + .getSender(); + if (player == null) + return; + + if (player.containerMenu instanceof GhostItemContainer) { + GhostItemContainer c = (GhostItemContainer) player.containerMenu; + c.ghostInventory.setStackInSlot(slot, item); + c.getSlot(36 + slot).setChanged(); + } + + }); + context.get() + .setPacketHandled(true); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/IClearableContainer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/IClearableContainer.java new file mode 100644 index 0000000..101d505 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/container/IClearableContainer.java @@ -0,0 +1,14 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.container; + +//import nl.requios.effortlessbuilding.create.foundation.networking.AllPackets; + +public interface IClearableContainer { + + default void sendClearPacket() { +// AllPackets.channel.sendToServer(new ClearContainerPacket()); + } + + @Deprecated //warning: does not work + public void clearContents(); + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/BoxElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/BoxElement.java new file mode 100644 index 0000000..e8af30a --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/BoxElement.java @@ -0,0 +1,153 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.math.Matrix4f; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.Couple; +import net.minecraft.client.renderer.GameRenderer; + +public class BoxElement extends RenderElement { + + protected Color background = new Color(0xff000000, true); + protected Color borderTop = new Color(0x40ffeedd, true); + protected Color borderBot = new Color(0x20ffeedd, true); + protected int borderOffset = 2; + + public T withBackground(Color color) { + this.background = color; + //noinspection unchecked + return (T) this; + } + + public T withBackground(int color) { + return withBackground(new Color(color, true)); + } + + public T flatBorder(Color color) { + this.borderTop = color; + this.borderBot = color; + //noinspection unchecked + return (T) this; + } + + public T flatBorder(int color) { + return flatBorder(new Color(color, true)); + } + + public T gradientBorder(Couple colors) { + this.borderTop = colors.getFirst(); + this.borderBot = colors.getSecond(); + //noinspection unchecked + return (T) this; + } + + public T gradientBorder(Color top, Color bot) { + this.borderTop = top; + this.borderBot = bot; + //noinspection unchecked + return (T) this; + } + + public T gradientBorder(int top, int bot) { + return gradientBorder(new Color(top, true), new Color(bot, true)); + } + + public T withBorderOffset(int offset) { + this.borderOffset = offset; + //noinspection unchecked + return (T) this; + } + + @Override + public void render(PoseStack ms) { + renderBox(ms); + } + + //total box width = 1 * 2 (outer border) + 1 * 2 (inner color border) + 2 * borderOffset + width + //defaults to 2 + 2 + 4 + 16 = 24px + //batch everything together to save a bunch of gl calls over ScreenUtils + protected void renderBox(PoseStack ms) { + /* + * _____________ + * _|_____________|_ + * | | ___________ | | + * | | | | | | | + * | | | | | | | + * | | |--* | | | | + * | | | h | | | + * | | | --w-+ | | | + * | | | | | | + * | | |_________| | | + * |_|_____________|_| + * |_____________| + * + * */ + RenderSystem.disableTexture(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + + Matrix4f model = ms.last().pose(); + int f = borderOffset; + Color c1 = background.copy().scaleAlpha(alpha); + Color c2 = borderTop.copy().scaleAlpha(alpha); + Color c3 = borderBot.copy().scaleAlpha(alpha); + Tesselator tessellator = Tesselator.getInstance(); + BufferBuilder b = tessellator.getBuilder(); + + b.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + //outer top + b.vertex(model, x - f - 1 , y - f - 2 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y - f - 2 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + //outer left + b.vertex(model, x - f - 2 , y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x - f - 2 , y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + //outer bottom + b.vertex(model, x - f - 1 , y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y + f + 2 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + 2 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + //outer right + b.vertex(model, x + f + 1 + width, y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 2 + width, y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 2 + width, y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + //inner background - also render behind the inner edges + b.vertex(model, x - f - 1 , y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + 1 + height, z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y - f - 1 , z).color(c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha()).endVertex(); + tessellator.end(); + b.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + //inner top - includes corners + b.vertex(model, x - f - 1 , y - f - 1 , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y - f , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y - f , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y - f - 1 , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + //inner left - excludes corners + b.vertex(model, x - f - 1 , y - f , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y + f + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x - f , y + f + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x - f , y - f , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + //inner bottom - includes corners + b.vertex(model, x - f - 1 , y + f + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x - f - 1 , y + f + 1 + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + 1 + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + //inner right - excludes corners + b.vertex(model, x + f + width, y - f , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + b.vertex(model, x + f + width, y + f + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y + f + height, z).color(c3.getRed(), c3.getGreen(), c3.getBlue(), c3.getAlpha()).endVertex(); + b.vertex(model, x + f + 1 + width, y - f , z).color(c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha()).endVertex(); + + tessellator.end(); + + RenderSystem.disableBlend(); + RenderSystem.enableTexture(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/CombinedStencilElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/CombinedStencilElement.java new file mode 100644 index 0000000..3f5183c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/CombinedStencilElement.java @@ -0,0 +1,79 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.vertex.PoseStack; + +import javax.annotation.Nonnull; + +public class CombinedStencilElement extends StencilElement { + + private StencilElement element1; + private StencilElement element2; + private ElementMode mode; + + private CombinedStencilElement() {} + + public static CombinedStencilElement of(@Nonnull StencilElement element1, @Nonnull StencilElement element2) { + return of(element1, element2, ElementMode.FIRST); + } + + public static CombinedStencilElement of(@Nonnull StencilElement element1, @Nonnull StencilElement element2, ElementMode mode) { + CombinedStencilElement e = new CombinedStencilElement(); + e.element1 = element1; + e.element2 = element2; + e.mode = mode; + return e; + } + + public T withFirst(StencilElement element) { + this.element1 = element; + //noinspection unchecked + return (T) this; + } + + public T withSecond(StencilElement element) { + this.element2 = element; + //noinspection unchecked + return (T) this; + } + + public T withMode(ElementMode mode) { + this.mode = mode; + //noinspection unchecked + return (T) this; + } + + @Override + protected void renderStencil(PoseStack ms) { + ms.pushPose(); + element1.transform(ms); + element1.withBounds(width, height); + element1.renderStencil(ms); + ms.popPose(); + ms.pushPose(); + element2.transform(ms); + element2.withBounds(width, height); + element2.renderStencil(ms); + ms.popPose(); + } + + @Override + protected void renderElement(PoseStack ms) { + if (mode.rendersFirst()) + element1.withBounds(width, height).renderElement(ms); + + if (mode.rendersSecond()) + element2.withBounds(width, height).renderElement(ms); + } + + public enum ElementMode { + FIRST, SECOND, BOTH; + + boolean rendersFirst() { + return this == FIRST || this == BOTH; + } + + boolean rendersSecond() { + return this == SECOND || this == BOTH; + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/DelegatedStencilElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/DelegatedStencilElement.java new file mode 100644 index 0000000..ba17d24 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/DelegatedStencilElement.java @@ -0,0 +1,52 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.UIRenderHelper; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; + +public class DelegatedStencilElement extends StencilElement { + + protected static final ElementRenderer EMPTY_RENDERER = (ms, width, height, alpha) -> {}; + protected static final ElementRenderer DEFAULT_ELEMENT = (ms, width, height, alpha) -> UIRenderHelper.angledGradient(ms, 0, -3, 5, height+4, width+6, new Color(0xff_10dd10).scaleAlpha(alpha), new Color(0xff_1010dd).scaleAlpha(alpha)); + + protected ElementRenderer stencil; + protected ElementRenderer element; + + public DelegatedStencilElement() { + stencil = EMPTY_RENDERER; + element = DEFAULT_ELEMENT; + } + + public DelegatedStencilElement(ElementRenderer stencil, ElementRenderer element) { + this.stencil = stencil; + this.element = element; + } + + public T withStencilRenderer(ElementRenderer renderer) { + stencil = renderer; + //noinspection unchecked + return (T) this; + } + + public T withElementRenderer(ElementRenderer renderer) { + element = renderer; + //noinspection unchecked + return (T) this; + } + + @Override + protected void renderStencil(PoseStack ms) { + stencil.render(ms, width, height, 1); + } + + @Override + protected void renderElement(PoseStack ms) { + element.render(ms, width, height, alpha); + } + + @FunctionalInterface + public interface ElementRenderer { + void render(PoseStack ms, int width, int height, float alpha); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/GuiGameElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/GuiGameElement.java new file mode 100644 index 0000000..d7d0fdb --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/GuiGameElement.java @@ -0,0 +1,297 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.jozufozu.flywheel.core.PartialModel; +import com.jozufozu.flywheel.core.model.ModelUtil; +import com.mojang.blaze3d.platform.GlStateManager.DestFactor; +import com.mojang.blaze3d.platform.GlStateManager.SourceFactor; +import com.mojang.blaze3d.platform.Lighting; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Vector3f; +//import nl.requios.effortlessbuilding.create.foundation.fluid.FluidRenderer; +import nl.requios.effortlessbuilding.create.foundation.gui.ILightingSettings; +import nl.requios.effortlessbuilding.create.foundation.gui.UIRenderHelper; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.VecHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.BaseFireBlock; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.client.RenderTypeHelper; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nullable; + +public class GuiGameElement { + + public static GuiRenderBuilder of(ItemStack stack) { + return new GuiItemRenderBuilder(stack); + } + + public static GuiRenderBuilder of(ItemLike itemProvider) { + return new GuiItemRenderBuilder(itemProvider); + } + + public static GuiRenderBuilder of(BlockState state) { + return new GuiBlockStateRenderBuilder(state); + } + + public static GuiRenderBuilder of(PartialModel partial) { + return new GuiBlockPartialRenderBuilder(partial); + } + + public static GuiRenderBuilder of(Fluid fluid) { + return new GuiBlockStateRenderBuilder(fluid.defaultFluidState() + .createLegacyBlock() + .setValue(LiquidBlock.LEVEL, 0)); + } + + public static abstract class GuiRenderBuilder extends RenderElement { + protected double xLocal, yLocal, zLocal; + protected double xRot, yRot, zRot; + protected double scale = 1; + protected int color = 0xFFFFFF; + protected Vec3 rotationOffset = Vec3.ZERO; + protected ILightingSettings customLighting = null; + + public GuiRenderBuilder atLocal(double x, double y, double z) { + this.xLocal = x; + this.yLocal = y; + this.zLocal = z; + return this; + } + + public GuiRenderBuilder rotate(double xRot, double yRot, double zRot) { + this.xRot = xRot; + this.yRot = yRot; + this.zRot = zRot; + return this; + } + + public GuiRenderBuilder rotateBlock(double xRot, double yRot, double zRot) { + return this.rotate(xRot, yRot, zRot) + .withRotationOffset(VecHelper.getCenterOf(BlockPos.ZERO)); + } + + public GuiRenderBuilder scale(double scale) { + this.scale = scale; + return this; + } + + public GuiRenderBuilder color(int color) { + this.color = color; + return this; + } + + public GuiRenderBuilder withRotationOffset(Vec3 offset) { + this.rotationOffset = offset; + return this; + } + + public GuiRenderBuilder lighting(ILightingSettings lighting) { + customLighting = lighting; + return this; + } + + protected void prepareMatrix(PoseStack matrixStack) { + matrixStack.pushPose(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableDepthTest(); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + prepareLighting(matrixStack); + } + + protected void transformMatrix(PoseStack matrixStack) { + matrixStack.translate(x, y, z); + matrixStack.scale((float) scale, (float) scale, (float) scale); + matrixStack.translate(xLocal, yLocal, zLocal); + UIRenderHelper.flipForGuiRender(matrixStack); + matrixStack.translate(rotationOffset.x, rotationOffset.y, rotationOffset.z); + matrixStack.mulPose(Vector3f.ZP.rotationDegrees((float) zRot)); + matrixStack.mulPose(Vector3f.XP.rotationDegrees((float) xRot)); + matrixStack.mulPose(Vector3f.YP.rotationDegrees((float) yRot)); + matrixStack.translate(-rotationOffset.x, -rotationOffset.y, -rotationOffset.z); + } + + protected void cleanUpMatrix(PoseStack matrixStack) { + matrixStack.popPose(); + cleanUpLighting(matrixStack); + } + + protected void prepareLighting(PoseStack matrixStack) { + if (customLighting != null) { + customLighting.applyLighting(); + } else { + Lighting.setupFor3DItems(); + } + } + + protected void cleanUpLighting(PoseStack matrixStack) { + if (customLighting != null) { + Lighting.setupFor3DItems(); + } + } + } + + private static class GuiBlockModelRenderBuilder extends GuiRenderBuilder { + + protected BakedModel blockModel; + protected BlockState blockState; + + public GuiBlockModelRenderBuilder(BakedModel blockmodel, @Nullable BlockState blockState) { + this.blockState = blockState == null ? Blocks.AIR.defaultBlockState() : blockState; + this.blockModel = blockmodel; + } + + @Override + public void render(PoseStack matrixStack) { + prepareMatrix(matrixStack); + + Minecraft mc = Minecraft.getInstance(); + BlockRenderDispatcher blockRenderer = mc.getBlockRenderer(); + MultiBufferSource.BufferSource buffer = mc.renderBuffers() + .bufferSource(); + + transformMatrix(matrixStack); + + RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); + renderModel(blockRenderer, buffer, matrixStack); + + cleanUpMatrix(matrixStack); + } + + protected void renderModel(BlockRenderDispatcher blockRenderer, MultiBufferSource.BufferSource buffer, + PoseStack ms) { + if (blockState.getBlock() == Blocks.AIR) { + RenderType renderType = Sheets.translucentCullBlockSheet(); + blockRenderer.getModelRenderer() + .renderModel(ms.last(), buffer.getBuffer(renderType), blockState, blockModel, 1, 1, 1, + LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, ModelUtil.VIRTUAL_DATA, null); + } else { + int color = Minecraft.getInstance() + .getBlockColors() + .getColor(blockState, null, null, 0); + Color rgb = new Color(color == -1 ? this.color : color); + + for (RenderType chunkType : blockModel.getRenderTypes(blockState, RandomSource.create(42L), ModelUtil.VIRTUAL_DATA)) { + RenderType renderType = RenderTypeHelper.getEntityRenderType(chunkType, true); + blockRenderer.getModelRenderer() + .renderModel(ms.last(), buffer.getBuffer(renderType), blockState, blockModel, + rgb.getRedAsFloat(), rgb.getGreenAsFloat(), rgb.getBlueAsFloat(), + LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, ModelUtil.VIRTUAL_DATA, chunkType); + } + } + + buffer.endBatch(); + } + + } + + public static class GuiBlockStateRenderBuilder extends GuiBlockModelRenderBuilder { + + public GuiBlockStateRenderBuilder(BlockState blockstate) { + super(Minecraft.getInstance() + .getBlockRenderer() + .getBlockModel(blockstate), blockstate); + } + + @Override + protected void renderModel(BlockRenderDispatcher blockRenderer, MultiBufferSource.BufferSource buffer, + PoseStack ms) { + if (blockState.getBlock() instanceof BaseFireBlock) { + Lighting.setupForFlatItems(); + super.renderModel(blockRenderer, buffer, ms); + Lighting.setupFor3DItems(); + return; + } + + super.renderModel(blockRenderer, buffer, ms); + + if (blockState.getFluidState() + .isEmpty()) + return; + +// FluidRenderer.renderFluidBox(new FluidStack(blockState.getFluidState() +// .getType(), 1000), 0, 0, 0, 1, 1, 1, buffer, ms, LightTexture.FULL_BRIGHT, false); +// buffer.endBatch(); + } + } + + public static class GuiBlockPartialRenderBuilder extends GuiBlockModelRenderBuilder { + + public GuiBlockPartialRenderBuilder(PartialModel partial) { + super(partial.get(), null); + } + + } + + public static class GuiItemRenderBuilder extends GuiRenderBuilder { + + private final ItemStack stack; + + public GuiItemRenderBuilder(ItemStack stack) { + this.stack = stack; + } + + public GuiItemRenderBuilder(ItemLike provider) { + this(new ItemStack(provider)); + } + + @Override + public void render(PoseStack matrixStack) { + prepareMatrix(matrixStack); + transformMatrix(matrixStack); + renderItemIntoGUI(matrixStack, stack, customLighting == null); + cleanUpMatrix(matrixStack); + } + + public static void renderItemIntoGUI(PoseStack matrixStack, ItemStack stack, boolean useDefaultLighting) { + ItemRenderer renderer = Minecraft.getInstance().getItemRenderer(); + BakedModel bakedModel = renderer.getModel(stack, null, null, 0); + + renderer.textureManager.getTexture(InventoryMenu.BLOCK_ATLAS).setFilter(false, false); + RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + matrixStack.pushPose(); + matrixStack.translate(0, 0, 100.0F + renderer.blitOffset); + matrixStack.translate(8.0F, -8.0F, 0.0F); + matrixStack.scale(16.0F, 16.0F, 16.0F); + MultiBufferSource.BufferSource buffer = Minecraft.getInstance().renderBuffers().bufferSource(); + boolean flatLighting = !bakedModel.usesBlockLight(); + if (useDefaultLighting && flatLighting) { + Lighting.setupForFlatItems(); + } + + renderer.render(stack, ItemTransforms.TransformType.GUI, false, matrixStack, buffer, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel); + buffer.endBatch(); + RenderSystem.enableDepthTest(); + if (useDefaultLighting && flatLighting) { + Lighting.setupFor3DItems(); + } + + matrixStack.popPose(); + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/RenderElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/RenderElement.java new file mode 100644 index 0000000..f5e1e86 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/RenderElement.java @@ -0,0 +1,89 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.vertex.PoseStack; + +public abstract class RenderElement implements ScreenElement { + + public static final RenderElement EMPTY = new RenderElement() { + @Override + public void render(PoseStack ms) { + } + }; + + public static RenderElement of(ScreenElement renderable) { + return new SimpleRenderElement(renderable); + } + + protected int width = 16, height = 16; + protected float x = 0, y = 0, z = 0; + protected float alpha = 1f; + + public T at(float x, float y) { + this.x = x; + this.y = y; + //noinspection unchecked + return (T) this; + } + + public T at(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + //noinspection unchecked + return (T) this; + } + + public T withBounds(int width, int height) { + this.width = width; + this.height = height; + //noinspection unchecked + return (T) this; + } + + public T withAlpha(float alpha) { + this.alpha = alpha; + //noinspection unchecked + return (T) this; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float getZ() { + return z; + } + + public abstract void render(PoseStack ms); + + @Override + public void render(PoseStack ms, int x, int y) { + this.at(x, y).render(ms); + } + + public static class SimpleRenderElement extends RenderElement { + + private ScreenElement renderable; + + public SimpleRenderElement(ScreenElement renderable) { + this.renderable = renderable; + } + + @Override + public void render(PoseStack ms) { + renderable.render(ms, (int) x, (int) y); + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/ScreenElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/ScreenElement.java new file mode 100644 index 0000000..453e38b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/ScreenElement.java @@ -0,0 +1,12 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public interface ScreenElement { + + @OnlyIn(Dist.CLIENT) + void render(PoseStack ms, int x, int y); + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/StencilElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/StencilElement.java new file mode 100644 index 0000000..ce71a8e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/StencilElement.java @@ -0,0 +1,50 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import org.lwjgl.opengl.GL11; + +public abstract class StencilElement extends RenderElement { + + @Override + public void render(PoseStack ms) { + ms.pushPose(); + transform(ms); + prepareStencil(ms); + renderStencil(ms); + prepareElement(ms); + renderElement(ms); + cleanUp(ms); + ms.popPose(); + } + + protected abstract void renderStencil(PoseStack ms); + + protected abstract void renderElement(PoseStack ms); + + protected void transform(PoseStack ms) { + ms.translate(x, y, z); + } + + protected void prepareStencil(PoseStack ms) { + GL11.glDisable(GL11.GL_STENCIL_TEST); + RenderSystem.stencilMask(~0); + RenderSystem.clear(GL11.GL_STENCIL_BUFFER_BIT, Minecraft.ON_OSX); + GL11.glEnable(GL11.GL_STENCIL_TEST); + RenderSystem.stencilOp(GL11.GL_REPLACE, GL11.GL_KEEP, GL11.GL_KEEP); + RenderSystem.stencilMask(0xFF); + RenderSystem.stencilFunc(GL11.GL_NEVER, 1, 0xFF); + } + + protected void prepareElement(PoseStack ms) { + GL11.glEnable(GL11.GL_STENCIL_TEST); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); + RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF); + } + + protected void cleanUp(PoseStack ms) { + GL11.glDisable(GL11.GL_STENCIL_TEST); + + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/TextStencilElement.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/TextStencilElement.java new file mode 100644 index 0000000..5ffd067 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/element/TextStencilElement.java @@ -0,0 +1,78 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.element; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.utility.Components; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.MutableComponent; + +public class TextStencilElement extends DelegatedStencilElement { + + protected Font font; + protected MutableComponent component; + protected boolean centerVertically = false; + protected boolean centerHorizontally = false; + + public TextStencilElement(Font font) { + super(); + this.font = font; + height = 10; + } + + public TextStencilElement(Font font, String text) { + this(font); + component = Components.literal(text); + } + + public TextStencilElement(Font font, MutableComponent component) { + this(font); + this.component = component; + } + + public TextStencilElement withText(String text) { + component = Components.literal(text); + return this; + } + + public TextStencilElement withText(MutableComponent component) { + this.component = component; + return this; + } + + public TextStencilElement centered(boolean vertical, boolean horizontal) { + this.centerVertically = vertical; + this.centerHorizontally = horizontal; + return this; + } + + @Override + protected void renderStencil(PoseStack ms) { + + float x = 0, y = 0; + if (centerHorizontally) + x = width / 2f - font.width(component) / 2f; + + if (centerVertically) + y = height / 2f - (font.lineHeight - 1) / 2f; + + font.draw(ms, component, x, y, 0xff_000000); + } + + @Override + protected void renderElement(PoseStack ms) { + float x = 0, y = 0; + if (centerHorizontally) + x = width / 2f - font.width(component) / 2f; + + if (centerVertically) + y = height / 2f - (font.lineHeight - 1) / 2f; + + ms.pushPose(); + ms.translate(x, y, 0); + element.render(ms, font.width(component), font.lineHeight + 2, alpha); + ms.popPose(); + } + + public MutableComponent getComponent() { + return component; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/AbstractSimiWidget.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/AbstractSimiWidget.java new file mode 100644 index 0000000..237559b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/AbstractSimiWidget.java @@ -0,0 +1,98 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.TickableGuiEventListener; +import nl.requios.effortlessbuilding.create.foundation.utility.Components; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; + +import javax.annotation.Nonnull; +import java.util.LinkedList; +import java.util.List; +import java.util.function.BiConsumer; + +public abstract class AbstractSimiWidget extends AbstractWidget implements TickableGuiEventListener { + + public static final int HEADER_RGB = 0x5391E1; + + protected float z; + protected boolean wasHovered = false; + protected List toolTip = new LinkedList<>(); + protected BiConsumer onClick = (_$, _$$) -> {}; + + public int lockedTooltipX = -1; + public int lockedTooltipY = -1; + + protected AbstractSimiWidget(int x, int y) { + this(x, y, 16, 16); + } + + protected AbstractSimiWidget(int x, int y, int width, int height) { + this(x, y, width, height, Components.immutableEmpty()); + } + + protected AbstractSimiWidget(int x, int y, int width, int height, Component message) { + super(x, y, width, height, message); + } + + public T withCallback(BiConsumer cb) { + this.onClick = cb; + //noinspection unchecked + return (T) this; + } + + public T withCallback(Runnable cb) { + return withCallback((_$, _$$) -> cb.run()); + } + + public T atZLevel(float z) { + this.z = z; + //noinspection unchecked + return (T) this; + } + + public List getToolTip() { + return toolTip; + } + + @Override + public void tick() {} + + @Override + public void render(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + if (visible) { + isHovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height; + beforeRender(ms, mouseX, mouseY, partialTicks); + renderButton(ms, mouseX, mouseY, partialTicks); + afterRender(ms, mouseX, mouseY, partialTicks); + wasHovered = isHoveredOrFocused(); + } + } + + protected void beforeRender(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + ms.pushPose(); + } + + @Override + public void renderButton(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + } + + protected void afterRender(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + ms.popPose(); + } + + public void runCallback(double mouseX, double mouseY) { + onClick.accept((int) mouseX, (int) mouseY); + } + + @Override + public void onClick(double mouseX, double mouseY) { + runCallback(mouseX, mouseY); + } + + @Override + public void updateNarration(NarrationElementOutput pNarrationElementOutput) { + defaultButtonNarrationText(pNarrationElementOutput); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/BoxWidget.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/BoxWidget.java new file mode 100644 index 0000000..1456d10 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/BoxWidget.java @@ -0,0 +1,228 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.Theme; +import nl.requios.effortlessbuilding.create.foundation.gui.Theme.Key; +import nl.requios.effortlessbuilding.create.foundation.gui.UIRenderHelper; +import nl.requios.effortlessbuilding.create.foundation.gui.element.BoxElement; +import nl.requios.effortlessbuilding.create.foundation.gui.element.DelegatedStencilElement; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.Couple; +import nl.requios.effortlessbuilding.create.foundation.utility.animation.LerpedFloat; + +import javax.annotation.Nonnull; +import java.util.function.Function; + +public class BoxWidget extends ElementWidget { + + public static final Function gradientFactory = (box) -> (ms, w, h, alpha) -> UIRenderHelper.angledGradient(ms, 90, w/2, -2, w + 4, h + 4, box.gradientColor1, box.gradientColor2); + + protected BoxElement box; + + protected Color customBorderTop; + protected Color customBorderBot; + protected Color customBackground; + protected boolean animateColors = true; + protected LerpedFloat colorAnimation = LerpedFloat.linear(); + + protected Color gradientColor1, gradientColor2; + private Color previousColor1, previousColor2; + private Color colorTarget1 = Theme.c(getIdleTheme(), true).copy(); + private Color colorTarget2 = Theme.c(getIdleTheme(), false).copy(); + + public BoxWidget() { + this(0, 0); + } + + public BoxWidget(int x, int y) { + this(x, y, 16, 16); + } + + public BoxWidget(int x, int y, int width, int height) { + super(x, y, width, height); + box = new BoxElement() + .at(x, y) + .withBounds(width, height); + gradientColor1 = colorTarget1; + gradientColor2 = colorTarget2; + } + + public T withBounds(int width, int height) { + this.width = width; + this.height = height; + //noinspection unchecked + return (T) this; + } + + public T withBorderColors(Couple colors) { + this.customBorderTop = colors.getFirst(); + this.customBorderBot = colors.getSecond(); + updateColorsFromState(); + //noinspection unchecked + return (T) this; + } + + public T withBorderColors(Color top, Color bot) { + this.customBorderTop = top; + this.customBorderBot = bot; + updateColorsFromState(); + //noinspection unchecked + return (T) this; + } + + public T withCustomBackground(Color color) { + this.customBackground = color; + //noinspection unchecked + return (T) this; + } + + public T animateColors(boolean b) { + this.animateColors = b; + //noinspection unchecked + return (T) this; + } + + @Override + public void tick() { + super.tick(); + colorAnimation.tickChaser(); + } + + @Override + public void onClick(double x, double y) { + super.onClick(x, y); + + gradientColor1 = Theme.c(getClickTheme(), true); + gradientColor2 = Theme.c(getClickTheme(), false); + startGradientAnimation(getColorForState(true), getColorForState(false), true, 0.15); + } + + @Override + protected void beforeRender(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + super.beforeRender(ms, mouseX, mouseY, partialTicks); + + if (isHovered != wasHovered) { + startGradientAnimation( + getColorForState(true), + getColorForState(false), + isHovered + ); + } + + if (colorAnimation.settled()) { + gradientColor1 = colorTarget1; + gradientColor2 = colorTarget2; + } else { + float animationValue = 1 - Math.abs(colorAnimation.getValue(partialTicks)); + gradientColor1 = Color.mixColors(previousColor1, colorTarget1, animationValue); + gradientColor2 = Color.mixColors(previousColor2, colorTarget2, animationValue); + } + + } + + @Override + public void renderButton(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + float fadeValue = fade.getValue(partialTicks); + if (fadeValue < .1f) + return; + + box.withAlpha(fadeValue); + box.withBackground(customBackground != null ? customBackground : Theme.c(Theme.Key.PONDER_BACKGROUND_TRANSPARENT)) + .gradientBorder(gradientColor1, gradientColor2) + .at(x, y, z) + .withBounds(width, height) + .render(ms); + + super.renderButton(ms, mouseX, mouseY, partialTicks); + + wasHovered = isHovered; + } + + @Override + public boolean isMouseOver(double mX, double mY) { + if (!active || !visible) + return false; + + float padX = 2 + paddingX; + float padY = 2 + paddingY; + + return x - padX <= mX && y - padY <= mY && mX < x + padX + width && mY < y + padY + height; + } + + @Override + protected boolean clicked(double pMouseX, double pMouseY) { + if (!active || !visible) + return false; + return isMouseOver(pMouseX, pMouseY); + } + + public BoxElement getBox() { + return box; + } + + public void updateColorsFromState() { + colorTarget1 = getColorForState(true); + colorTarget2 = getColorForState(false); + } + + public void animateGradientFromState() { + startGradientAnimation( + getColorForState(true), + getColorForState(false), + true + ); + } + + private void startGradientAnimation(Color c1, Color c2, boolean positive, double expSpeed) { + if (!animateColors) + return; + + colorAnimation.startWithValue(positive ? 1 : -1); + colorAnimation.chase(0, expSpeed, LerpedFloat.Chaser.EXP); + colorAnimation.tickChaser(); + + previousColor1 = gradientColor1; + previousColor2 = gradientColor2; + + colorTarget1 = c1; + colorTarget2 = c2; + } + + private void startGradientAnimation(Color c1, Color c2, boolean positive) { + startGradientAnimation(c1, c2, positive, 0.6); + } + + private Color getColorForState(boolean first) { + if (!active) + return Theme.p(getDisabledTheme()).get(first); + + if (isHovered) { + if (first) + return customBorderTop != null ? customBorderTop.darker() : Theme.c(getHoverTheme(), true); + else + return customBorderBot != null ? customBorderBot.darker() : Theme.c(getHoverTheme(), false); + } + + if (first) + return customBorderTop != null ? customBorderTop : Theme.c(getIdleTheme(), true); + else + return customBorderBot != null ? customBorderBot : Theme.c(getIdleTheme(), false); + } + + public Key getDisabledTheme() { + return Theme.Key.BUTTON_DISABLE; + } + + public Key getIdleTheme() { + return Theme.Key.BUTTON_IDLE; + } + + public Key getHoverTheme() { + return Theme.Key.BUTTON_HOVER; + } + + public Key getClickTheme() { + return Theme.Key.BUTTON_CLICK; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/ElementWidget.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/ElementWidget.java new file mode 100644 index 0000000..597f5da --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/ElementWidget.java @@ -0,0 +1,156 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.element.RenderElement; +import nl.requios.effortlessbuilding.create.foundation.gui.element.ScreenElement; +import nl.requios.effortlessbuilding.create.foundation.utility.animation.LerpedFloat; + +import javax.annotation.Nonnull; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +public class ElementWidget extends AbstractSimiWidget { + + protected RenderElement element = RenderElement.EMPTY; + + protected boolean usesFade = false; + protected int fadeModX; + protected int fadeModY; + protected LerpedFloat fade = LerpedFloat.linear().startWithValue(1); + + protected boolean rescaleElement = false; + protected float rescaleSizeX; + protected float rescaleSizeY; + + protected float paddingX = 0; + protected float paddingY = 0; + + public ElementWidget(int x, int y) { + super(x, y); + } + + public ElementWidget(int x, int y, int width, int height) { + super(x, y, width, height); + } + + public T showingElement(RenderElement element) { + this.element = element; + //noinspection unchecked + return (T) this; + } + + public T showing(ScreenElement renderable) { + return this.showingElement(RenderElement.of(renderable)); + } + + public T modifyElement(Consumer consumer) { + if (element != null) + consumer.accept(element); + //noinspection unchecked + return (T) this; + } + + public T mapElement(UnaryOperator function) { + if (element != null) + element = function.apply(element); + //noinspection unchecked + return (T) this; + } + + public T withPadding(float paddingX, float paddingY) { + this.paddingX = paddingX; + this.paddingY = paddingY; + //noinspection unchecked + return (T) this; + } + + public T enableFade(int fadeModifierX, int fadeModifierY) { + this.fade.startWithValue(0); + this.usesFade = true; + this.fadeModX = fadeModifierX; + this.fadeModY = fadeModifierY; + //noinspection unchecked + return (T) this; + } + + public T disableFade() { + this.fade.startWithValue(1); + this.usesFade = false; + //noinspection unchecked + return (T) this; + } + + public LerpedFloat fade() { + return fade; + } + + public T fade(float target) { + fade.chase(target, 0.1, LerpedFloat.Chaser.EXP); + //noinspection unchecked + return (T) this; + } + + /** + * Rescaling and its effects aren't properly tested with most elements. + * Thought it should work fine when using a TextStencilElement. + * Check BaseConfigScreen's title for such an example. + */ + @Deprecated + public T rescaleElement(float rescaleSizeX, float rescaleSizeY) { + this.rescaleElement = true; + this.rescaleSizeX = rescaleSizeX; + this.rescaleSizeY = rescaleSizeY; + //noinspection unchecked + return (T) this; + } + + public T disableRescale() { + this.rescaleElement = false; + //noinspection unchecked + return (T) this; + } + + @Override + public void tick() { + super.tick(); + fade.tickChaser(); + } + + @Override + protected void beforeRender(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + super.beforeRender(ms, mouseX, mouseY, partialTicks); + isHovered = isMouseOver(mouseX, mouseY); + + float fadeValue = fade.getValue(partialTicks); + element.withAlpha(fadeValue); + if (fadeValue < 1) { + ms.translate((1 - fadeValue) * fadeModX, (1 - fadeValue) * fadeModY, 0); + } + } + + @Override + public void renderButton(@Nonnull PoseStack ms, int mouseX, int mouseY, float partialTicks) { + ms.pushPose(); + ms.translate(x + paddingX, y + paddingY, z); + float innerWidth = width - 2 * paddingX; + float innerHeight = height - 2 * paddingY; + float eX = element.getX(), eY = element.getY(); + if (rescaleElement) { + float xScale = innerWidth / rescaleSizeX; + float yScale = innerHeight / rescaleSizeY; + ms.scale(xScale, yScale, 1); + element.at(eX / xScale, eY / yScale); + innerWidth /= xScale; + innerHeight /= yScale; + } + element.withBounds((int) innerWidth, (int) innerHeight).render(ms); + ms.popPose(); + if (rescaleElement) { + element.at(eX, eY); + } + } + + public RenderElement getRenderElement() { + return element; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/IconButton.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/IconButton.java new file mode 100644 index 0000000..86e3d98 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/IconButton.java @@ -0,0 +1,51 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.AllGuiTextures; +import nl.requios.effortlessbuilding.create.foundation.gui.element.ScreenElement; +import net.minecraft.network.chat.Component; + +import javax.annotation.Nonnull; + +public class IconButton extends AbstractSimiWidget { + + protected ScreenElement icon; + + public IconButton(int x, int y, ScreenElement icon) { + this(x, y, 18, 18, icon); + } + + public IconButton(int x, int y, int w, int h, ScreenElement icon) { + super(x, y, w, h); + this.icon = icon; + } + + @Override + public void renderButton(@Nonnull PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) { + if (visible) { + isHovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height; + + AllGuiTextures button = !active ? AllGuiTextures.BUTTON_DOWN + : isHoveredOrFocused() ? AllGuiTextures.BUTTON_HOVER : AllGuiTextures.BUTTON; + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + drawBg(matrixStack, button); + icon.render(matrixStack, x + 1, y + 1); + } + } + + protected void drawBg(PoseStack matrixStack, AllGuiTextures button) { + AllGuiTextures.BUTTON.bind(); + blit(matrixStack, x, y, button.startX, button.startY, button.width, button.height); + } + + public void setToolTip(Component text) { + toolTip.clear(); + toolTip.add(text); + } + + public void setIcon(ScreenElement icon) { + this.icon = icon; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/Indicator.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/Indicator.java new file mode 100644 index 0000000..c564910 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/Indicator.java @@ -0,0 +1,41 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.gui.AllGuiTextures; +import net.minecraft.network.chat.Component; + +import javax.annotation.Nonnull; + +public class Indicator extends AbstractSimiWidget { + + public State state; + + public Indicator(int x, int y, Component tooltip) { + super(x, y, AllGuiTextures.INDICATOR.width, AllGuiTextures.INDICATOR.height); + this.toolTip = ImmutableList.of(tooltip); + this.state = State.OFF; + } + + @Override + public void render(@Nonnull PoseStack matrixStack, int mouseX, int mouseY, float partialTicks ) { + if (!visible) + return; + AllGuiTextures toDraw; + switch (state) { + case ON: toDraw = AllGuiTextures.INDICATOR_WHITE; break; + case OFF: toDraw = AllGuiTextures.INDICATOR; break; + case RED: toDraw = AllGuiTextures.INDICATOR_RED; break; + case YELLOW: toDraw = AllGuiTextures.INDICATOR_YELLOW; break; + case GREEN: toDraw = AllGuiTextures.INDICATOR_GREEN; break; + default: toDraw = AllGuiTextures.INDICATOR; break; + } + toDraw.render(matrixStack, x, y, this); + } + + public enum State { + OFF, ON, + RED, YELLOW, GREEN; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/Label.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/Label.java new file mode 100644 index 0000000..f220dfd --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/Label.java @@ -0,0 +1,88 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.utility.Components; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +import javax.annotation.Nonnull; + +public class Label extends AbstractSimiWidget { + + public Component text; + public String suffix; + protected boolean hasShadow; + protected int color; + protected Font font; + + public Label(int x, int y, Component text) { + super(x, y, Minecraft.getInstance().font.width(text), 10); + font = Minecraft.getInstance().font; + this.text = Components.literal("Label"); + color = 0xFFFFFF; + hasShadow = false; + suffix = ""; + } + + public Label colored(int color) { + this.color = color; + return this; + } + + public Label withShadow() { + this.hasShadow = true; + return this; + } + + public Label withSuffix(String s) { + suffix = s; + return this; + } + + public void setTextAndTrim(Component newText, boolean trimFront, int maxWidthPx) { + Font fontRenderer = Minecraft.getInstance().font; + + if (fontRenderer.width(newText) <= maxWidthPx) { + text = newText; + return; + } + + String trim = "..."; + int trimWidth = fontRenderer.width(trim); + + String raw = newText.getString(); + StringBuilder builder = new StringBuilder(raw); + int startIndex = trimFront ? 0 : raw.length() - 1; + int endIndex = !trimFront ? 0 : raw.length() - 1; + int step = (int) Math.signum(endIndex - startIndex); + + for (int i = startIndex; i != endIndex; i += step) { + String sub = builder.substring(trimFront ? i : startIndex, trimFront ? endIndex + 1 : i + 1); + if (fontRenderer.width(Components.literal(sub).setStyle(newText.getStyle())) + trimWidth <= maxWidthPx) { + text = Components.literal(trimFront ? trim + sub : sub + trim).setStyle(newText.getStyle()); + return; + } + } + + } + + @Override + public void renderButton(@Nonnull PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) { + if (text == null || text.getString().isEmpty()) + return; + + RenderSystem.setShaderColor(1, 1, 1, 1); + MutableComponent copy = text.plainCopy(); + if (suffix != null && !suffix.isEmpty()) + copy.append(suffix); + + if (hasShadow) + font.drawShadow(matrixStack, copy, x, y, color); + else + font.draw(matrixStack, copy, x, y, color); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/ScrollInput.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/ScrollInput.java new file mode 100644 index 0000000..fa27a6f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/ScrollInput.java @@ -0,0 +1,173 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import nl.requios.effortlessbuilding.create.AllKeys; +//import nl.requios.effortlessbuilding.create.AllSoundEvents; +import nl.requios.effortlessbuilding.create.foundation.utility.Components; +import nl.requios.effortlessbuilding.create.foundation.utility.Lang; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +//import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class ScrollInput extends AbstractSimiWidget { + + protected Consumer onScroll; + protected int state; + protected Component title = Lang.translateDirect("gui.scrollInput.defaultTitle"); + protected final Component scrollToModify = Lang.translateDirect("gui.scrollInput.scrollToModify"); + protected final Component shiftScrollsFaster = Lang.translateDirect("gui.scrollInput.shiftScrollsFaster"); + protected Label displayLabel; + protected boolean inverted; + protected Function formatter; + + protected int min, max; + protected int shiftStep; + Function step; + + public ScrollInput(int xIn, int yIn, int widthIn, int heightIn) { + super(xIn, yIn, widthIn, heightIn); + state = 0; + min = 0; + max = 1; + shiftStep = 5; + step = standardStep(); + formatter = i -> Components.literal(String.valueOf(i)); + } + + public Function standardStep() { + return c -> c.shift ? shiftStep : 1; + } + + public ScrollInput inverted() { + inverted = true; + return this; + } + + public ScrollInput withRange(int min, int max) { + this.min = min; + this.max = max; + return this; + } + + public ScrollInput calling(Consumer onScroll) { + this.onScroll = onScroll; + return this; + } + + public ScrollInput format(Function formatter) { + this.formatter = formatter; + return this; + } + + public ScrollInput removeCallback() { + this.onScroll = null; + return this; + } + + public ScrollInput titled(MutableComponent title) { + this.title = title; + updateTooltip(); + return this; + } + + public ScrollInput withStepFunction(Function step) { + this.step = step; + return this; + } + + public ScrollInput writingTo(Label label) { + this.displayLabel = label; + if (label != null) + writeToLabel(); + return this; + } + + public int getState() { + return state; + } + + public ScrollInput setState(int state) { + this.state = state; + clampState(); + updateTooltip(); + if (displayLabel != null) + writeToLabel(); + return this; + } + + public ScrollInput withShiftStep(int step) { + shiftStep = step; + return this; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double delta) { + if (inverted) + delta *= -1; + + StepContext context = new StepContext(); + context.control = AllKeys.ctrlDown(); + context.shift = AllKeys.shiftDown(); + context.currentValue = state; + context.forward = delta > 0; + + int priorState = state; + boolean shifted = AllKeys.shiftDown(); + int step = (int) Math.signum(delta) * this.step.apply(context); + + state += step; + if (shifted) + state -= state % shiftStep; + + clampState(); + + if (priorState != state) { +// Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(AllSoundEvents.SCROLL_VALUE.getMainEvent(), 1.5f + 0.1f * (state-min)/(max-min))); + onChanged(); + } + + return priorState != state; + } + + protected void clampState() { + if (state >= max) + state = max - 1; + if (state < min) + state = min; + } + + public void onChanged() { + if (displayLabel != null) + writeToLabel(); + if (onScroll != null) + onScroll.accept(state); + updateTooltip(); + } + + protected void writeToLabel() { + displayLabel.text = formatter.apply(state); + } + + protected void updateTooltip() { + toolTip.clear(); + if (title == null) + return; + toolTip.add(title.plainCopy() + .withStyle(s -> s.withColor(HEADER_RGB))); + toolTip.add(scrollToModify.plainCopy() + .withStyle(ChatFormatting.ITALIC, ChatFormatting.DARK_GRAY)); + toolTip.add(shiftScrollsFaster.plainCopy() + .withStyle(ChatFormatting.ITALIC, ChatFormatting.DARK_GRAY)); + } + + public static class StepContext { + public int currentValue; + public boolean forward; + public boolean shift; + public boolean control; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/SelectionScrollInput.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/SelectionScrollInput.java new file mode 100644 index 0000000..b495563 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/SelectionScrollInput.java @@ -0,0 +1,67 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import nl.requios.effortlessbuilding.create.foundation.utility.Components; +import nl.requios.effortlessbuilding.create.foundation.utility.Lang; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +import java.util.ArrayList; +import java.util.List; + +public class SelectionScrollInput extends ScrollInput { + + private final MutableComponent scrollToSelect = Lang.translateDirect("gui.scrollInput.scrollToSelect"); + protected List options; + + public SelectionScrollInput(int xIn, int yIn, int widthIn, int heightIn) { + super(xIn, yIn, widthIn, heightIn); + options = new ArrayList<>(); + inverted(); + } + + public ScrollInput forOptions(List options) { + this.options = options; + this.max = options.size(); + format(options::get); + updateTooltip(); + return this; + } + + @Override + protected void updateTooltip() { + toolTip.clear(); + if (title == null) + return; + toolTip.add(title.plainCopy() + .withStyle(s -> s.withColor(HEADER_RGB))); + int min = Math.min(this.max - 16, state - 7); + int max = Math.max(this.min + 16, state + 8); + min = Math.max(min, this.min); + max = Math.min(max, this.max); + if (this.min + 1 == min) + min--; + if (min > this.min) + toolTip.add(Components.literal("> ...").withStyle(ChatFormatting.GRAY)); + if (this.max - 1 == max) + max++; + for (int i = min; i < max; i++) { + if (i == state) + toolTip.add(Components.empty() + .append("-> ") + .append(options.get(i)) + .withStyle(ChatFormatting.WHITE)); + else + toolTip.add(Components.empty() + .append("> ") + .append(options.get(i)) + .withStyle(ChatFormatting.GRAY)); + } + if (max < this.max) + toolTip.add(Components.literal("> ...").withStyle(ChatFormatting.GRAY)); + + toolTip.add(scrollToSelect.plainCopy() + .withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.ITALIC)); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/TooltipArea.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/TooltipArea.java new file mode 100644 index 0000000..7bcdf26 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/gui/widget/TooltipArea.java @@ -0,0 +1,25 @@ +package nl.requios.effortlessbuilding.create.foundation.gui.widget; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.network.chat.Component; + +import java.util.List; + +public class TooltipArea extends AbstractSimiWidget { + + public TooltipArea(int x, int y, int width, int height) { + super(x, y, width, height); + } + + @Override + public void renderButton(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + if (visible) + isHovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height; + } + + public TooltipArea withTooltip(List tooltip) { + this.toolTip = tooltip; + return this; + } + +} diff --git a/src/main/resources/assets/effortlessbuilding/textures/gui/display_link.png b/src/main/resources/assets/effortlessbuilding/textures/gui/display_link.png new file mode 100644 index 0000000..35c88f4 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/gui/display_link.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/gui/icons.png b/src/main/resources/assets/effortlessbuilding/textures/gui/icons.png new file mode 100644 index 0000000..7e06ef7 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/gui/icons.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/gui/license.txt b/src/main/resources/assets/effortlessbuilding/textures/gui/license.txt new file mode 100644 index 0000000..9c5be0c --- /dev/null +++ b/src/main/resources/assets/effortlessbuilding/textures/gui/license.txt @@ -0,0 +1,12 @@ +display_link.png, icons.png and widgets.png within this folder fall under the MIT license. + +The MIT License Copyright (c) Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/main/resources/assets/effortlessbuilding/textures/gui/widgets.png b/src/main/resources/assets/effortlessbuilding/textures/gui/widgets.png new file mode 100644 index 0000000..1ae22f0 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/gui/widgets.png differ