Skip to content

Commit

Permalink
Fix #2353 Maximize content area when avoiding extra modded GUI areas
Browse files Browse the repository at this point in the history
  • Loading branch information
mezz committed May 17, 2021
1 parent 55ef38b commit aa7eb8d
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 16 deletions.
7 changes: 7 additions & 0 deletions src/main/java/mezz/jei/gui/overlay/IngredientGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ public int size() {
return this.guiIngredientSlots.size();
}

public int maxWidth() {
final int columns = this.clientConfig.getMaxColumns();
final int ingredientsWidth = columns * INGREDIENT_WIDTH;
final int minWidth = ClientConfig.smallestNumColumns * INGREDIENT_WIDTH;
return Math.max(ingredientsWidth, minWidth);
}

public boolean updateBounds(Rectangle2d availableArea, Collection<Rectangle2d> exclusionAreas) {
final int columns = Math.min(availableArea.getWidth() / INGREDIENT_WIDTH, this.clientConfig.getMaxColumns());
final int rows = availableArea.getHeight() / INGREDIENT_HEIGHT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import mezz.jei.input.CombinedMouseHandler;
import mezz.jei.util.Rectangle2dBuilder;
Expand Down Expand Up @@ -34,6 +36,7 @@
import mezz.jei.util.CommandUtil;
import mezz.jei.util.MathUtil;
import net.minecraft.util.Tuple;
import net.minecraftforge.common.util.Size2i;

/**
* Displays a list of ingredients with navigation at the top.
Expand Down Expand Up @@ -84,10 +87,21 @@ public void updateLayout(boolean resetToFirstPage) {

public boolean updateBounds(Rectangle2d availableArea, Set<Rectangle2d> guiExclusionAreas) {
Tuple<Rectangle2d, Rectangle2d> result = MathUtil.splitY(availableArea, NAVIGATION_HEIGHT);
Rectangle2d estimatedNavigationArea = result.getA();
Rectangle2d navigationArea = MathUtil.moveDownToAvoidIntersection(guiExclusionAreas, estimatedNavigationArea);
int navigationMaxY = navigationArea.getY() + navigationArea.getHeight();
result = MathUtil.splitY(availableArea, navigationMaxY);
final Rectangle2d estimatedNavigationArea = result.getA();

Collection<Rectangle2d> intersectsNavigationArea = guiExclusionAreas.stream()
.filter(rectangle2d -> MathUtil.intersects(rectangle2d, estimatedNavigationArea))
.collect(Collectors.toList());

final int maxWidth = this.ingredientGrid.maxWidth();
Size2i maxContentSize = new Size2i(maxWidth, availableArea.getHeight());
availableArea = MathUtil.cropToAvoidIntersection(intersectsNavigationArea, availableArea, maxContentSize);
if (MathUtil.contentArea(availableArea, maxContentSize) == 0) {
return false;
}

result = MathUtil.splitY(availableArea, NAVIGATION_HEIGHT);
Rectangle2d navigationArea = result.getA();
Rectangle2d boundsWithoutNavigation = result.getB();
boolean gridHasRoom = this.ingredientGrid.updateBounds(boundsWithoutNavigation, guiExclusionAreas);
if (!gridHasRoom) {
Expand Down
115 changes: 103 additions & 12 deletions src/main/java/mezz/jei/util/MathUtil.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
package mezz.jei.util;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.Rectangle2d;
import net.minecraft.util.Tuple;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.util.text.ITextProperties;
import net.minecraftforge.common.util.Size2i;

public final class MathUtil {
@FunctionalInterface
private interface Rectangle2dCropper {
Rectangle2d crop(Rectangle2d original, Rectangle2d intersecting);
}

private static final List<Rectangle2dCropper> rectangle2dCroppers = Arrays.asList(
MathUtil::cropTop,
MathUtil::cropBottom,
MathUtil::cropLeft,
MathUtil::cropRight
);

private static final Rectangle2d emptyRect = new Rectangle2d(0, 0, 0, 0);

private MathUtil() {

}
Expand Down Expand Up @@ -37,19 +55,92 @@ public static boolean intersects(Rectangle2d rect1, Rectangle2d rect2) {
rect2.getY() < rect1.getY() + rect1.getHeight();
}

public static Rectangle2d moveDownToAvoidIntersection(Collection<Rectangle2d> areas, Rectangle2d comparisonArea) {
for (Rectangle2d area : areas) {
if (intersects(area, comparisonArea)) {
Rectangle2d movedDown = new Rectangle2d(
comparisonArea.getX(),
area.getY() + area.getHeight(),
comparisonArea.getWidth(),
comparisonArea.getHeight()
);
return moveDownToAvoidIntersection(areas, movedDown);
}
/**
* Tries cropping "comparisonArea" in 4 different directions to get out of the way of "areas".
* Returns the largest resulting area after the crop, to find the "best" way of getting out of the way.
*/
public static Rectangle2d cropToAvoidIntersection(Collection<Rectangle2d> areas, Rectangle2d comparisonArea, Size2i maxContentSize) {
return areas.stream()
.filter(rectangle2d -> intersects(rectangle2d, comparisonArea))
.sorted(Comparator.comparingInt(r -> contentArea(r, maxContentSize)))
.reduce(comparisonArea, (r1, r2) -> bestCrop(r1, r2, maxContentSize));
}

/**
* Crop the given "rect" to avoid "intersecting" while maximizing the available content space.
*/
private static Rectangle2d bestCrop(Rectangle2d rect, Rectangle2d intersecting, Size2i maxContentSize) {
if (contentArea(rect, maxContentSize) == 0) {
return rect;
}
return rectangle2dCroppers.stream()
.map(cropper -> cropper.crop(rect, intersecting))
.max(Comparator.comparingInt(r -> contentArea(r, maxContentSize)))
.orElse(emptyRect);
}

/**
* Calculates the area of flexible content that can fit in a given rect.
*/
public static int contentArea(Rectangle2d rect, Size2i maxContentSize) {
if (rect.getWidth() <= 0 || rect.getHeight() <= 0) {
return 0;
}
return Math.min(rect.getWidth(), maxContentSize.width) * Math.min(rect.getHeight(), maxContentSize.height);
}

private static Rectangle2d cropTop(Rectangle2d original, Rectangle2d intersecting) {
int maxY = original.getY() + original.getHeight();
int newY = intersecting.getY() + intersecting.getHeight();
if (maxY < newY) {
return emptyRect;
}
return new Rectangle2d(
original.getX(),
newY,
original.getWidth(),
maxY - newY
);
}

private static Rectangle2d cropBottom(Rectangle2d original, Rectangle2d intersecting) {
int newHeight = intersecting.getY() - original.getY();
if (newHeight < 0) {
return emptyRect;
}
return new Rectangle2d(
original.getX(),
original.getY(),
original.getWidth(),
newHeight
);
}

private static Rectangle2d cropRight(Rectangle2d original, Rectangle2d intersecting) {
int newWidth = intersecting.getX() - original.getX();
if (newWidth < 0) {
return emptyRect;
}
return new Rectangle2d(
original.getX(),
original.getY(),
newWidth,
original.getHeight()
);
}

private static Rectangle2d cropLeft(Rectangle2d original, Rectangle2d intersecting) {
int maxX = original.getX() + original.getWidth();
int newX = intersecting.getX() + intersecting.getWidth();
if (maxX < newX) {
return emptyRect;
}
return comparisonArea;
return new Rectangle2d(
newX,
original.getY(),
maxX - newX,
original.getHeight()
);
}

public static boolean contains(Collection<Rectangle2d> areas, double x, double y) {
Expand Down

0 comments on commit aa7eb8d

Please sign in to comment.