diff --git a/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java b/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java index b644a5a9f..067d41ed8 100644 --- a/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java +++ b/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java @@ -38,4 +38,7 @@ public class ModularUIConfig { @Config.RequiresMcRestart @Config.Comment("Enables a test overlay shown on title screen and watermark shown on every GuiContainer.") public static boolean enableTestOverlays = false; + + @Config.Comment("If true, vanilla tooltip will be replaced with MUI's RichTooltip") + public static boolean replaceVanillaTooltips = false; } diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/GuiUtilsMixin.java b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiUtilsMixin.java new file mode 100644 index 000000000..a3e3bca0d --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/GuiUtilsMixin.java @@ -0,0 +1,41 @@ +package com.cleanroommc.modularui.core.mixin; + +import com.cleanroommc.modularui.ModularUIConfig; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.client.config.GuiUtils; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(value = GuiUtils.class, remap = false) +public class GuiUtilsMixin { + + @Inject(method = "drawHoveringText(Lnet/minecraft/item/ItemStack;Ljava/util/List;IIIIILnet/minecraft/client/gui/FontRenderer;)V", + at = @At("HEAD"), cancellable = true) + private static void postRichTooltipEvent(ItemStack stack, List textLines, int x, int y, int w, int h, int maxTextWidth, FontRenderer font, CallbackInfo ci) { + if (ModularUIConfig.replaceVanillaTooltips && !textLines.isEmpty()) { + RichTooltip tooltip = new RichTooltip(); + tooltip.parent(area -> RichTooltip.findIngredientArea(area, x, y)); + // Other positions don't really work due to the lack of GuiContext in non-modular uis + tooltip.add(textLines.get(0)).newLine(); + if (!stack.isEmpty()) { + tooltip.spaceLine(); + } + for (int i = 1, n = textLines.size(); i < n; i++) { + tooltip.add(textLines.get(i)).newLine(); + } + + tooltip.draw(GuiContext.getDefault(), stack); + // Canceling vanilla tooltip rendering + ci.cancel(); + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java b/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java index 9e7ad75a0..caaabcd23 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java @@ -1,6 +1,8 @@ package com.cleanroommc.modularui.drawable; import com.cleanroommc.modularui.drawable.text.TextRenderer; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.RichTooltipEvent; import com.cleanroommc.modularui.utils.Color; import net.minecraft.client.Minecraft; @@ -19,6 +21,7 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; import java.util.List; @@ -592,12 +595,17 @@ public static void drawText(String text, float x, float y, float scale, int colo GlStateManager.enableBlend(); } - public static void drawTooltipBackground(ItemStack stack, List lines, int x, int y, int textWidth, int height) { + public static void drawTooltipBackground(ItemStack stack, List lines, int x, int y, int textWidth, int height, @Nullable RichTooltip tooltip) { // TODO theme color int backgroundColor = 0xF0100010; int borderColorStart = 0x505000FF; int borderColorEnd = (borderColorStart & 0xFEFEFE) >> 1 | borderColorStart & 0xFF000000; - RenderTooltipEvent.Color colorEvent = new RenderTooltipEvent.Color(stack, lines, x, y, TextRenderer.getFontRenderer(), backgroundColor, borderColorStart, borderColorEnd); + RenderTooltipEvent.Color colorEvent; + if (tooltip != null) { + colorEvent = new RichTooltipEvent.Color(stack, lines, x, y, TextRenderer.getFontRenderer(), backgroundColor, borderColorStart, borderColorEnd, tooltip); + } else { + colorEvent = new RenderTooltipEvent.Color(stack, lines, x, y, TextRenderer.getFontRenderer(), backgroundColor, borderColorStart, borderColorEnd); + } MinecraftForge.EVENT_BUS.post(colorEvent); backgroundColor = colorEvent.getBackground(); borderColorStart = colorEvent.getBorderStart(); diff --git a/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java b/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java index dcb88549a..69394633e 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java @@ -41,7 +41,7 @@ public Area getRenderedArea() { @Override public @NotNull RichTooltip tooltip() { - if (this.tooltip == null) this.tooltip = new RichTooltip(area -> area.set(getRenderedArea())); + if (this.tooltip == null) this.tooltip = new RichTooltip().parent(area -> area.set(getRenderedArea())); return tooltip; } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java b/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java index 21ee86a02..12c697d3c 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java @@ -5,6 +5,8 @@ import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.utils.TooltipLines; + import net.minecraft.client.gui.FontRenderer; import java.util.ArrayList; @@ -15,6 +17,7 @@ public class RichText implements IDrawable, IRichTextBuilder { private static final TextRenderer renderer = new TextRenderer(); private final List elements = new ArrayList<>(); + private TooltipLines stringList; private Alignment alignment = Alignment.CenterLeft; private float scale = 1f; private Integer color = null; @@ -26,35 +29,17 @@ public boolean isEmpty() { return this.elements.isEmpty(); } - public List getStringRepresentation() { - List list = new ArrayList<>(); - StringBuilder builder = new StringBuilder(); - for (Object o : this.elements) { - if (o == IKey.LINE_FEED) { - list.add(builder.toString()); - builder.delete(0, builder.length()); - continue; - } - String s = null; - if (o instanceof IKey key) { - s = key.get(); - } else if (o instanceof String s1) { - s = s1; - } else if (o instanceof TextIcon ti) { - s = ti.getText(); - } - if (s != null) { - for (String part : s.split("\n")) { - builder.append(part); - list.add(builder.toString()); - builder.delete(0, builder.length()); - } - } + public List getAsStrings() { + if (this.stringList == null) { + this.stringList = new TooltipLines(this.elements); } - if (!list.isEmpty() && list.get(list.size() - 1).isEmpty()) { - list.remove(list.size() - 1); + return this.stringList; + } + + private void clearStrings() { + if (this.stringList != null) { + this.stringList.clearCache(); } - return list; } public int getMinWidth() { @@ -95,6 +80,7 @@ public IRichTextBuilder getRichText() { public RichText add(String s) { this.elements.add(s); + clearStrings(); return this; } @@ -103,18 +89,21 @@ public RichText add(IDrawable drawable) { Object o = drawable; if (!(o instanceof IKey) && !(o instanceof IIcon)) o = drawable.asIcon(); this.elements.add(o); + clearStrings(); return this; } @Override public RichText addLine(ITextLine line) { this.elements.add(line); + clearStrings(); return this; } @Override public RichText clearText() { this.elements.clear(); + clearStrings(); return this; } @@ -154,6 +143,7 @@ public RichText insertTitleMargin(int margin) { } else { objects.add(i + 1, Spacer.of(margin)); } + clearStrings(); return this; } } @@ -201,4 +191,14 @@ public Object getHoveringElement(FontRenderer fr, int x, int y) { } return null; } + + public RichText copy() { + RichText copy = new RichText(); + copy.elements.addAll(this.elements); + copy.alignment = this.alignment; + copy.scale = this.scale; + copy.color = this.color; + copy.shadow = this.shadow; + return copy; + } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/RichTooltip.java b/src/main/java/com/cleanroommc/modularui/screen/RichTooltip.java index 08c44ed06..8b8e4015c 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/RichTooltip.java +++ b/src/main/java/com/cleanroommc/modularui/screen/RichTooltip.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.screen; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.MCHelper; @@ -8,17 +9,23 @@ import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.drawable.text.RichText; import com.cleanroommc.modularui.drawable.text.TextRenderer; +import com.cleanroommc.modularui.integration.jei.ModularUIJeiPlugin; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.utils.MathUtils; import com.cleanroommc.modularui.widget.sizer.Area; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import net.minecraft.util.math.MathHelper; -import net.minecraftforge.client.event.RenderTooltipEvent; import net.minecraftforge.common.MinecraftForge; +import mezz.jei.input.IClickedIngredient; +import mezz.jei.input.IShowsRecipeFocuses; import org.jetbrains.annotations.Nullable; import java.awt.*; @@ -30,8 +37,8 @@ public class RichTooltip implements IRichTextBuilder { private static final Area HOLDER = new Area(); - private final Consumer parent; private final RichText text = new RichText(); + private Consumer parent; private Pos pos = null; private Consumer tooltipBuilder; private int showUpTimer = 0; @@ -44,23 +51,28 @@ public class RichTooltip implements IRichTextBuilder { private boolean dirty; - public RichTooltip(IWidget parent) { - this(area -> { - area.setSize(parent.getArea()); - area.setPos(0, 0); - }); + public RichTooltip() { + parent(Area.ZERO); } - public RichTooltip(Area parent) { - this(area -> area.set(parent)); + public RichTooltip parent(Consumer parent) { + this.parent = parent; + return this; } - public RichTooltip(Supplier parent) { - this(area -> area.set(parent.get())); + public RichTooltip parent(Supplier parent) { + return parent(area -> area.set(parent.get())); } - public RichTooltip(Consumer parent) { - this.parent = parent; + public RichTooltip parent(Area parent) { + return parent(area -> area.set(parent)); + } + + public RichTooltip parent(IWidget parent) { + return parent(area -> { + area.setPos(0, 0); + area.setSize(parent.getArea()); + }); } public void buildTooltip() { @@ -94,9 +106,9 @@ public void draw(GuiContext context, @Nullable ItemStack stack) { this.maxWidth = Math.min(this.maxWidth, screen.width); int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); TextRenderer renderer = TextRenderer.SHARED; + RichText copy = this.text.copy(); // this only turns the text and not any drawables into strings - List textLines = this.text.getStringRepresentation(); - RenderTooltipEvent.Pre event = new RenderTooltipEvent.Pre(stack, textLines, mouseX, mouseY, screen.width, screen.height, this.maxWidth, TextRenderer.getFontRenderer()); + RichTooltipEvent.Pre event = new RichTooltipEvent.Pre(stack, copy.getAsStrings(), mouseX, mouseY, screen.width, screen.height, this.maxWidth, TextRenderer.getFontRenderer(), copy); if (MinecraftForge.EVENT_BUS.post(event)) return; // canceled // we are supposed to now use the strings of the event, but we can't properly determine where to put them mouseX = event.getX(); @@ -105,10 +117,15 @@ public void draw(GuiContext context, @Nullable ItemStack stack) { this.maxWidth = event.getMaxWidth(); // simulate to figure how big this tooltip is without any restrictions - this.text.setupRenderer(renderer, 0, 0, this.maxWidth, -1, Color.WHITE.main, false); - this.text.compileAndDraw(renderer, context, true); + copy.setupRenderer(renderer, 0, 0, this.maxWidth, -1, Color.WHITE.main, false); + copy.compileAndDraw(renderer, context, true); - Rectangle area = determineTooltipArea(context, renderer, screenWidth, screenHeight, mouseX, mouseY); + Rectangle area = determineTooltipArea(copy, context, renderer, screenWidth, screenHeight, mouseX, mouseY); + renderer.setPos(area.x, area.y); + renderer.setAlignment(copy.getAlignment(), area.width, -1); + copy.compileAndDraw(renderer, context, true); + area.width = (int) renderer.getLastWidth(); + area.height = (int) renderer.getLastHeight(); GlStateManager.disableRescaleNormal(); RenderHelper.disableStandardItemLighting(); @@ -116,21 +133,24 @@ public void draw(GuiContext context, @Nullable ItemStack stack) { GlStateManager.disableDepth(); GlStateManager.disableBlend(); - GuiDraw.drawTooltipBackground(stack, textLines, area.x, area.y, area.width, area.height); + GuiDraw.drawTooltipBackground(stack, copy.getAsStrings(), area.x, area.y, area.width, area.height, this); - MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostBackground(stack, textLines, area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height)); + MinecraftForge.EVENT_BUS.post(new RichTooltipEvent.PostBackground(stack, copy.getAsStrings(), area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height, copy)); GlStateManager.color(1f, 1f, 1f, 1f); renderer.setPos(area.x, area.y); - this.text.compileAndDraw(renderer, context, false); + copy.compileAndDraw(renderer, context, false); - MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostText(stack, textLines, area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height)); + MinecraftForge.EVENT_BUS.post(new RichTooltipEvent.PostText(stack, copy.getAsStrings(), area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height, copy)); } - public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, int screenWidth, int screenHeight, int mouseX, int mouseY) { + public Rectangle determineTooltipArea(RichText text, GuiContext context, TextRenderer renderer, int screenWidth, int screenHeight, int mouseX, int mouseY) { int width = (int) renderer.getLastWidth(); int height = (int) renderer.getLastHeight(); + if (width > screenWidth - 14) { + width = screenWidth - 14; + } Pos pos = this.pos; if (pos == null) { @@ -141,6 +161,12 @@ public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, return new Rectangle(this.x, this.y, width, height); } + Area area = HOLDER; + this.parent.accept(area); + if (area.x == 0 && area.y == 0 && area.width == 0 && area.height == 0) { + pos = Pos.NEXT_TO_MOUSE; + } + if (pos == Pos.NEXT_TO_MOUSE) { // vanilla style, tooltip floats next to mouse // note that this behaves slightly different from vanilla (better imo) @@ -149,7 +175,7 @@ public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, final int mouseOffset = 12; int x = mouseX + mouseOffset, y = mouseY - mouseOffset; if (x < padding) { - x = padding; // this cant happen mathematically since mouse is always positive + x = padding; // this can't happen mathematically since mouse is always positive } else if (x + width + padding > screenWidth) { // doesn't fit on the right side of the screen if (screenWidth - mouseX < mouseX) { // check if left side has more space @@ -157,14 +183,14 @@ public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, if (x < padding) { x = padding; // went of screen } - width = mouseX - 12 - x; // max space on left side + width = mouseX - mouseOffset - padding; // max space on left side } else { width = screenWidth - padding - x; // max space on right side } // recalculate with and height renderer.setPos(x, y); - renderer.setAlignment(this.text.getAlignment(), width, -1); - this.text.compileAndDraw(renderer, context, true); + renderer.setAlignment(text.getAlignment(), width, -1); + text.compileAndDraw(renderer, context, true); width = (int) renderer.getLastWidth(); height = (int) renderer.getLastHeight(); } @@ -177,30 +203,16 @@ public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, throw new IllegalStateException("Tooltip pos is " + pos.name() + ", but no widget parent is set!"); } - int minWidth = this.text.getMinWidth(); + int minWidth = text.getMinWidth(); int shiftAmount = 10; int padding = 7; - Area area = HOLDER; - this.parent.accept(area); area.transformAndRectanglerize(context); int x = 0, y = 0; if (pos.axis.isVertical()) { // above or below - if (width < area.width) { - x = area.x + shiftAmount; - } else { - x = area.x - shiftAmount; - if (x < padding) { - x = padding; - } else if (x + width > screenWidth - padding) { - int maxWidth = Math.max(minWidth, screenWidth - x - padding); - renderer.setAlignment(this.text.getAlignment(), maxWidth); - this.text.compileAndDraw(renderer, context, true); - width = (int) renderer.getLastWidth(); - height = (int) renderer.getLastHeight(); - } - } + x = area.x + (width < area.width ? shiftAmount : -shiftAmount); + x = MathUtils.clamp(x, padding, screenWidth - padding - width); if (pos == Pos.VERTICAL) { int bottomSpace = screenHeight - area.ey(); @@ -242,8 +254,8 @@ public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, maxWidth = Math.max(minWidth, screenWidth - area.ex() - padding * 2); } usedMoreSpaceSide = true; - renderer.setAlignment(this.text.getAlignment(), maxWidth); - this.text.compileAndDraw(renderer, context, true); + renderer.setAlignment(text.getAlignment(), maxWidth); + text.compileAndDraw(renderer, context, true); width = (int) renderer.getLastWidth(); height = (int) renderer.getLastHeight(); } @@ -257,6 +269,10 @@ public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, x = area.ex() + padding; } else if (pos == Pos.LEFT) { x = area.x - width - padding; + if (x < padding) { + x = padding; + width = area.x - x - padding; + } } } return new Rectangle(x, y, width, height); @@ -344,6 +360,35 @@ public RichTooltip titleMargin(int margin) { return this; } + public static void findIngredientArea(Area area, int x, int y) { + GuiScreen screen = MCHelper.getCurrentScreen(); + if (screen instanceof GuiContainer guiContainer) { + Slot slot = guiContainer.getSlotUnderMouse(); + if (slot != null) { + int sx = slot.xPos + guiContainer.getGuiLeft(); + int sy = slot.yPos + guiContainer.getGuiTop(); + if (sx >= 0 && sy >= 0) { + area.set(sx - 1, sy - 1, 18, 18); + return; + } + } + } + if (ModularUI.isJeiLoaded()) { + IShowsRecipeFocuses overlay = (IShowsRecipeFocuses) ModularUIJeiPlugin.getRuntime().getIngredientListOverlay(); + IClickedIngredient ingredient = overlay.getIngredientUnderMouse(x, y); + if (ingredient == null || ingredient.getArea() == null) { + overlay = (IShowsRecipeFocuses) ModularUIJeiPlugin.getRuntime().getBookmarkOverlay(); + ingredient = overlay.getIngredientUnderMouse(x, y); + } + if (ingredient != null && ingredient.getArea() != null) { + Rectangle slot = ingredient.getArea(); + area.set(slot.x - 1, slot.y - 1, 18, 18); + return; + } + } + area.set(Area.ZERO); + } + public enum Pos { ABOVE(GuiAxis.Y), diff --git a/src/main/java/com/cleanroommc/modularui/screen/RichTooltipEvent.java b/src/main/java/com/cleanroommc/modularui/screen/RichTooltipEvent.java new file mode 100644 index 000000000..452f5c75e --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/screen/RichTooltipEvent.java @@ -0,0 +1,79 @@ +package com.cleanroommc.modularui.screen; + +import com.cleanroommc.modularui.api.drawable.IRichTextBuilder; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.item.ItemStack; +import net.minecraftforge.client.event.RenderTooltipEvent; +import net.minecraftforge.fml.common.eventhandler.Cancelable; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class RichTooltipEvent { + + private RichTooltipEvent () {} + + @Cancelable + public static class Pre extends RenderTooltipEvent.Pre { + + private final IRichTextBuilder tooltip; + + public Pre(@NotNull ItemStack stack, @NotNull List lines, int x, int y, int screenWidth, int screenHeight, int maxWidth, + @NotNull FontRenderer fr, IRichTextBuilder tooltip) { + super(stack, lines, x, y, screenWidth, screenHeight, maxWidth, fr); + this.tooltip = tooltip; + } + + public IRichTextBuilder getTooltip() { + return tooltip; + } + } + + public static class Color extends RenderTooltipEvent.Color { + + private final IRichTextBuilder tooltip; + + public Color(@NotNull ItemStack stack, @NotNull List lines, int x, int y, + @NotNull FontRenderer fr, int background, int borderStart, + int borderEnd, IRichTextBuilder tooltip) { + super(stack, lines, x, y, fr, background, borderStart, borderEnd); + this.tooltip = tooltip; + } + + public IRichTextBuilder getTooltip() { + return tooltip; + } + } + + public static class PostBackground extends RenderTooltipEvent.PostBackground { + + private final IRichTextBuilder tooltip; + + public PostBackground(@NotNull ItemStack stack, @NotNull List lines, int x, int y, + @NotNull FontRenderer fr, int width, int height, IRichTextBuilder tooltip) { + super(stack, lines, x, y, fr, width, height); + this.tooltip = tooltip; + } + + public IRichTextBuilder getTooltip() { + return tooltip; + } + } + + public static class PostText extends RenderTooltipEvent.PostText { + + private final IRichTextBuilder tooltip; + + public PostText(@NotNull ItemStack stack, @NotNull List lines, int x, int y, + @NotNull FontRenderer fr, int width, int height, IRichTextBuilder tooltip) { + super(stack, lines, x, y, fr, width, height); + this.tooltip = tooltip; + } + + public IRichTextBuilder getTooltip() { + return tooltip; + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java b/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java deleted file mode 100644 index 0c751cbcf..000000000 --- a/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java +++ /dev/null @@ -1,381 +0,0 @@ -package com.cleanroommc.modularui.screen; - -import com.cleanroommc.modularui.ModularUIConfig; -import com.cleanroommc.modularui.api.drawable.IDrawable; -import com.cleanroommc.modularui.api.drawable.IIcon; -import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.drawable.GuiDraw; -import com.cleanroommc.modularui.drawable.Icon; -import com.cleanroommc.modularui.drawable.IconRenderer; -import com.cleanroommc.modularui.drawable.text.TextIcon; -import com.cleanroommc.modularui.drawable.text.TextRenderer; -import com.cleanroommc.modularui.screen.viewport.GuiContext; -import com.cleanroommc.modularui.utils.Alignment; -import com.cleanroommc.modularui.utils.Color; -import com.cleanroommc.modularui.widget.sizer.Area; - -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.MathHelper; -import net.minecraftforge.client.event.RenderTooltipEvent; -import net.minecraftforge.common.MinecraftForge; - -import org.jetbrains.annotations.Nullable; - -import java.awt.*; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -@Deprecated -public class Tooltip { - - private final IWidget parent; - private final List lines = new ArrayList<>(); - private List additionalLines = new ArrayList<>(); - private RichTooltip.Pos pos = null; - private Consumer tooltipBuilder; - private int showUpTimer = 0; - - private int x = 0, y = 0; - private int maxWidth = Integer.MAX_VALUE; - private boolean textShadow = true; - private int textColor = Color.WHITE.main; - private float scale = 1.0f; - private Alignment alignment = Alignment.TopLeft; - private boolean autoUpdate = false; - private boolean hasTitleMargin = true; - private int linePadding = 1; - - private boolean dirty = true; - - public Tooltip(IWidget parent) { - this.parent = parent; - } - - public void buildTooltip() { - this.dirty = false; - this.lines.clear(); - List additionalLines = this.additionalLines; - this.additionalLines = this.lines; - if (this.tooltipBuilder != null) { - this.tooltipBuilder.accept(this); - } - this.lines.addAll(additionalLines); - this.additionalLines = additionalLines; - if (this.hasTitleMargin && this.lines.size() > 1) { - this.lines.add(1, Icon.EMPTY_2PX); - } - } - - public void draw(GuiContext context) { - draw(context, ItemStack.EMPTY); - } - - public void draw(GuiContext context, @Nullable ItemStack stack) { - if (this.autoUpdate) { - markDirty(); - } - if (isEmpty()) return; - - if (this.maxWidth <= 0) { - this.maxWidth = Integer.MAX_VALUE; - } - if (stack == null) stack = ItemStack.EMPTY; - Area screen = context.getScreenArea(); - int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); - IconRenderer renderer = IconRenderer.SHARED; - List textLines = lines.stream().filter(drawable -> drawable instanceof IKey).map(key -> ((IKey) key).get()).collect(Collectors.toList()); - RenderTooltipEvent.Pre event = new RenderTooltipEvent.Pre(stack, textLines, mouseX, mouseY, screen.width, screen.height, this.maxWidth, TextRenderer.getFontRenderer()); - if (MinecraftForge.EVENT_BUS.post(event)) { - return; - } - //lines = event.getLines(); - mouseX = event.getX(); - mouseY = event.getY(); - int screenWidth = event.getScreenWidth(), screenHeight = event.getScreenHeight(); - this.maxWidth = event.getMaxWidth(); - - renderer.setShadow(this.textShadow); - renderer.setColor(this.textColor); - renderer.setScale(this.scale); - renderer.setAlignment(this.alignment, this.maxWidth); - renderer.setLinePadding(this.linePadding); - renderer.setSimulate(true); - renderer.setPos(0, 0); - - //List icons = renderer.measureLines(this.lines); - renderer.draw(context, this.lines); - - Rectangle area = determineTooltipArea(context, this.lines, renderer, screenWidth, screenHeight, mouseX, mouseY); - - GlStateManager.disableRescaleNormal(); - RenderHelper.disableStandardItemLighting(); - GlStateManager.disableLighting(); - GlStateManager.disableDepth(); - GlStateManager.disableBlend(); - - GuiDraw.drawTooltipBackground(stack, textLines, area.x, area.y, area.width, area.height); - - MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostBackground(stack, textLines, area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height)); - - GlStateManager.color(1f, 1f, 1f, 1f); - - renderer.setSimulate(false); - //renderer.setAlignment(Alignment.TopLeft, area.width, area.height); - renderer.setPos(area.x, area.y); - renderer.draw(context, this.lines); - - MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostText(stack, textLines, area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height)); - } - - public Rectangle determineTooltipArea(GuiContext context, List lines, IconRenderer renderer, int screenWidth, int screenHeight, int mouseX, int mouseY) { - int width = (int) renderer.getLastWidth(); - int height = (int) renderer.getLastHeight(); - - RichTooltip.Pos pos = this.pos; - if (pos == null) { - pos = context.isMuiContext() ? context.getMuiContext().getScreen().getCurrentTheme().getTooltipPosOverride() : null; - if (pos == null) pos = ModularUIConfig.tooltipPos; - } - if (pos == RichTooltip.Pos.FIXED) { - return new Rectangle(this.x, this.y, width, height); - } - - if (pos == RichTooltip.Pos.NEXT_TO_MOUSE) { - final int padding = 8; - // magic number to place tooltip nicer. Look at GuiScreen#L237 - final int mouseOffset = 12; - int x = mouseX + mouseOffset, y = mouseY - mouseOffset; - if (x < padding) { - x = padding; - } else if (x + width + padding > screenWidth) { - x -= mouseOffset * 2 + width; // flip side of cursor - if (x < padding) { - x = padding; - } - } - y = MathHelper.clamp(y, padding, screenHeight - padding - height); - return new Rectangle(x, y, width, height); - } - - if (this.parent == null) { - throw new IllegalStateException("Tooltip pos is " + this.pos.name() + ", but no widget parent is set!"); - } - - int minWidth = 0; - for (IDrawable line : lines) { - if (line instanceof IIcon icon && !(line instanceof TextIcon)) { - minWidth = Math.max(minWidth, icon.getWidth()); - } else if (!(line instanceof IKey)) { - minWidth = Math.max(minWidth, 18); - } - } - - int shiftAmount = 10; - int padding = 7; - - Area area = Area.SHARED; - area.set(this.parent.getArea()); - area.setPos(0, 0); // context is transformed to this widget - area.transformAndRectanglerize(context); - int x = 0, y = 0; - if (pos.axis.isVertical()) { - if (width < area.width) { - x = area.x + shiftAmount; - } else { - x = area.x - shiftAmount; - if (x < padding) { - x = padding; - } else if (x + width > screenWidth - padding) { - int maxWidth = Math.max(minWidth, screenWidth - x - padding); - renderer.setAlignment(this.alignment, maxWidth); - renderer.draw(context, lines); - width = (int) renderer.getLastWidth(); - height = (int) renderer.getLastHeight(); - } - } - - RichTooltip.Pos pos1 = pos; - if (pos == RichTooltip.Pos.VERTICAL) { - int bottomSpace = screenHeight - area.ey(); - pos1 = bottomSpace < height + padding && bottomSpace < area.y ? RichTooltip.Pos.ABOVE : RichTooltip.Pos.BELOW; - } - - if (pos1 == RichTooltip.Pos.BELOW) { - y = area.ey() + padding; - } else if (pos1 == RichTooltip.Pos.ABOVE) { - y = area.y - height - padding; - } - } else if (pos.axis.isHorizontal()) { - boolean usedMoreSpaceSide = false; - RichTooltip.Pos pos1 = pos; - if (pos == RichTooltip.Pos.HORIZONTAL) { - if (area.x > screenWidth - area.ex()) { - pos1 = RichTooltip.Pos.LEFT; - // x = 0; - } else { - pos1 = RichTooltip.Pos.RIGHT; - x = screenWidth - area.ex() + padding; - } - } - - if (height < area.height) { - y = area.y + shiftAmount; - } else { - y = area.y - shiftAmount; - if (y < padding) { - y = padding; - } - } - - if (x + width > screenWidth - padding) { - int maxWidth; - if (pos1 == RichTooltip.Pos.LEFT) { - maxWidth = Math.max(minWidth, area.x - padding * 2); - } else { - maxWidth = Math.max(minWidth, screenWidth - area.ex() - padding * 2); - } - usedMoreSpaceSide = true; - renderer.setAlignment(this.alignment, maxWidth); - renderer.draw(context, lines); - width = (int) renderer.getLastWidth(); - height = (int) renderer.getLastHeight(); - } - - if (pos == RichTooltip.Pos.HORIZONTAL && !usedMoreSpaceSide) { - int rightSpace = screenWidth - area.ex(); - pos1 = rightSpace < width + padding && rightSpace < area.x ? RichTooltip.Pos.LEFT : RichTooltip.Pos.RIGHT; - } - - if (pos1 == RichTooltip.Pos.RIGHT) { - x = area.ex() + padding; - } else if (pos1 == RichTooltip.Pos.LEFT) { - x = area.x - width - padding; - } - } - return new Rectangle(x, y, width, height); - } - - public boolean isEmpty() { - if (this.dirty) { - buildTooltip(); - } - return this.lines.isEmpty(); - } - - public void markDirty() { - this.dirty = true; - } - - public int getShowUpTimer() { - return this.showUpTimer; - } - - @Nullable - public Consumer getTooltipBuilder() { - return this.tooltipBuilder; - } - - public boolean isAutoUpdate() { - return this.autoUpdate; - } - - public boolean hasTitleMargin() { - return this.hasTitleMargin; - } - - public Tooltip pos(RichTooltip.Pos pos) { - this.pos = pos; - return this; - } - - public Tooltip pos(int x, int y) { - this.pos = RichTooltip.Pos.FIXED; - this.x = x; - this.y = y; - return this; - } - - public Tooltip alignment(Alignment alignment) { - this.alignment = alignment; - return this; - } - - public Tooltip textShadow(boolean textShadow) { - this.textShadow = textShadow; - return this; - } - - public Tooltip textColor(int textColor) { - this.textColor = textColor; - return this; - } - - public Tooltip scale(float scale) { - this.scale = scale; - return this; - } - - public Tooltip showUpTimer(int showUpTimer) { - this.showUpTimer = showUpTimer; - return this; - } - - public Tooltip tooltipBuilder(Consumer tooltipBuilder) { - Consumer existingBuilder = this.tooltipBuilder; - if (existingBuilder != null) { - this.tooltipBuilder = tooltip -> { - existingBuilder.accept(this); - tooltipBuilder.accept(this); - }; - } else { - this.tooltipBuilder = tooltipBuilder; - } - return this; - } - - public Tooltip setAutoUpdate(boolean update) { - this.autoUpdate = update; - return this; - } - - public Tooltip setHasTitleMargin(boolean hasTitleMargin) { - this.hasTitleMargin = hasTitleMargin; - return this; - } - - /** - * By default, tooltips have 1px of space between lines. Set to 0 if you want to disable it. - */ - public Tooltip setLinePadding(int linePadding) { - this.linePadding = linePadding; - return this; - } - - public Tooltip addLine(IDrawable drawable) { - this.additionalLines.add(drawable); - return this; - } - - public Tooltip addLine(String line) { - return addLine(IKey.str(line)); - } - - public Tooltip addDrawableLines(Iterable lines) { - for (IDrawable line : lines) { - addLine(line); - } - return this; - } - - public Tooltip addStringLines(Iterable lines) { - for (String line : lines) { - addLine(IKey.str(line)); - } - return this; - } -} diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index 32565ba9d..b0e589343 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -1,8 +1,13 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.factory.ClientGUI; +import com.cleanroommc.modularui.screen.RichTooltipEvent; + import net.minecraft.init.Items; +import net.minecraft.util.text.TextFormatting; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -20,4 +25,11 @@ public static void onItemUse(PlayerInteractEvent.RightClickItem event) { ClientGUI.open(new TestGuis()); } } + + @SubscribeEvent + public static void onRichTooltip(RichTooltipEvent.Pre event) { + event.getTooltip() + .add(IKey.str("Powered By: ").style(TextFormatting.GOLD, TextFormatting.ITALIC)) + .add(GuiTextures.MUI_LOGO.asIcon().size(18)).newLine(); + } } diff --git a/src/main/java/com/cleanroommc/modularui/utils/TooltipLines.java b/src/main/java/com/cleanroommc/modularui/utils/TooltipLines.java new file mode 100644 index 000000000..23b128986 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/utils/TooltipLines.java @@ -0,0 +1,138 @@ +package com.cleanroommc.modularui.utils; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.text.TextIcon; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +public class TooltipLines extends AbstractList { + + private final List elements; + private final List lines = new ArrayList<>(8); + private int lastElementIndex = 0; + + public TooltipLines(List elements) { + this.elements = elements; + } + + public void clearCache() { + this.lines.clear(); + this.lastElementIndex = 0; + } + + private void buildUntil(int index) { + while (index >= lines.size()) { + Line line = parseNext(); + if (line == null) break; + lines.add(line); + } + } + + private Line parseNext() { + if (this.lastElementIndex >= elements.size()) return null; + StringBuilder currentLine = new StringBuilder(); + int currentLength = 0; + for (int i = this.lastElementIndex; i < this.elements.size(); i++) { + Object o = elements.get(i); + currentLength++; + if (o == IKey.LINE_FEED) { + Line line = new Line(currentLine.toString(), this.lastElementIndex, currentLength); + this.lastElementIndex += currentLength; + return line; + } + String s = null; + if (o instanceof IKey key) { + s = key.get(); + } else if (o instanceof String s1) { + s = s1; + } else if (o instanceof TextIcon ti) { + s = ti.getText(); + } + if (s != null) { + currentLine.append(s); + } + } + if (currentLength > 0) { + Line line = new Line(currentLine.toString(), this.lastElementIndex, currentLength); + this.lastElementIndex += currentLength; + return line; + } + return null; + } + + @Override + public String get(int index) { + buildUntil(index); + return lines.get(index).text; + } + + @Override + public int size() { + buildUntil(Integer.MAX_VALUE); + return lines.size(); + } + + @Override + public String remove(int index) { + buildUntil(index); + Line line = lines.remove(index); + + if (line.length == 1) { + this.elements.remove(line.index); + } else { + this.elements.subList(line.index, line.index + line.length).clear(); + } + for (int i = index; i < lines.size(); i++) { + lines.get(i).index -= line.length; + } + this.lastElementIndex -= line.length; + + return line.text; + } + + @Override + public void add(int index, String s) { + buildUntil(index); + int elementIndex = index >= this.lines.size() ? this.lastElementIndex : this.lines.get(index).index; + lines.add(index, new Line(s, elementIndex, 1)); + for (int i = index + 1; i < this.lines.size(); i++) { + lines.get(i).index++; + } + this.elements.add(elementIndex, s); + this.lastElementIndex++; + } + + @Override + public String set(int index, String element) { + Line line = lines.get(index); + if (line.length == 1) { + this.elements.set(line.index, element); + this.lines.set(index, new Line(element, line.index, line.length)); + } else { + remove(index); + add(index, element); + } + return line.text; + } + + @Override + public void clear() { + this.elements.clear(); + this.lines.clear(); + this.lastElementIndex = 0; + } + + private static class Line { + private final String text; + private final int length; + private int index; + + private Line(String text, int index, int length) { + this.text = text; + this.index = index; + this.length = length; + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index ce56c69e0..8c392392f 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -209,7 +209,7 @@ public RichTooltip getTooltip() { @Override public @NotNull RichTooltip tooltip() { if (this.tooltip == null) { - this.tooltip = new RichTooltip(this); + this.tooltip = new RichTooltip().parent(this); } return this.tooltip; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java index 6ecfc6bc6..8294705ca 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -38,8 +38,7 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) { private final Box margin = new Box(); private final Box padding = new Box(); - public Area() { - } + public Area() {} public Area(int x, int y, int w, int h) { super(x, y, w, h); @@ -438,7 +437,7 @@ public void set(Rectangle area) { /** * Transforms the four corners of this rectangle with the given pose stack. The new rectangle can be rotated. - * Then a min fit rectangle, which is not rotated and aligned with the screen, is put around the corner. + * Then a min fit rectangle, which is not rotated and aligned with the screen, is put around the corners. * * @param stack pose stack */ diff --git a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java index bb2df0d5c..6d0069ead 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java @@ -223,7 +223,7 @@ protected W stateCount(int stateCount) { this.stateCount = stateCount; // adjust tooltip buffer size while (this.stateTooltip.size() < this.stateCount) { - this.stateTooltip.add(new RichTooltip(this)); + this.stateTooltip.add(new RichTooltip().parent(this)); } while (this.stateTooltip.size() > this.stateCount) { this.stateTooltip.remove(this.stateTooltip.size() - 1); diff --git a/src/main/resources/mixin.modularui.json b/src/main/resources/mixin.modularui.json index ffd2d6ac3..daaf0a14f 100644 --- a/src/main/resources/mixin.modularui.json +++ b/src/main/resources/mixin.modularui.json @@ -10,6 +10,7 @@ "GuiContainerAccessor", "GuiContainerMixin", "GuiScreenAccessor", + "GuiUtilsMixin", "KeyBindAccess", "MinecraftMixin" ],