diff options
author | Anthony Hilyard <anthony.hilyard@gmail.com> | 2022-01-05 16:34:14 -0800 |
---|---|---|
committer | Anthony Hilyard <anthony.hilyard@gmail.com> | 2022-01-05 16:34:14 -0800 |
commit | f8c05d3ed6f96393f34f4ab661e99d3083f893b6 (patch) | |
tree | 697a0b3719c439513ed03a3260b09bf3168df262 /src/main | |
parent | 2c50ee296e0daf85e6a087865b1d04bcebcbf565 (diff) | |
download | Iceberg-f8c05d3ed6f96393f34f4ab661e99d3083f893b6.tar.gz Iceberg-f8c05d3ed6f96393f34f4ab661e99d3083f893b6.tar.bz2 Iceberg-f8c05d3ed6f96393f34f4ab661e99d3083f893b6.zip |
Ported changes through 1.0.34. Fixed a crash issue for client-side dependants run on dedicated server. Cleaned up warnings in log file. Fixed incorrect background alpha bug.
Diffstat (limited to 'src/main')
7 files changed, 389 insertions, 65 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java index d0693e3..622d3e0 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java +++ b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java @@ -3,24 +3,42 @@ package com.anthonyhilyard.iceberg.events; import java.util.List; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.datafixers.util.Either; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.network.chat.FormattedText; import net.minecraft.world.InteractionResult; +import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; public final class RenderTooltipEvents { public RenderTooltipEvents() { } + public static final Event<RenderTooltipEvents.Gather> GATHER = EventFactory.createArrayBacked(RenderTooltipEvents.Gather.class, + callbacks -> (itemStack, screenWidth, screenHeight, tooltipElements, maxWidth, index) -> { + GatherResult result = new GatherResult(InteractionResult.PASS, maxWidth, tooltipElements); + for (RenderTooltipEvents.Gather callback : callbacks) + { + result = callback.onGather(itemStack, screenWidth, screenHeight, tooltipElements, maxWidth, index); + + if (result.result != InteractionResult.PASS) + { + return result; + } + } + return result; + }); + public static final Event<RenderTooltipEvents.PreExt> PREEXT = EventFactory.createArrayBacked(RenderTooltipEvents.PreExt.class, - callbacks -> (stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison) -> { + callbacks -> (stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison, index) -> { PreExtResult result = new PreExtResult(InteractionResult.PASS, x, y, screenWidth, screenHeight, font); for (RenderTooltipEvents.PreExt callback : callbacks) { - result = callback.onPre(stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison); + result = callback.onPre(stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison, index); if (result.result != InteractionResult.PASS) { @@ -46,11 +64,11 @@ public final class RenderTooltipEvents }); public static final Event<RenderTooltipEvents.ColorExt> COLOREXT = EventFactory.createArrayBacked(RenderTooltipEvents.ColorExt.class, - callbacks -> (stack, components, poseStack, x, y, font, backgroundStart, backgroundEnd, borderStart, borderEnd, comparison) -> { + callbacks -> (stack, components, poseStack, x, y, font, backgroundStart, backgroundEnd, borderStart, borderEnd, comparison, index) -> { ColorExtResult result = new ColorExtResult(backgroundStart, backgroundEnd, borderStart, borderEnd); for (RenderTooltipEvents.ColorExt callback : callbacks) { - result = callback.onColor(stack, components, poseStack, x, y, font, result.backgroundStart, result.backgroundEnd, result.borderStart, result.borderEnd, comparison); + result = callback.onColor(stack, components, poseStack, x, y, font, result.backgroundStart, result.backgroundEnd, result.borderStart, result.borderEnd, comparison, index); } return result; }); @@ -66,6 +84,14 @@ public final class RenderTooltipEvents return result; }); + public static final Event<RenderTooltipEvents.PostExt> POSTEXT = EventFactory.createArrayBacked(RenderTooltipEvents.PostExt.class, + callbacks -> (stack, components, poseStack, x, y, font, width, height, comparison, index) -> { + for (RenderTooltipEvents.PostExt callback : callbacks) + { + callback.onPost(stack, components, poseStack, x, y, font, width, height, comparison, index); + } + }); + public static final Event<RenderTooltipEvents.Post> POST = EventFactory.createArrayBacked(RenderTooltipEvents.Post.class, callbacks -> (stack, components, poseStack, x, y, font, width, height, comparison) -> { for (RenderTooltipEvents.Post callback : callbacks) @@ -75,9 +101,15 @@ public final class RenderTooltipEvents }); @FunctionalInterface + public interface Gather + { + GatherResult onGather(ItemStack itemStack, int screenWidth, int screenHeight, List<Either<FormattedText, TooltipComponent>> tooltipElements, int maxWidth, int index); + } + + @FunctionalInterface public interface PreExt { - PreExtResult onPre(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, int screenWidth, int screenHeight, Font font, boolean comparison); + PreExtResult onPre(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, int screenWidth, int screenHeight, Font font, boolean comparison, int index); } @Deprecated @@ -90,7 +122,7 @@ public final class RenderTooltipEvents @FunctionalInterface public interface ColorExt { - ColorExtResult onColor(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int backgroundStart, int backgroundEnd, int borderStart, int borderEnd, boolean comparison); + ColorExtResult onColor(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int backgroundStart, int backgroundEnd, int borderStart, int borderEnd, boolean comparison, int index); } @Deprecated @@ -101,11 +133,19 @@ public final class RenderTooltipEvents } @FunctionalInterface + public interface PostExt + { + void onPost(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int width, int height, boolean comparison, int index); + } + + @Deprecated + @FunctionalInterface public interface Post { void onPost(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int width, int height, boolean comparison); } + public record GatherResult(InteractionResult result, int maxWidth, List<Either<FormattedText, TooltipComponent>> tooltipElements) {} public record PreExtResult(InteractionResult result, int x, int y, int screenWidth, int screenHeight, Font font) {} public record ColorExtResult(int backgroundStart, int backgroundEnd, int borderStart, int borderEnd) {} public record ColorResult(int background, int borderStart, int borderEnd) {} diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java index 55a97c3..a797c9f 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java @@ -1,10 +1,13 @@ package com.anthonyhilyard.iceberg.mixin; import java.util.List; +import java.util.Optional; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorExtResult; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorResult; +import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult; +import com.anthonyhilyard.iceberg.util.Tooltips; import com.google.common.collect.Lists; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; @@ -26,9 +29,11 @@ import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionResult; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; @Mixin(Screen.class) @@ -40,10 +45,40 @@ public class ScreenMixin extends AbstractContainerEventHandler @Shadow private List<GuiEventListener> children = Lists.newArrayList(); + private ItemStack tooltipStack = ItemStack.EMPTY; + + @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/world/item/ItemStack;II)V", at = @At(value = "HEAD")) + protected void renderTooltipHead(PoseStack poseStack, ItemStack itemStack, int x, int y, CallbackInfo info) + { + tooltipStack = itemStack; + } + + @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/world/item/ItemStack;II)V", at = @At(value = "TAIL")) + protected void renderTooltipTail(PoseStack poseStack, ItemStack itemStack, int x, int y, CallbackInfo info) + { + tooltipStack = ItemStack.EMPTY; + } + + @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;Ljava/util/Optional;II)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderTooltipInternal(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;II)V"), + locals = LocalCapture.CAPTURE_FAILEXCEPTION) + public void renderTooltip(PoseStack poseStack, List<Component> textComponents, Optional<TooltipComponent> itemComponent, int x, int y, CallbackInfo info, List<ClientTooltipComponent> components) + { + Screen self = (Screen)(Object)this; + + List<ClientTooltipComponent> newComponents = Tooltips.gatherTooltipComponents(tooltipStack, textComponents, itemComponent, x, self.width, self.height, null, font, -1); + if (newComponents != null && !newComponents.isEmpty()) + { + components.clear(); + components.addAll(newComponents); + } + } + @SuppressWarnings({"unchecked", "deprecation"}) @Inject(method = "renderTooltipInternal", at = @At(value = "HEAD"), cancellable = true) private void preRenderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info) { + PreExtResult eventResult = null; Screen self = (Screen)(Object)this; if (self instanceof AbstractContainerScreen) { @@ -53,7 +88,9 @@ public class ScreenMixin extends AbstractContainerEventHandler if (hoveredSlot != null) { ItemStack tooltipStack = hoveredSlot.getItem(); - InteractionResult result = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false).result(); + InteractionResult result = InteractionResult.PASS; + eventResult = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false, 0); + result = eventResult.result(); if (result != InteractionResult.PASS) { @@ -100,7 +137,7 @@ public class ScreenMixin extends AbstractContainerEventHandler @SuppressWarnings({"unchecked", "deprecation"}) @Inject(method = "renderTooltipInternal", at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V", shift = Shift.BEFORE), + target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V", ordinal = 0, shift = Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILEXCEPTION) private void preFillGradient(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info, int __, int ___, int left, int top, int width, int height, int background, int borderStart, int borderEnd, @@ -123,7 +160,7 @@ public class ScreenMixin extends AbstractContainerEventHandler int backgroundEnd = background; // Do colors now, sure why not. - ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, background, borderStart, borderEnd, false); + ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, backgroundEnd, borderStart, borderEnd, false, 0); if (result != null) { background = result.backgroundStart(); @@ -141,7 +178,6 @@ public class ScreenMixin extends AbstractContainerEventHandler borderEnd = colorResult.borderEnd(); } - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 4, left + width + 3, top - 3, zIndex, background, background); Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top + height + 3, left + width + 3, top + height + 4, zIndex, backgroundEnd, backgroundEnd); Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3, left + width + 3, top + height + 3, zIndex, background, backgroundEnd); @@ -154,7 +190,7 @@ public class ScreenMixin extends AbstractContainerEventHandler } } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "deprecation"}) @Inject(method = "renderTooltipInternal", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) private void renderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info, int tooltipWidth, int tooltipHeight, int postX, int postY) { @@ -166,6 +202,7 @@ public class ScreenMixin extends AbstractContainerEventHandler if (hoveredSlot != null) { ItemStack tooltipStack = hoveredSlot.getItem(); + RenderTooltipEvents.POSTEXT.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false, 0); RenderTooltipEvents.POST.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false); } } diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java new file mode 100644 index 0000000..02bca31 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java @@ -0,0 +1,36 @@ +package com.anthonyhilyard.iceberg.mixin; + +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.CallbackInfoReturnable; + +import net.minecraft.network.chat.TextColor; + +@Mixin(TextColor.class) +public class TextColorMixin +{ + /** + * Fix an issue in TextColor parsing that makes it so only alpha values up to 0x7F are supported. + */ + @Inject(method = "parseColor", at = @At("HEAD"), cancellable = true) + private static boolean parseColor(String colorString, CallbackInfoReturnable<TextColor> info) + { + if (!colorString.startsWith("#")) + { + return false; + } + + try + { + int i = Integer.parseUnsignedInt(colorString.substring(1), 16); + info.setReturnValue(TextColor.fromRgb(i)); + return true; + } + catch (NumberFormatException numberformatexception) + { + info.setReturnValue(null); + return true; + } + } +}
\ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java b/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java index 6f8de4e..74e4772 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java @@ -1,12 +1,36 @@ package com.anthonyhilyard.iceberg.util; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; import net.minecraft.network.chat.TextColor; +import net.minecraft.util.FormattedCharSink; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +import java.util.List; + import net.minecraft.ChatFormatting; public class ItemColor { + private static class ColorCollector implements FormattedCharSink + { + private TextColor color = null; + + @Override + public boolean accept(int index, Style style, int codePoint) + { + if (style.getColor() != null) + { + color = style.getColor(); + return false; + } + return true; + } + + public TextColor getColor() { return color; } + } + public static TextColor findFirstColorCode(Component textComponent) { // This function finds the first specified color code in the given text component. @@ -20,7 +44,7 @@ public class ItemColor try { ChatFormatting format = ChatFormatting.getByCode(rawTitle.charAt(i + 1)); - if (format.isColor()) + if (format != null && format.isColor()) { return TextColor.fromLegacyFormat(format); } @@ -36,6 +60,7 @@ public class ItemColor return null; } } + return null; } @@ -61,13 +86,32 @@ public class ItemColor result = item.getHoverName().getStyle().getColor(); } - // Finally if there is a color code specified for the item name, use that. - TextColor formattingColor = findFirstColorCode(item.getItem().getName(item)); + // If there is a color code specified for the item name, use that. + TextColor formattingColor = findFirstColorCode(item.getHoverName()); if (formattingColor != null) { result = formattingColor; } + // Finally, if there is a color style stored per-character, use the first one found. + ColorCollector colorCollector = new ColorCollector(); + item.getHoverName().getVisualOrderText().accept(colorCollector); + if (colorCollector.getColor() != null) + { + result = colorCollector.getColor(); + } + + // If we haven't found a color or we're still using the rarity color, check the actual tooltip. + // This is slow, so it better get cached externally! + if (result == null || result.equals(item.getDisplayName().getStyle().getColor())) + { + List<Component> lines = item.getTooltipLines(null, TooltipFlag.Default.ADVANCED); + if (!lines.isEmpty()) + { + result = lines.get(0).getStyle().getColor(); + } + } + // Fallback to the default TextColor if we somehow haven't found a single valid TextColor. if (result == null) { diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java index af5c3af..c097323 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java @@ -3,15 +3,16 @@ package com.anthonyhilyard.iceberg.util; import net.minecraft.core.Registry; import net.minecraft.world.item.ItemStack; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.BiPredicate; import java.util.List; -import net.minecraft.client.Minecraft; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NumericTag; import net.minecraft.nbt.Tag; +import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextColor; import net.minecraft.resources.ResourceLocation; @@ -72,6 +73,25 @@ public class Selectors }); }}; + public static record SelectorDocumentation(String name, String description, List<String> examples) + { + public SelectorDocumentation(String name, String description, String... examples) { this(name, description, Arrays.asList(examples)); } + } + + public static List<SelectorDocumentation> selectorDocumentation() + { + return Arrays.asList( + new SelectorDocumentation("Item name", "Use item name for vanilla items or include mod name for modded items.", "minecraft:stick", "iron_ore"), + new SelectorDocumentation("Tag", "$ followed by tag name.", "$forge:stone", "$planks"), + new SelectorDocumentation("Mod name", "@ followed by mod identifier.", "@spoiledeggs"), + new SelectorDocumentation("Rarity", "! followed by item's rarity. This is ONLY vanilla rarities.", "!uncommon", "!rare", "!epic"), + new SelectorDocumentation("Item name color", "# followed by color hex code, the hex code must match exactly.", "#23F632"), + new SelectorDocumentation("Display name", "% followed by any text. Will match any item with this text in its tooltip display name.", "%Netherite", "%[Uncommon]"), + new SelectorDocumentation("Tooltip text", "Will match any item with this text anywhere in the tooltip text (besides the name).", "^Legendary"), + new SelectorDocumentation("NBT tag", "& followed by tag name and optional comparator (=, >, <, or !=) and value, in the format <tag><comparator><value> or just <tag>.", "&Damage=0", "&Tier>1", "&map!=128", "&Enchantments") + ); + } + /** * Returns true if this selector is syntactically valid. * @param value The selector. @@ -174,8 +194,7 @@ public class Selectors // Tooltip text else if (selector.startsWith("^")) { - Minecraft mc = Minecraft.getInstance(); - List<Component> lines = item.getTooltipLines(mc.player, TooltipFlag.Default.ADVANCED); + List<Component> lines = item.getTooltipLines(null, TooltipFlag.Default.ADVANCED); String tooltipText = ""; // Skip title line. @@ -212,57 +231,66 @@ public class Selectors } } - // Look for a tag matching the given name. - Tag matchedTag = getSubtag(item.getTag(), tagName); - if (matchedTag != null) - { - // A tag value of null means that we are just looking for the presence of this tag. - if (tagValue == null) - { - return true; - } - // Otherwise, we will use the provided comparator. - else - { - if (valueChecker != null) - { - return valueChecker.test(matchedTag, tagValue); - } - } - } + // Look for a tag matching the given name and value. + return findMatchingSubtag(item.getTag(), tagName, tagValue, valueChecker); } return false; } - /** - * Retrieves the first inner tag with the given key, or null if there is no match. - */ - private static Tag getSubtag(CompoundTag tag, String key) + private static boolean findMatchingSubtag(Tag tag, String key, String value, BiPredicate<Tag, String> valueChecker) { if (tag == null) { - return null; + return false; } - if (tag.contains(key)) + if (tag.getId() == Tag.TAG_COMPOUND) { - return tag.get(key); + CompoundTag compoundTag = (CompoundTag)tag; + + if (compoundTag.contains(key)) + { + // Just checking presence. + if (value == null && valueChecker == null) + { + return true; + } + // Otherwise, we will use the provided comparator. + else + { + return valueChecker.test(compoundTag.get(key), value); + } + } + else + { + for (String innerKey : compoundTag.getAllKeys()) + { + if (compoundTag.getTagType(innerKey) == Tag.TAG_LIST || compoundTag.getTagType(innerKey) == Tag.TAG_COMPOUND) + { + if (findMatchingSubtag(compoundTag.get(innerKey), key, value, valueChecker)) + { + return true; + } + } + } + return false; + } } - else + else if (tag.getId() == Tag.TAG_LIST) { - for (String innerKey : tag.getAllKeys()) + ListTag listTag = (ListTag)tag; + for (Tag innerTag : listTag) { - if (tag.getTagType(innerKey) == Tag.TAG_COMPOUND) + if (innerTag.getId() == Tag.TAG_LIST || innerTag.getId() == Tag.TAG_COMPOUND) { - Tag innerTag = getSubtag(tag.getCompound(innerKey), key); - if (innerTag != null) + if (findMatchingSubtag(innerTag, key, value, valueChecker)) { - return innerTag; + return true; } } } - return null; } + return false; } }
\ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java index 6d48c1a..7143b25 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java @@ -2,9 +2,14 @@ package com.anthonyhilyard.iceberg.util; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorExtResult; +import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.GatherResult; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult; import com.mojang.blaze3d.vertex.PoseStack; @@ -15,10 +20,17 @@ import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.Rect2i; import net.minecraft.client.renderer.MultiBufferSource.BufferSource; import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.Style; +import net.minecraft.util.FormattedCharSequence; import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.datafixers.util.Either; import net.minecraft.world.InteractionResult; +import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; import com.mojang.math.Matrix4f; @@ -85,12 +97,13 @@ public class Tooltips Rect2i rect, int screenWidth, int screenHeight, int backgroundColor, int borderColorStart, int borderColorEnd, boolean comparison, boolean constrain) { - renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, backgroundColor, borderColorStart, borderColorEnd, comparison, constrain); + renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, backgroundColor, borderColorStart, borderColorEnd, comparison, constrain, false, 0); } public static void renderItemTooltip(final ItemStack stack, PoseStack poseStack, TooltipInfo info, - Rect2i rect, int screenWidth, int screenHeight, - int backgroundColorStart, int backgroundColorEnd, int borderColorStart, int borderColorEnd, boolean comparison, boolean constrain) + Rect2i rect, int screenWidth, int screenHeight, + int backgroundColorStart, int backgroundColorEnd, int borderColorStart, int borderColorEnd, + boolean comparison, boolean constrain, boolean centeredTitle, int index) { if (info.components.isEmpty()) { @@ -103,10 +116,16 @@ public class Tooltips init(Minecraft.getInstance()); } - int rectX = rect.getX() - 8; - int rectY = rect.getY() + 18; + // Center the title now if needed. + if (centeredTitle) + { + info = new TooltipInfo(centerTitle(info.getComponents(), info.getFont(), rect.getWidth()), info.getFont()); + } + + int rectX = rect.getX() + 4; + int rectY = rect.getY() + 4; - PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, info.getComponents(), poseStack, rectX, rectY, screenWidth, screenHeight, info.getFont(), comparison); + PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, info.getComponents(), poseStack, rectX, rectY, screenWidth, screenHeight, info.getFont(), comparison, index); if (preResult.result() != InteractionResult.PASS) { return; @@ -126,7 +145,7 @@ public class Tooltips itemRenderer.blitOffset = zLevel; Matrix4f mat = poseStack.last().pose(); - ColorExtResult colors = RenderTooltipEvents.COLOREXT.invoker().onColor(stack, info.components, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, comparison); + ColorExtResult colors = RenderTooltipEvents.COLOREXT.invoker().onColor(stack, info.components, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, comparison, index); backgroundColorStart = colors.backgroundStart(); backgroundColorEnd = colors.backgroundEnd(); @@ -168,11 +187,84 @@ public class Tooltips itemRenderer.blitOffset = f; - RenderTooltipEvents.POST.invoker().onPost(stack, info.getComponents(), poseStack, rectX, rectY, info.getFont(), rect.getWidth(), rect.getHeight(), comparison); + RenderTooltipEvents.POSTEXT.invoker().onPost(stack, info.getComponents(), poseStack, rectX, rectY, info.getFont(), rect.getWidth(), rect.getHeight(), comparison, index); + } + + public static List<ClientTooltipComponent> gatherTooltipComponents(ItemStack stack, List<? extends FormattedText> textElements, Optional<TooltipComponent> itemComponent, + int mouseX, int screenWidth, int screenHeight, Font forcedFont, Font fallbackFont, int maxWidth) + { + return gatherTooltipComponents(stack, textElements, itemComponent, mouseX, screenWidth, screenHeight, forcedFont, fallbackFont, maxWidth, 0); + } + + public static List<ClientTooltipComponent> gatherTooltipComponents(ItemStack stack, List<? extends FormattedText> textElements, Optional<TooltipComponent> itemComponent, + int mouseX, int screenWidth, int screenHeight, Font forcedFont, Font fallbackFont, int maxWidth, int index) + { + final Font font = forcedFont == null ? fallbackFont : forcedFont; + + List<Either<FormattedText, TooltipComponent>> elements = textElements.stream() + .map((Function<FormattedText, Either<FormattedText, TooltipComponent>>) Either::left) + .collect(Collectors.toCollection(ArrayList::new)); + + itemComponent.ifPresent(c -> elements.add(1, Either.right(c))); + + GatherResult eventResult = RenderTooltipEvents.GATHER.invoker().onGather(stack, screenWidth, screenHeight, elements, maxWidth, index); + if (eventResult.result() != InteractionResult.PASS) + { + return List.of(); + } + + // Wrap text as needed. First get the maximum width of all components. + int tooltipTextWidth = eventResult.tooltipElements().stream() + .mapToInt(either -> either.map(font::width, component -> 0)) + .max().orElse(0); + + boolean needsWrap = false; + + int tooltipX = mouseX + 12; + if (tooltipX + tooltipTextWidth + 4 > screenWidth) + { + tooltipX = mouseX - 16 - tooltipTextWidth; + if (tooltipX < 4) + { + if (mouseX > screenWidth / 2) + { + tooltipTextWidth = mouseX - 12 - 8; + } + else + { + tooltipTextWidth = screenWidth - 16 - mouseX; + } + needsWrap = true; + } + } + + if (eventResult.maxWidth() > 0 && tooltipTextWidth > eventResult.maxWidth()) + { + tooltipTextWidth = eventResult.maxWidth(); + needsWrap = true; + } + + final int tooltipTextWidthFinal = tooltipTextWidth; + if (needsWrap) + { + return eventResult.tooltipElements().stream().flatMap(either -> either.map(text -> + font.split(text, tooltipTextWidthFinal).stream().map(ClientTooltipComponent::create), + component -> Stream.of(ClientTooltipComponent.create(component)))).toList(); + } + + return eventResult.tooltipElements().stream().map(either -> either.map(text -> + ClientTooltipComponent.create(text instanceof Component ? ((Component) text).getVisualOrderText() : Language.getInstance().getVisualOrder(text)), + ClientTooltipComponent::create)).toList(); } - public static Rect2i calculateRect(final ItemStack stack, PoseStack poseStack, List<ClientTooltipComponent> components, int mouseX, int mouseY, - int screenWidth, int screenHeight, int maxTextWidth, Font font) + public static Rect2i calculateRect(final ItemStack stack, PoseStack poseStack, List<ClientTooltipComponent> components, + int mouseX, int mouseY,int screenWidth, int screenHeight, int maxTextWidth, Font font) + { + return calculateRect(stack, poseStack, components, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, font, 0, false); + } + + public static Rect2i calculateRect(final ItemStack stack, PoseStack poseStack, List<ClientTooltipComponent> components, + int mouseX, int mouseY,int screenWidth, int screenHeight, int maxTextWidth, Font font, int minWidth, boolean centeredTitle) { Rect2i rect = new Rect2i(0, 0, 0, 0); if (components == null || components.isEmpty() || stack == null) @@ -181,7 +273,7 @@ public class Tooltips } // Generate a tooltip event even though we aren't rendering anything in case event handlers are modifying the input values. - PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, components, poseStack, mouseX, mouseY, screenWidth, screenHeight, font, false); + PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, components, poseStack, mouseX, mouseY, screenWidth, screenHeight, font, false, 0); if (preResult.result() != InteractionResult.PASS) { return rect; @@ -193,9 +285,14 @@ public class Tooltips screenHeight = preResult.screenHeight(); font = preResult.font(); - int tooltipTextWidth = 0; + int tooltipTextWidth = minWidth; int tooltipHeight = components.size() == 1 ? -2 : 0; + if (centeredTitle) + { + components = centerTitle(components, font, minWidth); + } + for (ClientTooltipComponent component : components) { int componentWidth = component.getWidth(font); @@ -219,7 +316,49 @@ public class Tooltips tooltipY = screenHeight - tooltipHeight - 6; } - rect = new Rect2i(tooltipX - 4, tooltipY - 4, tooltipTextWidth + 8, tooltipHeight + 8); + rect = new Rect2i(tooltipX - 2, tooltipY - 4, tooltipTextWidth, tooltipHeight); return rect; } -} + + public static List<ClientTooltipComponent> centerTitle(List<ClientTooltipComponent> components, Font font, int minWidth) + { + // Calculate tooltip width first. + int tooltipWidth = minWidth; + + for (ClientTooltipComponent clienttooltipcomponent : components) + { + if (clienttooltipcomponent == null) + { + return components; + } + int componentWidth = clienttooltipcomponent.getWidth(font); + if (componentWidth > tooltipWidth) + { + tooltipWidth = componentWidth; + } + } + + // TODO: If the title is multiple lines, we need to extend this for each one. + + List<FormattedText> recomposedLines = StringRecomposer.recompose(List.of(components.get(0))); + if (recomposedLines.isEmpty()) + { + return components; + } + + List<ClientTooltipComponent> result = new ArrayList<>(components); + + FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0)); + FormattedCharSequence space = FormattedCharSequence.forward(" ", Style.EMPTY); + while (result.get(0).getWidth(font) < tooltipWidth) + { + title = FormattedCharSequence.fromList(List.of(space, title, space)); + if (title == null) + { + break; + } + result.set(0, ClientTooltipComponent.create(title)); + } + return result; + } +}
\ No newline at end of file diff --git a/src/main/resources/iceberg.mixins.json b/src/main/resources/iceberg.mixins.json index ed2f38f..c94d7b8 100644 --- a/src/main/resources/iceberg.mixins.json +++ b/src/main/resources/iceberg.mixins.json @@ -2,7 +2,6 @@ "required": true, "package": "com.anthonyhilyard.iceberg.mixin", "compatibilityLevel": "JAVA_17", - "verbose": true, "mixins": [ "EntityMixin", "PlayerAdvancementsMixin" @@ -11,7 +10,8 @@ "ScreenMixin", "ClientPacketListenerMixin", "LivingEntityMixin", - "MinecraftMixin" + "MinecraftMixin", + "TextColorMixin" ], "injectors": { "defaultRequire": 1 |