mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 08:07:42 +00:00
Tidy the map hover card
Sections with labelled values.
This commit is contained in:
1 parent
3123ab3753
commit
97612385e5
2 files changed
+205
-72
No files matched your search
@@ -839,11 +839,11 @@ final class MapCanvas extends JPanel {
|
||||
return;
|
||||
}
|
||||
MapHoverProbe.Details details = hoverDetails;
|
||||
List<String> lines = MapHoverCard.lines(quick.subject(), quick.region(), details, currentType, detailedHover,
|
||||
MapHoverCard card = MapHoverCard.of(quick.subject(), quick.region(), details, currentType, detailedHover,
|
||||
quick.worldX(), quick.worldZ());
|
||||
int screenX = (int) Math.round(worldToScreenX(quick.worldX())) + StudioTheme.scale(16);
|
||||
int screenY = (int) Math.round(worldToScreenZ(quick.worldZ()));
|
||||
drawCard(canvas, screenX, screenY, 0D, 0D, lines);
|
||||
card.paint(canvas, screenX, screenY, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
private void renderHelp(Graphics2D canvas) {
|
||||
|
||||
@@ -24,49 +24,131 @@ import com.volmit.iris.core.loader.IrisRegistrant;
|
||||
import com.volmit.iris.engine.HybridBiomeSelector;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The text of the card that follows the cursor: what the drawn map shows there at once, and the rest once probed.
|
||||
* The card that follows the cursor: what the drawn map shows there at once, and the rest once probed, in titled
|
||||
* sections of labelled values.
|
||||
*/
|
||||
final class MapHoverCard {
|
||||
private MapHoverCard() {
|
||||
record MapHoverCard(String title, String subtitle, List<Section> sections, String footer) {
|
||||
private static final int PADDING = 12;
|
||||
private static final int SUBTITLE_GAP = 2;
|
||||
private static final int SECTION_GAP = 10;
|
||||
private static final int SECTION_TITLE_GAP = 4;
|
||||
private static final int COLUMN_GAP = 14;
|
||||
private static final int ROW_GAP = 2;
|
||||
|
||||
/**
|
||||
* @param subject the biome, or region in the region view, the drawn tile shows; null while that tile is drawing
|
||||
* @param details the probed facts about the spot, or null until they arrive
|
||||
* @param detailed whether Shift is held, which adds the pack files
|
||||
*/
|
||||
static MapHoverCard of(MapLabels.Label subject, MapLabels.Label region, MapHoverProbe.Details details,
|
||||
RenderType type, boolean detailed, double worldX, double worldZ) {
|
||||
List<Section> sections = new ArrayList<>();
|
||||
if (details != null) {
|
||||
sections.add(terrainSection(details, type));
|
||||
addPickSection(sections, details);
|
||||
}
|
||||
sections.add(locationSection(worldX, worldZ, detailed));
|
||||
if (detailed) {
|
||||
addFilesSection(sections, subject, region);
|
||||
}
|
||||
return new MapHoverCard(title(subject, details), subtitle(region, details, type), sections,
|
||||
detailed ? null : "Shift for pack files · H highlights");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subject the biome, or region in the region view, the drawn tile shows; null while that tile is drawing
|
||||
* @param details the probed facts about the spot, or null until they arrive
|
||||
* @param detailed whether Shift is held, which adds keys and files
|
||||
* Draws the card with its top left corner as close to the given point as fits inside the bounds.
|
||||
*/
|
||||
static List<String> lines(MapLabels.Label subject, MapLabels.Label region, MapHoverProbe.Details details,
|
||||
RenderType type, boolean detailed, double worldX, double worldZ) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
lines.add(title(subject, details));
|
||||
if (type != RenderType.REGION) {
|
||||
String regionName = region != null ? region.name() : details != null ? details.region().getName() : null;
|
||||
if (regionName != null) {
|
||||
lines.add("Region " + regionName);
|
||||
void paint(Graphics2D canvas, int anchorX, int anchorY, int boundsWidth, int boundsHeight) {
|
||||
int padding = StudioTheme.scale(PADDING);
|
||||
int labelWidth = labelColumnWidth(canvas);
|
||||
int width = Math.max(headerWidth(canvas), labelWidth + StudioTheme.scale(COLUMN_GAP) + valueColumnWidth(canvas)) + padding * 2;
|
||||
int height = contentHeight(canvas) + padding * 2;
|
||||
int x = Math.max(padding, Math.min(boundsWidth - width - padding, anchorX));
|
||||
int y = Math.max(padding, Math.min(boundsHeight - height - padding, anchorY));
|
||||
StudioOverlay.drawCard(canvas, x, y, width, height);
|
||||
|
||||
int left = x + padding;
|
||||
int top = y + padding;
|
||||
top = drawLine(canvas, title, StudioTheme.boldFont(), StudioTheme.TEXT, left, top);
|
||||
if (subtitle != null) {
|
||||
top = drawLine(canvas, subtitle, StudioTheme.smallFont(), StudioTheme.SECONDARY_TEXT, left,
|
||||
top + StudioTheme.scale(SUBTITLE_GAP));
|
||||
}
|
||||
for (Section section : sections) {
|
||||
top += StudioTheme.scale(SECTION_GAP);
|
||||
canvas.setColor(StudioTheme.OVERLAY_BORDER);
|
||||
canvas.fillRect(left, top, width - padding * 2, 1);
|
||||
top += StudioTheme.scale(SECTION_GAP) / 2;
|
||||
top = drawLine(canvas, section.title().toUpperCase(Locale.ROOT), StudioTheme.sectionFont(), StudioTheme.MUTED_TEXT,
|
||||
left, top) + StudioTheme.scale(SECTION_TITLE_GAP);
|
||||
for (Row row : section.rows()) {
|
||||
drawLine(canvas, row.label(), StudioTheme.font(), StudioTheme.SECONDARY_TEXT, left, top);
|
||||
top = drawLine(canvas, row.value(), StudioTheme.font(), StudioTheme.TEXT,
|
||||
left + labelWidth + StudioTheme.scale(COLUMN_GAP), top) + StudioTheme.scale(ROW_GAP);
|
||||
}
|
||||
}
|
||||
if (details != null) {
|
||||
addTerrain(lines, details, type);
|
||||
if (footer != null) {
|
||||
drawLine(canvas, footer, StudioTheme.smallFont(), StudioTheme.MUTED_TEXT, left, top + StudioTheme.scale(SECTION_GAP));
|
||||
}
|
||||
int blockX = MapCanvas.floorWorldCoordinate(worldX);
|
||||
int blockZ = MapCanvas.floorWorldCoordinate(worldZ);
|
||||
lines.add("Block " + blockX + ", " + blockZ + " · chunk " + (blockX >> 4) + ", " + (blockZ >> 4));
|
||||
if (!detailed) {
|
||||
lines.add("Shift for keys and files · H highlights");
|
||||
return lines;
|
||||
}
|
||||
|
||||
private int labelColumnWidth(Graphics2D canvas) {
|
||||
FontMetrics metrics = canvas.getFontMetrics(StudioTheme.font());
|
||||
return sections.stream().flatMap(section -> section.rows().stream())
|
||||
.mapToInt(row -> metrics.stringWidth(row.label())).max().orElse(0);
|
||||
}
|
||||
|
||||
private int valueColumnWidth(Graphics2D canvas) {
|
||||
FontMetrics metrics = canvas.getFontMetrics(StudioTheme.font());
|
||||
return sections.stream().flatMap(section -> section.rows().stream())
|
||||
.mapToInt(row -> metrics.stringWidth(row.value())).max().orElse(0);
|
||||
}
|
||||
|
||||
private int headerWidth(Graphics2D canvas) {
|
||||
int width = canvas.getFontMetrics(StudioTheme.boldFont()).stringWidth(title);
|
||||
FontMetrics small = canvas.getFontMetrics(StudioTheme.smallFont());
|
||||
if (subtitle != null) {
|
||||
width = Math.max(width, small.stringWidth(subtitle));
|
||||
}
|
||||
addSource(lines, subject);
|
||||
if (subject != null && subject.kind() != MapLabels.Kind.REGION) {
|
||||
addSource(lines, region);
|
||||
if (footer != null) {
|
||||
width = Math.max(width, small.stringWidth(footer));
|
||||
}
|
||||
lines.add("Region file r." + (blockX >> 9) + "." + (blockZ >> 9) + ".mca");
|
||||
return lines;
|
||||
return width;
|
||||
}
|
||||
|
||||
private int contentHeight(Graphics2D canvas) {
|
||||
int small = canvas.getFontMetrics(StudioTheme.smallFont()).getHeight();
|
||||
int row = canvas.getFontMetrics(StudioTheme.font()).getHeight() + StudioTheme.scale(ROW_GAP);
|
||||
int sectionTitle = canvas.getFontMetrics(StudioTheme.sectionFont()).getHeight() + StudioTheme.scale(SECTION_TITLE_GAP);
|
||||
int height = canvas.getFontMetrics(StudioTheme.boldFont()).getHeight();
|
||||
if (subtitle != null) {
|
||||
height += StudioTheme.scale(SUBTITLE_GAP) + small;
|
||||
}
|
||||
for (Section section : sections) {
|
||||
height += StudioTheme.scale(SECTION_GAP) + StudioTheme.scale(SECTION_GAP) / 2 + sectionTitle + section.rows().size() * row;
|
||||
}
|
||||
if (footer != null) {
|
||||
height += StudioTheme.scale(SECTION_GAP) + small;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
private static int drawLine(Graphics2D canvas, String text, Font font, Color color, int left, int top) {
|
||||
canvas.setFont(font);
|
||||
canvas.setColor(color);
|
||||
FontMetrics metrics = canvas.getFontMetrics();
|
||||
canvas.drawString(text, left, top + metrics.getAscent());
|
||||
return top + metrics.getHeight();
|
||||
}
|
||||
|
||||
private static String title(MapLabels.Label subject, MapHoverProbe.Details details) {
|
||||
@@ -79,61 +161,115 @@ final class MapHoverCard {
|
||||
return "Drawing…";
|
||||
}
|
||||
|
||||
private static void addTerrain(List<String> lines, MapHoverProbe.Details details, RenderType type) {
|
||||
lines.add("Height " + Math.round(details.height()) + " · sea level " + Math.round(details.fluidHeight()));
|
||||
String slope = "slope " + Math.round(details.slopeDegrees()) + "°";
|
||||
lines.add(details.terrainKind() == null ? capitalize(slope) : details.terrainKind() + " · " + slope);
|
||||
// The region view's title already is the region, so only the kind of ground is left to say.
|
||||
private static String subtitle(MapLabels.Label region, MapHoverProbe.Details details, RenderType type) {
|
||||
List<String> parts = new ArrayList<>();
|
||||
if (type != RenderType.REGION) {
|
||||
String regionName = region != null ? region.name() : details != null ? details.region().getName() : null;
|
||||
if (regionName != null) {
|
||||
parts.add(regionName + " region");
|
||||
}
|
||||
}
|
||||
if (details != null && details.terrainKind() != null) {
|
||||
parts.add(details.terrainKind());
|
||||
}
|
||||
return parts.isEmpty() ? null : String.join(" · ", parts);
|
||||
}
|
||||
|
||||
private static Section terrainSection(MapHoverProbe.Details details, RenderType type) {
|
||||
List<Row> rows = new ArrayList<>();
|
||||
long height = Math.round(details.height());
|
||||
rows.add(new Row("Height", height + " · " + heightAboveSea(details.height() - details.fluidHeight())));
|
||||
rows.add(new Row("Slope", Math.round(details.slopeDegrees()) + "°"));
|
||||
IrisBiome surface = details.surfaceBiome();
|
||||
IrisBiome cave = details.caveBiome();
|
||||
if (type == RenderType.CAVE_LAND && surface != null) {
|
||||
lines.add("Surface biome " + surface.getName());
|
||||
rows.add(new Row("Surface biome", surface.getName()));
|
||||
} else if (type != RenderType.CAVE_LAND && cave != null && cave != surface) {
|
||||
lines.add("Cave biome " + cave.getName());
|
||||
rows.add(new Row("Cave biome", cave.getName()));
|
||||
}
|
||||
if (details.vanillaBiome() != null) {
|
||||
lines.add("Minecraft biome " + details.vanillaBiome().replace('_', ' '));
|
||||
rows.add(new Row("Minecraft", details.vanillaBiome().replace('_', ' ')));
|
||||
}
|
||||
return new Section("Terrain", rows);
|
||||
}
|
||||
|
||||
private static void addPickSection(List<Section> sections, MapHoverProbe.Details details) {
|
||||
List<Row> rows = new ArrayList<>();
|
||||
MapHoverProbe.PickNoise noise = details.pickNoise();
|
||||
if (noise != null && noise.regionNoise() != null) {
|
||||
rows.add(new Row("Region noise", noiseText(noise.regionNoise())));
|
||||
}
|
||||
if (noise != null && noise.biomeType() != null) {
|
||||
String label = capitalize(noise.biomeType().name().toLowerCase(Locale.ROOT)) + " noise";
|
||||
rows.add(new Row(label, biomeNoiseText(noise, details.surfaceBiome())));
|
||||
}
|
||||
if (details.placement() != null) {
|
||||
lines.add(placementText(details.placement()));
|
||||
rows.add(new Row("Height", placementText(details.placement())));
|
||||
}
|
||||
if (details.pickNoise() != null) {
|
||||
addPickNoise(lines, details.pickNoise(), surface);
|
||||
if (!rows.isEmpty()) {
|
||||
sections.add(new Section("Picked by", rows));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addPickNoise(List<String> lines, MapHoverProbe.PickNoise noise, IrisBiome surface) {
|
||||
if (noise.regionNoise() != null) {
|
||||
lines.add("Region noise " + noiseText(noise.regionNoise()));
|
||||
}
|
||||
if (noise.biomeType() == null) {
|
||||
return;
|
||||
}
|
||||
String biomeNoise = capitalize(noise.biomeType().name().toLowerCase(Locale.ROOT)) + " biome noise "
|
||||
+ noiseText(noise.biomeNoise());
|
||||
private static String biomeNoiseText(MapHoverProbe.PickNoise noise, IrisBiome surface) {
|
||||
String value = noiseText(noise.biomeNoise());
|
||||
IrisBiome picked = noise.noisePick();
|
||||
if (picked == null) {
|
||||
lines.add(biomeNoise);
|
||||
} else if (surface == null || picked.getLoadKey().equals(surface.getLoadKey())) {
|
||||
lines.add(biomeNoise + " picks " + picked.getName());
|
||||
} else {
|
||||
lines.add(biomeNoise + " picks " + picked.getName() + ", then " + surface.getName() + " takes over");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static String noiseText(double noise) {
|
||||
return String.format(Locale.ROOT, "%.3f", noise);
|
||||
if (surface == null || picked.getLoadKey().equals(surface.getLoadKey())) {
|
||||
return value + " → " + picked.getName();
|
||||
}
|
||||
return value + " → " + picked.getName() + ", child " + surface.getName() + " took over";
|
||||
}
|
||||
|
||||
// How hybrid terrain chose the base biome; a child biome or a coast can still replace it afterwards.
|
||||
private static String placementText(HybridBiomeSelector.Choice choice) {
|
||||
String height = "Picked at " + heightAboveSea(choice.placementHeight()) + " · ";
|
||||
String height = heightAboveSea(choice.placementHeight()) + " · ";
|
||||
if (choice.fallback()) {
|
||||
return height + "no range covers it, closest range wins";
|
||||
return height + "no range covers it, closest wins";
|
||||
}
|
||||
if (choice.rivalCount() == 0) {
|
||||
return height + "the only biome in range";
|
||||
return height + "only biome in range";
|
||||
}
|
||||
return height + "beat " + choice.rivalCount() + (choice.rivalCount() == 1 ? " other biome" : " other biomes") + " in range";
|
||||
return height + "beat " + choice.rivalCount() + (choice.rivalCount() == 1 ? " other" : " others") + " in range";
|
||||
}
|
||||
|
||||
private static Section locationSection(double worldX, double worldZ, boolean detailed) {
|
||||
int blockX = MapCanvas.floorWorldCoordinate(worldX);
|
||||
int blockZ = MapCanvas.floorWorldCoordinate(worldZ);
|
||||
List<Row> rows = new ArrayList<>();
|
||||
rows.add(new Row("Block", blockX + ", " + blockZ));
|
||||
rows.add(new Row("Chunk", (blockX >> 4) + ", " + (blockZ >> 4)));
|
||||
if (detailed) {
|
||||
rows.add(new Row("Region file", "r." + (blockX >> 9) + "." + (blockZ >> 9) + ".mca"));
|
||||
}
|
||||
return new Section("Location", rows);
|
||||
}
|
||||
|
||||
private static void addFilesSection(List<Section> sections, MapLabels.Label subject, MapLabels.Label region) {
|
||||
List<Row> rows = new ArrayList<>();
|
||||
addPackFile(rows, subject);
|
||||
if (subject != null && subject.kind() != MapLabels.Kind.REGION) {
|
||||
addPackFile(rows, region);
|
||||
}
|
||||
if (!rows.isEmpty()) {
|
||||
sections.add(new Section("Pack files", rows));
|
||||
}
|
||||
}
|
||||
|
||||
// The key is the path under the type's folder, so folder, key and extension give the file within the pack.
|
||||
private static void addPackFile(List<Row> rows, MapLabels.Label label) {
|
||||
if (label == null || !(label.subject() instanceof IrisRegistrant registrant)) {
|
||||
return;
|
||||
}
|
||||
String kind = label.kind() == MapLabels.Kind.REGION ? "Region" : "Biome";
|
||||
File file = registrant.getLoadFile();
|
||||
String fileName = file == null ? "" : file.getName();
|
||||
int extensionStart = fileName.lastIndexOf('.');
|
||||
String extension = extensionStart < 0 ? "" : fileName.substring(extensionStart);
|
||||
rows.add(new Row(kind, registrant.getFolderName() + "/" + label.key() + extension));
|
||||
}
|
||||
|
||||
private static String heightAboveSea(double height) {
|
||||
@@ -141,20 +277,17 @@ final class MapHoverCard {
|
||||
return rounded < 0 ? -rounded + " below sea" : rounded + " above sea";
|
||||
}
|
||||
|
||||
private static void addSource(List<String> lines, MapLabels.Label label) {
|
||||
if (label == null || !(label.subject() instanceof IrisRegistrant registrant)) {
|
||||
return;
|
||||
}
|
||||
String kind = label.kind() == MapLabels.Kind.REGION ? "Region" : "Biome";
|
||||
lines.add(kind + " key " + label.key());
|
||||
File file = registrant.getLoadFile();
|
||||
if (file != null) {
|
||||
File folder = file.getParentFile();
|
||||
lines.add(kind + " file " + (folder == null ? "" : folder.getName() + "/") + file.getName());
|
||||
}
|
||||
private static String noiseText(double noise) {
|
||||
return String.format(Locale.ROOT, "%.3f", noise);
|
||||
}
|
||||
|
||||
private static String capitalize(String text) {
|
||||
return Character.toUpperCase(text.charAt(0)) + text.substring(1);
|
||||
}
|
||||
|
||||
record Section(String title, List<Row> rows) {
|
||||
}
|
||||
|
||||
record Row(String label, String value) {
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user