Skip to content

Commit

Permalink
Add RenderTooltipEvent (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
glowredman authored Dec 20, 2024
1 parent 71ef2e0 commit bdf4234
Show file tree
Hide file tree
Showing 4 changed files with 344 additions and 1 deletion.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ usesMixins = true
separateMixinSourceSet =

# Adds some debug arguments like verbose output and class export.
usesMixinDebug = false
usesMixinDebug = true

# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
mixinPlugin =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.gtnewhorizon.gtnhlib.client.event;

import java.util.List;
import java.util.function.Consumer;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;

import cpw.mods.fml.common.eventhandler.Cancelable;
import cpw.mods.fml.common.eventhandler.Event;

/**
* RenderTooltipEvent is fired when a tooltip is about to be rendered. With this event you can modify colors,
* coordinates, font or replace the renderer completely. To stop the tooltip from rendering at all, you can
* {@link Event#setCanceled(boolean) cancel} it.
* <p>
* <b>Note:</b> Use {@link ItemTooltipEvent} to modify the text being displayed.
* <p>
* This event is fired on the {@link MinecraftForge#EVENT_BUS} (client-side only).
*
* @since 0.6.0
* @author glowredman
* @see ItemTooltipEvent
*/
@Cancelable
public class RenderTooltipEvent extends Event {

public static final int ORIGINAL_BG_START = 0xF0100010;
public static final int ORIGINAL_BG_END = 0xF0100010;
public static final int ORIGINAL_BORDER_START = 0x505000FF;
public static final int ORIGINAL_BORDER_END = (ORIGINAL_BORDER_START & 0xFEFEFE) >> 1
| ORIGINAL_BORDER_START & 0xFF000000;

/**
* The ItemStack of which the tooltip is being displayed
*/
@Nonnull
public final ItemStack itemStack;

/**
* The GUI in which the tooltip is being rendered
*/
@Nonnull
public final GuiScreen gui;

/**
* The upper background color in ARGB format
* <p>
* Default value: {@code 0xF0100010}
*/
public int backgroundStart;

/**
* The lower background color in ARGB format
* <p>
* Default value: {@code 0xF0100010}
*/
public int backgroundEnd;

/**
* The upper border color in ARGB format
* <p>
* Default value: {@code 0x505000FF}
*/
public int borderStart;

/**
* The lower border color in ARGB format
* <p>
* Default value: {@code 0x5028007F}
*/
public int borderEnd;

/**
* The X coordinate of the mouse cursor
*/
public int x;

/**
* The Y coordinate of the mouse cursor
*/
public int y;

/**
* The FontRenderer used to render the tooltip's text
* <p>
* Default value: {@link GuiScreen#fontRendererObj} or {@link Item#getFontRenderer(ItemStack)} (if that method
* doesn't return {@code null})
*/
@Nonnull
public FontRenderer font;

/**
* Optional hook to completely replace the rendering code. Will be used instead of the vanilla code if not
* {@code null}. The provided argument is the text to render.
* <p>
* <b>Note:</b> The usage may break compat with other mods, for example AppleCore!
* <p>
* Default value: {@code null}
*
* @apiNote The following GL states will be disabled before calling this hook: {@link GL12#GL_RESCALE_NORMAL},
* {@link GL11#GL_LIGHTING}, {@link GL11#GL_LIGHT0}, {@link GL11#GL_LIGHT1},
* {@link GL11#GL_COLOR_MATERIAL}, {@link GL11#GL_DEPTH_TEST}. They will be re-enabled after the hook is
* called. {@link Gui#zLevel GuiScreen.zLevel} and {@link RenderItem#zLevel GuiScreen.itemRender.zLevel}
* are set to {@code 300} before calling {@link Consumer#accept(Object) alternativeRenderer.accept(List)}
* and reset to {@code 0} afterwards. Any more complex behaviors must be handled by the hook. An <a
* href=https://forge.gemwire.uk/wiki/Access_Transformers>AccessTransformer</a> may be needed for this.
*/
@Nullable
public Consumer<List<String>> alternativeRenderer;

public RenderTooltipEvent(ItemStack itemStack, GuiScreen gui, int backgroundStart, int backgroundEnd,
int borderStart, int borderEnd, int x, int y, FontRenderer font) {
this.itemStack = itemStack;
this.gui = gui;
this.backgroundStart = backgroundStart;
this.backgroundEnd = backgroundEnd;
this.borderStart = borderStart;
this.borderEnd = borderEnd;
this.x = x;
this.y = y;
this.font = font;
}

/**
* Convenience method to set both background {@link #backgroundStart start}/{@link #backgroundEnd end} color to the
* same value (the default background does not have a gradient)
*
* @param background The background color in ARGB format
*/
public void setBackground(int background) {
this.backgroundStart = background;
this.backgroundEnd = background;
}

/**
* Calculates {@link #borderEnd} based on {@link #borderStart} using the vanilla algorithm
*/
public void calculateBorderEnd() {
this.borderEnd = (this.borderStart & 0xFEFEFE) >> 1 | this.borderStart & 0xFF000000;
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public enum Mixins {
.setPhase(Phase.EARLY).addMixinClasses("fml.MixinGuiModList")),
EVENT_BUS_ACCESSOR(new Builder("EventBusAccessor").addTargetedMod(TargetedMod.VANILLA).setSide(Side.BOTH)
.setPhase(Phase.EARLY).addMixinClasses("fml.EventBusAccessor", "fml.EnumHolderAccessor")),
TOOLTIP_RENDER(new Builder("TooltipRenderer").addMixinClasses("MixinGuiScreen").addTargetedMod(TargetedMod.VANILLA)
.setApplyIf(() -> true).setPhase(Phase.EARLY).setSide(Side.CLIENT)),
DEBUG_TEXTURES(new Builder("Dump textures sizes").addTargetedMod(TargetedMod.VANILLA).setSide(Side.CLIENT)
.setPhase(Phase.EARLY)
.setApplyIf(() -> Boolean.parseBoolean(System.getProperty("gtnhlib.debugtextures", "false")))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package com.gtnewhorizon.gtnhlib.mixins.early;

import static com.gtnewhorizon.gtnhlib.client.event.RenderTooltipEvent.*;

import java.util.List;

import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import com.gtnewhorizon.gtnhlib.client.event.RenderTooltipEvent;

// This mixin must run before AppleCore's GuiScreenMixin because we're using an @Overwrite
@Mixin(priority = 999, value = GuiScreen.class)
public class MixinGuiScreen extends Gui {

@Unique
private ItemStack gtnhlib$currentStack;

@Shadow
protected static RenderItem itemRender;
@Shadow
public int width;
@Shadow
public int height;

@Inject(at = @At("HEAD"), method = "renderToolTip")
private void preRenderToolTip(ItemStack itemIn, int x, int y, CallbackInfo ci) {
this.gtnhlib$currentStack = itemIn;
}

@Inject(at = @At("TAIL"), method = "renderToolTip")
private void postRenderToolTip(CallbackInfo ci) {
this.gtnhlib$currentStack = null;
}

/**
* @author glowredman
* @reason Add RenderTooltipEvent
*/
@Overwrite(remap = false)
protected void drawHoveringText(List<String> textLines, int mouseX, int mouseY, FontRenderer font) {
if (!textLines.isEmpty()) {
// spotless:off
/***************************************************************************************************************************************
* IMPORTANT NOTE: *
* The int variables width, lineWidth, x, y and height must stay in this order and no other int variables may be inserted before them. *
* This due to compat with AppleCore. *
* *
* The potentially conflicting mixin can be found here: *
* https://github.com/GTNewHorizons/AppleCore/blob/master/src/main/java/squeek/applecore/mixins/early/minecraft/GuiScreenMixin.java *
***************************************************************************************************************************************/
// spotless:on

// create event
RenderTooltipEvent event = new RenderTooltipEvent(
this.gtnhlib$currentStack,
(GuiScreen) (Object) this,
ORIGINAL_BG_START,
ORIGINAL_BG_END,
ORIGINAL_BORDER_START,
ORIGINAL_BORDER_END,
mouseX,
mouseY,
font);

// post event if called from renderToolTip
if (this.gtnhlib$currentStack != null) {
MinecraftForge.EVENT_BUS.post(event);
if (event.isCanceled()) {
// skip all rendering
return;
}
}

// set GL states
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
RenderHelper.disableStandardItemLighting();
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_DEPTH_TEST);

if (event.alternativeRenderer == null) {
// re-assign variables because they might have been modified by an event handler
mouseX = event.x;
mouseY = event.y;
font = event.font;

// determine max line width
int width = 0;
for (String s : textLines) {
int lineWidth = font.getStringWidth(s);
if (lineWidth > width) {
width = lineWidth;
}
}

// calculate coordinates
int x = mouseX + 12;
int y = mouseY - 12;
int height = 8;

if (textLines.size() > 1) {
height += 2 + (textLines.size() - 1) * 10;
}

if (x + width > this.width) {
x -= 28 + width;
}

if (y + height + 6 > this.height) {
y = this.height - height - 6;
}

// set Z level
this.zLevel = 300.0F;
itemRender.zLevel = 300.0F;

int backgroundStart = event.backgroundStart;
int backgroundEnd = event.backgroundEnd;
int borderStart = event.borderStart;
int borderEnd = event.borderEnd;

// spotless:off
// draw background
this.drawGradientRect(x - 3, y - 4, x + width + 3, y - 3, backgroundStart, backgroundStart); // top
this.drawGradientRect(x - 3, y + height + 3, x + width + 3, y + height + 4, backgroundEnd, backgroundEnd); // bottom
this.drawGradientRect(x - 3, y - 3, x + width + 3, y + height + 3, backgroundStart, backgroundEnd); // middle
this.drawGradientRect(x - 4, y - 3, x - 3, y + height + 3, backgroundStart, backgroundEnd); // left
this.drawGradientRect(x + width + 3, y - 3, x + width + 4, y + height + 3, backgroundStart, backgroundEnd); // right
// draw inner border
this.drawGradientRect(x - 3, y - 2, x - 2, y + height + 2, borderStart, borderEnd); // left
this.drawGradientRect(x + width + 2, y - 2, x + width + 3, y + height + 2, borderStart, borderEnd); // right
this.drawGradientRect(x - 3, y - 3, x + width + 3, y - 2, borderStart, borderStart); // top
this.drawGradientRect(x - 3, y + height + 2, x + width + 3, y + height + 3, borderEnd, borderEnd); // bottom
// spotless:on

// draw text
for (int i = 0; i < textLines.size(); i++) {
String s = textLines.get(i);
font.drawStringWithShadow(s, x, y, -1);

// increase Y coordinate
if (i == 0) {
y += 2;
}
y += 10;
}

// reset Z level
this.zLevel = 0.0F;
itemRender.zLevel = 0.0F;
} else {
// set Z level
this.zLevel = 300.0F;
itemRender.zLevel = 300.0F;

// render
event.alternativeRenderer.accept(textLines);

// reset Z level
this.zLevel = 0.0F;
itemRender.zLevel = 0.0F;
}

// reset GL states
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST);
RenderHelper.enableStandardItemLighting();
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
}
}

}

0 comments on commit bdf4234

Please sign in to comment.