Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TooltipDataCallback to allow modders add their own tooltips to existing items #3403

Open
wants to merge 10 commits into
base: 1.20.2
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

/**
* Allows registering a mapping from {@link TooltipData} to {@link TooltipComponent}.
* This allows custom tooltips for items: first, override {@link Item#getTooltipData} and return a custom {@code TooltipData}.
* This allows custom tooltips for items: first, override {@link Item#getTooltipData} and return a custom {@code TooltipData}, or use
* {@link TooltipDataCallback} to add {@link TooltipData} to an existing item.
* Second, register a listener to this event and convert the data to your component implementation if it's an instance of your data class.
*
* <p>Note that failure to map some data to a component will throw an exception,
Expand Down
JumperOnJava marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.api.client.rendering.v1;

import java.util.List;

import net.minecraft.client.gui.tooltip.TooltipComponent;
import net.minecraft.client.item.TooltipData;
import net.minecraft.item.ItemStack;

import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;

/**
* Allows registering custom {@link TooltipData} object for item.
* This allows you to add your own tooltips to existing items.
*
* <p>Custom {@link TooltipData} should be registered using {@link TooltipComponentCallback},
* otherwise game will crash when trying to map {@link TooltipData} to {@link TooltipComponent}.
*/
@FunctionalInterface
public interface TooltipDataCallback {
Event<TooltipDataCallback> EVENT = EventFactory.createArrayBacked(TooltipDataCallback.class, callbacks -> (itemStack, tooltipDataList) -> {
for (TooltipDataCallback callback : callbacks) {
callback.appendTooltipData(itemStack, tooltipDataList);
}
});

/**
* Add your own {@link TooltipData} to passed list if itemStack matches your requirements.
*/
void appendTooltipData(ItemStack itemStack, List<TooltipData> tooltipDataList);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.impl.client.rendering.tooltip;

import java.util.ArrayList;
import java.util.List;

import org.joml.Matrix4f;

import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.tooltip.TooltipComponent;
import net.minecraft.client.item.TooltipData;
import net.minecraft.client.render.VertexConsumerProvider;

/**
* This class renders multiple tooltip components as one.
*/
public class MultiTooltipComponent implements TooltipComponent {
private final int height;
private final int width;
private final List<TooltipComponent> components;

public static MultiTooltipComponent of(List<TooltipData> data) {
var l = new ArrayList<TooltipComponent>(data.size());

for (TooltipData d : data) {
l.add(TooltipComponent.of(d));
}

return new MultiTooltipComponent(l);
}

public MultiTooltipComponent(List<TooltipComponent> components) {
this.components = components;
int height = 0;
int width = 0;

for (TooltipComponent component : components) {
height += component.getHeight();
width = Math.max(width, component.getWidth(MinecraftClient.getInstance().textRenderer));
}

this.height = height;
this.width = width;
}

@Override
public int getHeight() {
return height;
}

@Override
public int getWidth(TextRenderer textRenderer) {
return width;
}

@Override
public void drawText(TextRenderer textRenderer, int x, int y, Matrix4f matrix, VertexConsumerProvider.Immediate vertexConsumers) {
Matrix4f matrixCopy = new Matrix4f(matrix);

for (TooltipComponent c : components) {
JumperOnJava marked this conversation as resolved.
Show resolved Hide resolved
c.drawText(textRenderer, x, y, matrixCopy, vertexConsumers);
matrixCopy.translate(0, c.getHeight(), 0);
}
}

@Override
public void drawItems(TextRenderer textRenderer, int x, int y, DrawContext context) {
context.getMatrices().push();

for (TooltipComponent c : components) {
c.drawItems(textRenderer, x, y, context);
context.getMatrices().translate(0, c.getHeight(), 0);
}

context.getMatrices().pop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.impl.client.rendering.tooltip;

import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback;

public class MultiTooltipComponentRegister implements ClientModInitializer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MultiTooltipComponentInitializer maybe, this class name is a little weird.

@Override
public void onInitializeClient() {
TooltipComponentCallback.EVENT.register((tooltipData) -> {
if (tooltipData instanceof MultiTooltipData multiTooltipData) {
return MultiTooltipComponent.of(multiTooltipData.tooltipData());
}

return null;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.impl.client.rendering.tooltip;

import java.util.List;

import net.minecraft.client.item.TooltipData;

/**
* This class stores multiple TooltipData object to their further mapping to MultiTooltipComponent.
*/
public record MultiTooltipData(List<TooltipData> tooltipData) implements TooltipData {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.client.rendering;

import java.util.ArrayList;
import java.util.Optional;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.item.TooltipData;
import net.minecraft.item.ItemStack;

import net.fabricmc.fabric.api.client.rendering.v1.TooltipDataCallback;
import net.fabricmc.fabric.impl.client.rendering.tooltip.MultiTooltipData;

@Mixin(HandledScreen.class)
class HandledScreenMixin {
@Redirect(method = "drawMouseoverTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getTooltipData()Ljava/util/Optional;"))
JumperOnJava marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A WrapOperation would be better.

Copy link
Author

@JumperOnJava JumperOnJava Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixin extras avaliable only on loader 0.15+, this version is 0.14 compatible
Changing it for one small feature doesn't make much sense (for me)
Here is ME version for 1.20.4 #3486

Optional<TooltipData> addMultiData(ItemStack stack) {
Optional<TooltipData> original = stack.getTooltipData();
var multiData = new MultiTooltipData(new ArrayList<>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: No need to create MultiTooltipData here, just start with the list, and then create it later if the list > 1

original.ifPresent(multiData.tooltipData()::add);
TooltipDataCallback.EVENT.invoker().appendTooltipData(stack, multiData.tooltipData());

if (multiData.tooltipData().isEmpty()) {
return original;
}
Comment on lines +42 to +44
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect people to be allowed to remove the original?


if (multiData.tooltipData().size() == 1){
return Optional.of(multiData.tooltipData().get(0));
}

return Optional.of(multiData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"EntityModelLayersAccessor",
"EntityModelsMixin",
"EntityRenderersMixin",
"HandledScreenMixin",
"InGameHudMixin",
"ItemColorsMixin",
"LivingEntityRendererAccessor",
Expand Down
5 changes: 5 additions & 0 deletions fabric-rendering-v1/src/client/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
},
"entrypoints": {
"client": [
"net.fabricmc.fabric.impl.client.rendering.tooltip.MultiTooltipComponentRegister"
]
},
"description": "Hooks and registries for rendering-related things.",
"mixins": [
"fabric-rendering-v1.mixins.json"
Expand Down
5 changes: 4 additions & 1 deletion fabric-rendering-v1/src/testmod/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"net.fabricmc.fabric.test.rendering.client.FeatureRendererTest",
"net.fabricmc.fabric.test.rendering.client.TooltipComponentTests",
"net.fabricmc.fabric.test.rendering.client.DimensionalRenderingTest",
"net.fabricmc.fabric.test.rendering.client.HudAndShaderTest"
"net.fabricmc.fabric.test.rendering.client.HudAndShaderTest",
"net.fabricmc.fabric.test.rendering.client.tooltip.BundleFullnessTooltipTest",
"net.fabricmc.fabric.test.rendering.client.tooltip.DurabilityTooltipTest",
"net.fabricmc.fabric.test.rendering.client.tooltip.FoodTooltipTest"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.test.rendering.client.tooltip;

import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.tooltip.TooltipComponent;
import net.minecraft.client.item.TooltipData;
import net.minecraft.item.BundleItem;

import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback;
import net.fabricmc.fabric.api.client.rendering.v1.TooltipDataCallback;

public class BundleFullnessTooltipTest implements ClientModInitializer {
@Override
public void onInitializeClient() {
TooltipDataCallback.EVENT.register((itemStack, tooltipDataList) -> {
if (itemStack.getItem() instanceof BundleItem bundle) {
tooltipDataList.add(0, new BundleCustomTooltipData(BundleItem.getAmountFilled(itemStack)));
}
});
TooltipComponentCallback.EVENT.register(data -> {
if (data instanceof BundleCustomTooltipData bundleCustomTooltipData) {
return new BundleFullnessTooltipComponent(bundleCustomTooltipData.fullness);
}

return null;
});
}

private static class BundleCustomTooltipData implements TooltipData {
private final float fullness;
BundleCustomTooltipData(float fullness) {
this.fullness = fullness;
}
}

private static class BundleFullnessTooltipComponent implements TooltipComponent {
private static final int BAR_WIDTH = 40;
private static final int BAR_HEIGHT = 10;
private static final int GAP = 2;
private final float fullness;

BundleFullnessTooltipComponent(float fullness) {
this.fullness = fullness;
}

@Override
public int getHeight() {
return BAR_HEIGHT + GAP;
}

@Override
public int getWidth(TextRenderer textRenderer) {
return BAR_WIDTH;
}

@Override
public void drawItems(TextRenderer textRenderer, int x, int y, DrawContext context) {
context.getMatrices().push();
context.getMatrices().translate(x, y, 0);
context.fill(0, 0, BAR_WIDTH, BAR_HEIGHT, 0xFF3F007F);
context.fill(0, 0, (int) (BAR_WIDTH * fullness), BAR_HEIGHT, 0xFF7F00FF);
context.getMatrices().pop();
}
}
}
Loading