Skip to content

Commit

Permalink
add an IPlaceable interface for GUI elements that can have their posi…
Browse files Browse the repository at this point in the history
…tion set
  • Loading branch information
mezz committed Oct 1, 2024
1 parent cbe395a commit c8357cd
Show file tree
Hide file tree
Showing 29 changed files with 497 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package mezz.jei.common.gui.elements;

import net.minecraft.client.gui.GuiGraphics;
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.gui.placement.IPlaceable;
import net.minecraft.client.gui.GuiGraphics;

/**
* Draws with a built-in offset.
*/
public class OffsetDrawable implements IDrawable {
public class OffsetDrawable implements IDrawable, IPlaceable<OffsetDrawable> {
public static IDrawable create(IDrawable drawable, int xOffset, int yOffset) {
if (xOffset == 0 && yOffset == 0) {
return drawable;
Expand All @@ -15,10 +16,10 @@ public static IDrawable create(IDrawable drawable, int xOffset, int yOffset) {
}

private final IDrawable drawable;
private final int xOffset;
private final int yOffset;
private int xOffset;
private int yOffset;

private OffsetDrawable(IDrawable drawable, int xOffset, int yOffset) {
public OffsetDrawable(IDrawable drawable, int xOffset, int yOffset) {
this.drawable = drawable;
this.xOffset = xOffset;
this.yOffset = yOffset;
Expand Down Expand Up @@ -47,4 +48,11 @@ public void draw(GuiGraphics guiGraphics, int xOffset, int yOffset) {
public void draw(GuiGraphics guiGraphics) {
this.drawable.draw(guiGraphics, this.xOffset, this.yOffset);
}

@Override
public OffsetDrawable setPosition(int xPos, int yPos) {
this.xOffset = xPos;
this.yOffset = yPos;
return this;
}
}
98 changes: 48 additions & 50 deletions Common/src/main/java/mezz/jei/common/gui/elements/TextWidget.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package mezz.jei.common.gui.elements;

import mezz.jei.api.gui.builder.ITooltipBuilder;
import mezz.jei.api.gui.placement.HorizontalAlignment;
import mezz.jei.api.gui.placement.VerticalAlignment;
import mezz.jei.api.gui.widgets.IRecipeWidget;
import mezz.jei.api.gui.widgets.ITextWidget;
import mezz.jei.common.config.DebugConfig;
import mezz.jei.common.util.HorizontalAlignment;
import mezz.jei.common.util.ImmutableRect2i;
import mezz.jei.common.util.StringUtil;
import mezz.jei.common.util.VerticalAlignment;
import mezz.jei.core.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
Expand All @@ -22,19 +22,20 @@

public class TextWidget implements ITextWidget, IRecipeWidget {
private final List<FormattedText> text;
private final ImmutableRect2i area;
private ImmutableRect2i availableArea;

private HorizontalAlignment horizontalAlignment;
private VerticalAlignment verticalAlignment;
private Font font;
private int color;
private boolean shadow;
private int lineSpacing;
private List<FormattedText> tooltipText = List.of();

private @Nullable List<FormattedText> wrappedText;
private boolean truncated = false;

public TextWidget(List<FormattedText> text, int xPos, int yPos, int maxWidth, int maxHeight) {
this.area = new ImmutableRect2i(xPos, yPos, maxWidth, maxHeight);
this.availableArea = new ImmutableRect2i(xPos, yPos, maxWidth, maxHeight);
Minecraft minecraft = Minecraft.getInstance();
this.font = minecraft.font;
this.color = 0xFF000000;
Expand All @@ -44,88 +45,93 @@ public TextWidget(List<FormattedText> text, int xPos, int yPos, int maxWidth, in
this.verticalAlignment = VerticalAlignment.TOP;
}

@Override
public ITextWidget alignHorizontalLeft() {
this.horizontalAlignment = HorizontalAlignment.LEFT;
return this;
private void invalidateCachedValues() {
wrappedText = null;
truncated = false;
}

@Override
public ITextWidget alignHorizontalRight() {
this.horizontalAlignment = HorizontalAlignment.RIGHT;
return this;
public int getWidth() {
return availableArea.width();
}

@Override
public ITextWidget alignHorizontalCenter() {
this.horizontalAlignment = HorizontalAlignment.CENTER;
return this;
public int getHeight() {
return availableArea.height();
}

@Override
public ITextWidget alignVerticalTop() {
this.verticalAlignment = VerticalAlignment.TOP;
public TextWidget setPosition(int xPos, int yPos) {
this.availableArea = this.availableArea.setPosition(xPos, yPos);
invalidateCachedValues();
return this;
}

@Override
public ITextWidget alignVerticalCenter() {
this.verticalAlignment = VerticalAlignment.CENTER;
public TextWidget setTextAlignment(HorizontalAlignment horizontalAlignment) {
if (this.horizontalAlignment.equals(horizontalAlignment)) {
return this;
}
this.horizontalAlignment = horizontalAlignment;
invalidateCachedValues();
return this;
}

@Override
public ITextWidget alignVerticalBottom() {
this.verticalAlignment = VerticalAlignment.BOTTOM;
public TextWidget setTextAlignment(VerticalAlignment verticalAlignment) {
if (this.verticalAlignment.equals(verticalAlignment)) {
return this;
}
this.verticalAlignment = verticalAlignment;
invalidateCachedValues();
return this;
}

@Override
public ITextWidget setFont(Font font) {
this.font = font;
invalidateCachedValues();
return this;
}

@Override
public ITextWidget setColor(int color) {
this.color = color;
invalidateCachedValues();
return this;
}

@Override
public ITextWidget setLineSpacing(int lineSpacing) {
this.lineSpacing = lineSpacing;
invalidateCachedValues();
return this;
}

@Override
public ITextWidget setShadow(boolean shadow) {
this.shadow = shadow;
invalidateCachedValues();
return this;
}

@Override
public ScreenPosition getPosition() {
return area.getScreenPosition();
return availableArea.getScreenPosition();
}

private List<FormattedText> calculateWrappedText() {
if (wrappedText != null) {
return wrappedText;
}
int lineHeight = getLineHeight();
int maxLines = area.height() / lineHeight;
if (maxLines * lineHeight + font.lineHeight <= area.height()) {
int maxLines = availableArea.height() / lineHeight;
if (maxLines * lineHeight + font.lineHeight <= availableArea.height()) {
maxLines++;
}
Pair<List<FormattedText>, Boolean> result = StringUtil.splitLines(font, text, area.width(), maxLines);
Pair<List<FormattedText>, Boolean> result = StringUtil.splitLines(font, text, availableArea.width(), maxLines);
this.wrappedText = result.first();
boolean truncated = result.second();
if (truncated) {
this.tooltipText = text;
} else {
this.tooltipText = List.of();
}
this.truncated = result.second();
return wrappedText;
}

Expand All @@ -148,38 +154,30 @@ public void drawWidget(GuiGraphics guiGraphics, double mouseX, double mouseY) {
}

if (DebugConfig.isDebugGuisEnabled()) {
guiGraphics.fill(0,0, area.width(), area.height(), 0xAAAAAA00);
guiGraphics.fill(0,0, availableArea.width(), availableArea.height(), 0xAAAAAA00);
}
}

@Override
public void getTooltip(ITooltipBuilder tooltip, double mouseX, double mouseY) {
if (mouseX >= 0 && mouseX < area.width() && mouseY >= 0 && mouseY < area.height()) {
if (mouseX >= 0 && mouseX < availableArea.width() && mouseY >= 0 && mouseY < availableArea.height()) {
calculateWrappedText();
tooltip.addAll(tooltipText);
if (truncated) {
tooltip.addAll(text);
}
}
}

private int getXPos(FormattedCharSequence text) {
return switch (horizontalAlignment) {
case LEFT -> 0;
case RIGHT -> this.area.width() - font.width(text);
case CENTER -> Math.round((this.area.width() - font.width(text)) / 2f);
};
return getXPos(font.width(text));
}

private int getYPosStart(int lineHeight, List<FormattedText> text) {
if (verticalAlignment == VerticalAlignment.TOP) {
return 0;
}
private int getXPos(int lineWidth) {
return horizontalAlignment.getXPos(this.availableArea.width(), lineWidth);
}

private int getYPosStart(int lineHeight, List<FormattedText> text) {
int linesHeight = (lineHeight * text.size()) - lineSpacing - 1;
if (verticalAlignment == VerticalAlignment.BOTTOM) {
return area.height() - linesHeight;
} else if (verticalAlignment == VerticalAlignment.CENTER) {
return Math.round((area.height() - linesHeight) / 2f);
} else {
throw new IllegalArgumentException("Unknown verticalAlignment " + verticalAlignment);
}
return verticalAlignment.getYPos(this.availableArea.height(), linesHeight);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mezz.jei.api.gui.builder;

import mezz.jei.api.gui.placement.IPlaceable;
import mezz.jei.api.gui.widgets.ISlottedWidgetFactory;
import mezz.jei.api.recipe.IFocusGroup;
import mezz.jei.api.recipe.RecipeIngredientRole;
Expand All @@ -24,7 +25,8 @@ public interface IRecipeLayoutBuilder {
* @since 19.19.0
*/
default IRecipeSlotBuilder addInputSlot(int x, int y) {
return addSlot(RecipeIngredientRole.INPUT, x, y);
return addSlot(RecipeIngredientRole.INPUT)
.setPosition(x, y);
}

/**
Expand All @@ -37,20 +39,34 @@ default IRecipeSlotBuilder addInputSlot(int x, int y) {
* @since 19.19.0
*/
default IRecipeSlotBuilder addOutputSlot(int x, int y) {
return addSlot(RecipeIngredientRole.OUTPUT, x, y);
return addSlot(RecipeIngredientRole.OUTPUT)
.setPosition(x, y);
}

/**
* Add a slot that will be drawn at the given position relative to the recipe layout.
*
* @param recipeIngredientRole the {@link RecipeIngredientRole} of this slot (for lookups).
* @param role the {@link RecipeIngredientRole} of this slot (for lookups).
* @param x relative x position of the slot on the recipe layout.
* @param y relative y position of the slot on the recipe layout.
* @return a {@link IRecipeSlotBuilder} that has further methods for adding ingredients, etc.
*
* @since 9.3.0
*/
IRecipeSlotBuilder addSlot(RecipeIngredientRole recipeIngredientRole, int x, int y);
default IRecipeSlotBuilder addSlot(RecipeIngredientRole role, int x, int y) {
return addSlot(role)
.setPosition(x, y);
}

/**
* Add a slot and set its position using {@link IPlaceable} methods.
*
* @param role the {@link RecipeIngredientRole} of this slot (for lookups).
* @return a {@link IRecipeSlotBuilder} that has further methods for adding ingredients, etc.
*
* @since 19.19.1
*/
IRecipeSlotBuilder addSlot(RecipeIngredientRole role);

/**
* Assign this slot to a {@link ISlottedWidgetFactory},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback;
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
import mezz.jei.api.gui.placement.IPlaceable;
import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.ingredients.IIngredientRenderer;
import mezz.jei.api.ingredients.IIngredientType;
Expand All @@ -21,7 +22,7 @@
* @since 9.3.0
*/
@ApiStatus.NonExtendable
public interface IRecipeSlotBuilder extends IIngredientAcceptor<IRecipeSlotBuilder> {
public interface IRecipeSlotBuilder extends IIngredientAcceptor<IRecipeSlotBuilder>, IPlaceable<IRecipeSlotBuilder> {
/**
* Add a callback to alter the tooltip for these ingredients.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package mezz.jei.api.gui.placement;

/**
* Represents a horizontal alignment of an element inside a larger area.
* @since 19.19.1
*/
public enum HorizontalAlignment {
LEFT {
@Override
public int getXPos(int availableWidth, int elementWidth) {
return 0;
}
},
CENTER {
@Override
public int getXPos(int availableWidth, int elementWidth) {
return Math.round((availableWidth - elementWidth) / 2f);
}
},
RIGHT {
@Override
public int getXPos(int availableWidth, int elementWidth) {
return availableWidth - elementWidth;
}
};

/**
* Calculate the x position needed to align an element with the given width inside the availableArea.
* @since 19.19.1
*/
public abstract int getXPos(int availableWidth, int elementWidth);
}
37 changes: 37 additions & 0 deletions CommonApi/src/main/java/mezz/jei/api/gui/placement/IPlaceable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package mezz.jei.api.gui.placement;

/**
* Interface for things that can have their position set, and be aligned vertically and horizontally in an area.
*
* @since 19.19.1
*/
public interface IPlaceable<THIS extends IPlaceable<THIS>> {
/**
* Place this element at the given position.
* @since 19.19.1
*/
THIS setPosition(int xPos, int yPos);

/**
* Place this element inside the given area, with the given alignment.
*
* @since 19.19.1
*/
default THIS setPosition(int areaX, int areaY, int areaWidth, int areaHeight, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment) {
int x = areaX + horizontalAlignment.getXPos(areaWidth, getWidth());
int y = areaY + verticalAlignment.getYPos(areaHeight, getHeight());
return setPosition(x, y);
}

/**
* Get the width of this element.
* @since 19.19.1
*/
int getWidth();

/**
* Get the height of this element.
* @since 19.19.1
*/
int getHeight();
}
Loading

0 comments on commit c8357cd

Please sign in to comment.