Collapse regions in the biome inspector

Short tables, one-line single picks.
This commit is contained in:
piexel committed 2026-10-21 21:41:14 +02:00
1 parent 86ef09e768
commit 60ba82c6fb
4 files changed
+291 -54

No files matched your search

@@ -88,8 +88,11 @@ final class BiomeExplorer {
for (RegionPicks region : measurement.regions()) {
List<JComponent> charts = region.coverage() != null
? List.of(PickInspectorPanel.heightChart(region.coverage()))
: region.spreads().stream().map(spread -> (JComponent) new NoisePickChart(spread, "Biomes")).toList();
sections.add(new PickInspectorPanel.Section(region.name(), region.color(), region.share(), charts, region.notes()));
: region.spreads().stream()
.map(spread -> (JComponent) inspector.noiseChart(spread, "Biomes", region.key() + "/" + spread.title()))
.toList();
sections.add(new PickInspectorPanel.Section(region.key(), region.name(), region.color(), region.share(),
biomeCountText(region.biomeCount()), charts, region.notes()));
}
String detail = measurement.hybrid()
? "Hybrid terrain · each region picks biomes by height"
@@ -97,6 +100,10 @@ final class BiomeExplorer {
inspector.showRegionSections(detail, sections, measurement.notes());
}
private static String biomeCountText(int count) {
return count == 1 ? "1 biome" : count + " biomes";
}
private Measurement measurePicks(MapCensus.ViewArea area) {
Engine engine = engineSource.get();
IrisComplex complex = engine == null ? null : engine.getComplex();
@@ -165,27 +172,35 @@ final class BiomeExplorer {
private RegionPicks picks(Engine engine, int totalCount) {
double share = totalCount == 0 ? 0D : count / (double) totalCount;
String key = region.getLoadKey();
String name = region.getName();
int color = PickMeasuring.regionColor(engine, region);
List<String> notes = new ArrayList<>();
if (complex.isHybridTerrain()) {
RegionHeightCoverage coverage = RegionHeightCoverage.measure(PickMeasuring.biomeRanges(engine, region),
List<RegionHeightCoverage.BiomeRange> ranges = PickMeasuring.biomeRanges(engine, region);
RegionHeightCoverage coverage = RegionHeightCoverage.measure(ranges,
pickingHeights.stream().mapToInt(Integer::intValue).toArray());
notes.addAll(coverage.notes());
return new RegionPicks(name, color, share, List.of(), coverage, notes);
return new RegionPicks(key, name, color, share, ranges.size(), List.of(), coverage, notes);
}
List<TypedSpread> inView = spreads.stream().filter(typed -> typed.spread().sampleCount() > 0).toList();
PickMeasuring.addBiomeSpreadNotes(notes, inView);
PickMeasuring.addChildBiomeNote(notes, complex, region);
return new RegionPicks(name, color, share, inView.stream().map(TypedSpread::spread).toList(), null, notes);
int biomeCount = (int) inView.stream()
.flatMap(typed -> typed.spread().rows().stream())
.map(row -> row.option().key())
.distinct()
.count();
return new RegionPicks(key, name, color, share, biomeCount, inView.stream().map(TypedSpread::spread).toList(),
null, notes);
}
}
/**
* @param coverage how the region's biomes cover the heights in view in hybrid terrain, or null in biome terrain
*/
private record RegionPicks(String name, int color, double share, List<NoisePickSpread> spreads,
RegionHeightCoverage coverage, List<String> notes) {
private record RegionPicks(String key, String name, int color, double share, int biomeCount,
List<NoisePickSpread> spreads, RegionHeightCoverage coverage, List<String> notes) {
}
private record Measurement(boolean hybrid, List<RegionPicks> regions, List<String> notes) {
@@ -20,17 +20,26 @@ package com.volmit.iris.core.gui;
import javax.swing.JComponent;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* One noise that picks regions or biomes: the spread of its values in view above the bands that pick each option,
* and a table of how much of the noise each option is given and how much it gets in view.
* and a table of how much of the noise each option is given and how much it gets in view. A long table shows only
* the options with the most ground in view until asked for all; a noise with one option to pick is a single line.
*/
final class NoisePickChart extends JComponent {
private static final long serialVersionUID = 1873365510472853316L;
@@ -44,21 +53,36 @@ final class NoisePickChart extends JComponent {
private static final Color HISTOGRAM_BAR = new Color(0x9D, 0xA0, 0xA8, 150);
private static final Color BAND_DIVIDER = new Color(255, 255, 255, 70);
private static final String WIDEST_SHARE = "▲ 100.0%";
private static final int SHORT_TABLE_ROWS = 6;
private final NoisePickSpread spread;
private final List<NoisePickSpread.Row> rows;
private final String optionNoun;
private final Consumer<Boolean> onShowAllRowsChanged;
private boolean showsAllRows;
private Rectangle showAllToggleBounds;
/**
* @param optionNoun what the noise picks, as a table heading: "Regions" or "Biomes"
* @param optionNoun what the noise picks, as a table heading: "Regions" or "Biomes"
* @param showsAllRows whether a long table starts with all its rows
* @param onShowAllRowsChanged receives whether the table shows all its rows after the user asked for more or fewer
*/
NoisePickChart(NoisePickSpread spread, String optionNoun) {
NoisePickChart(NoisePickSpread spread, String optionNoun, boolean showsAllRows, Consumer<Boolean> onShowAllRowsChanged) {
this.spread = spread;
this.rows = spread.rows();
this.optionNoun = optionNoun;
this.showsAllRows = showsAllRows;
this.onShowAllRowsChanged = onShowAllRowsChanged;
setAlignmentX(LEFT_ALIGNMENT);
listenForShowAllToggle();
}
@Override
public Dimension getPreferredSize() {
if (hasSingleOption()) {
return new Dimension(StudioTheme.scale(PREFERRED_WIDTH), StudioTheme.scale(SECTION_GAP) + tableHeight()
+ StudioTheme.scale(BOTTOM_GAP));
}
int height = StudioTheme.scale(SECTION_GAP) + spreadHeight() + StudioTheme.scale(SECTION_GAP) + tableHeight()
+ StudioTheme.scale(BOTTOM_GAP);
return new Dimension(StudioTheme.scale(PREFERRED_WIDTH), height);
@@ -78,6 +102,10 @@ final class NoisePickChart extends JComponent {
int margin = StudioTheme.scale(MARGIN);
int width = getWidth() - margin * 2;
int top = StudioTheme.scale(SECTION_GAP);
if (hasSingleOption()) {
drawSingleOption(canvas, margin, top, width);
return;
}
drawSpread(canvas, margin, top, width);
drawTable(canvas, margin, top + spreadHeight() + StudioTheme.scale(SECTION_GAP), width);
} finally {
@@ -90,8 +118,63 @@ final class NoisePickChart extends JComponent {
+ StudioTheme.scale(2) + StudioTheme.scale(BAND_HEIGHT) + StudioTheme.scale(3) + smallMetrics().getHeight();
}
private boolean hasSingleOption() {
return rows.size() == 1;
}
private boolean isLongTable() {
return rows.size() > SHORT_TABLE_ROWS;
}
private int tableHeight() {
return sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP) + rowHeight() * spread.rows().size();
int toggleRows = isLongTable() ? 1 : 0;
return sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP) + rowHeight() * (shownRows().size() + toggleRows);
}
// A short table keeps the options with the most ground in view, still in the order the noise meets them.
private List<NoisePickSpread.Row> shownRows() {
if (showsAllRows || !isLongTable()) {
return rows;
}
Set<NoisePickSpread.Row> largest = rows.stream()
.sorted(Comparator.comparingDouble(NoisePickSpread.Row::actualShare).reversed())
.limit(SHORT_TABLE_ROWS)
.collect(Collectors.toSet());
return rows.stream().filter(largest::contains).toList();
}
private void listenForShowAllToggle() {
MouseAdapter toggle = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
if (showAllToggleBounds == null || !showAllToggleBounds.contains(event.getPoint())) {
return;
}
showsAllRows = !showsAllRows;
onShowAllRowsChanged.accept(showsAllRows);
revalidate();
repaint();
}
@Override
public void mouseMoved(MouseEvent event) {
boolean overToggle = showAllToggleBounds != null && showAllToggleBounds.contains(event.getPoint());
setCursor(overToggle ? Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) : null);
}
};
addMouseListener(toggle);
addMouseMotionListener(toggle);
}
private void drawSingleOption(Graphics2D canvas, int x, int top, int width) {
drawSectionTitle(canvas, spread.title(), x, top + sectionMetrics().getAscent());
int rowTop = top + sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP);
NoisePickSpread.Row row = rows.getFirst();
String only = "picked everywhere";
int nameRight = x + width - textMetrics().stringWidth(only) - StudioTheme.scale(10);
int baseline = drawOptionName(canvas, row, x, rowTop, nameRight);
canvas.setColor(StudioTheme.MUTED_TEXT);
drawRightAligned(canvas, only, x + width, baseline);
}
private void drawSpread(Graphics2D canvas, int x, int top, int width) {
@@ -151,20 +234,10 @@ final class NoisePickChart extends JComponent {
drawRightAligned(canvas, "WEIGHT", weightRight, titleBaseline);
drawRightAligned(canvas, "IN VIEW", actualRight, titleBaseline);
FontMetrics text = textMetrics();
int rowHeight = rowHeight();
int swatchSize = text.getAscent() - text.getDescent();
int rowTop = top + sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP);
List<NoisePickSpread.Row> rows = spread.rows();
for (NoisePickSpread.Row row : rows) {
int baseline = rowTop + (rowHeight + text.getAscent() - text.getDescent()) / 2;
int arc = StudioTheme.scale(3);
canvas.setColor(new Color(row.option().color()));
canvas.fillRoundRect(x, rowTop + (rowHeight - swatchSize) / 2, swatchSize, swatchSize, arc, arc);
canvas.setFont(StudioTheme.font());
canvas.setColor(StudioTheme.TEXT);
int nameLeft = x + swatchSize + StudioTheme.scale(7);
canvas.drawString(fit(canvas, distinctName(row, rows), nameRight - nameLeft), nameLeft, baseline);
for (NoisePickSpread.Row row : shownRows()) {
int baseline = drawOptionName(canvas, row, x, rowTop, nameRight);
canvas.setColor(StudioTheme.SECONDARY_TEXT);
drawRightAligned(canvas, percent(row.weightShare()), weightRight, baseline);
boolean farFromWeight = row.isFarBelowWeight() || row.isFarAboveWeight();
@@ -172,6 +245,35 @@ final class NoisePickChart extends JComponent {
drawRightAligned(canvas, actualText(row), actualRight, baseline);
rowTop += rowHeight;
}
showAllToggleBounds = isLongTable() ? drawShowAllToggle(canvas, x, rowTop) : null;
}
// Returns the row's text baseline.
private int drawOptionName(Graphics2D canvas, NoisePickSpread.Row row, int x, int rowTop, int nameRight) {
FontMetrics text = textMetrics();
int rowHeight = rowHeight();
int swatchSize = text.getAscent() - text.getDescent();
int baseline = rowTop + (rowHeight + text.getAscent() - text.getDescent()) / 2;
int arc = StudioTheme.scale(3);
canvas.setColor(new Color(row.option().color()));
canvas.fillRoundRect(x, rowTop + (rowHeight - swatchSize) / 2, swatchSize, swatchSize, arc, arc);
canvas.setFont(StudioTheme.font());
canvas.setColor(StudioTheme.TEXT);
int nameLeft = x + swatchSize + StudioTheme.scale(7);
canvas.drawString(fit(canvas, distinctName(row, rows), nameRight - nameLeft), nameLeft, baseline);
return baseline;
}
private Rectangle drawShowAllToggle(Graphics2D canvas, int x, int rowTop) {
String text = showsAllRows
? "Show fewer"
: "Show all " + rows.size() + " " + optionNoun.toLowerCase(Locale.ROOT);
FontMetrics metrics = textMetrics();
int baseline = rowTop + (rowHeight() + metrics.getAscent() - metrics.getDescent()) / 2;
canvas.setFont(StudioTheme.font());
canvas.setColor(StudioTheme.LINK);
canvas.drawString(text, x, baseline);
return new Rectangle(x, rowTop, metrics.stringWidth(text), rowHeight());
}
private int rowHeight() {
@@ -25,15 +25,20 @@ import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.Icon;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.DefaultCaret;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
@@ -41,16 +46,23 @@ import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
/**
* The inspector of the region and biome views: how noise picks the regions or biomes in view, measured against what
* is in view and put in words, with a switch for how the map draws the picks.
* is in view and put in words, with a switch for how the map draws the picks. What the user opens or closes stays so
* while the view is measured again.
*/
final class PickInspectorPanel extends JPanel {
private static final long serialVersionUID = -2874105263188240317L;
@@ -66,7 +78,12 @@ final class PickInspectorPanel extends JPanel {
private final JPanel cardPanel = new JPanel(cards);
private final JTextArea message = wrappingText();
private final JPanel details = new ViewportWidthPanel();
private final JScrollPane detailsScroll = StudioToolbar.scroll(details);
private final Set<String> expandedSectionKeys = new HashSet<>();
private final Set<String> fullTableKeys = new HashSet<>();
private final String picksName;
private boolean sectionsChosenByUser;
private String shownDetailsTitle;
/**
* @param picksName what the map shows when it draws the picks: "Regions" or "Biomes"
@@ -82,7 +99,7 @@ final class PickInspectorPanel extends JPanel {
cardPanel.add(buildMessageCard(), MESSAGE_CARD);
details.setLayout(new BoxLayout(details, BoxLayout.Y_AXIS));
details.setBackground(StudioTheme.PANEL_BACKGROUND);
cardPanel.add(StudioToolbar.scroll(details), DETAILS_CARD);
cardPanel.add(detailsScroll, DETAILS_CARD);
add(cardPanel, BorderLayout.CENTER);
showMeasuring(picksName);
}
@@ -99,13 +116,13 @@ final class PickInspectorPanel extends JPanel {
void showRegionPicks(NoisePickSpread spread, List<String> notes) {
showHeader("Regions", "Region noise, split by rarity");
nameFirstDisplay("Regions");
showDetails(List.of(new NoisePickChart(spread, "Regions")), notes);
showDetails(List.of(noiseChart(spread, "Regions", "regions")), notes);
}
void showBiomePicks(String regionName, List<NoisePickSpread> spreads, List<String> notes) {
showHeader(regionName, "Biome terrain · biome noise, split by rarity");
nameFirstDisplay("Biomes");
showDetails(spreads.stream().map(spread -> (JComponent) new NoisePickChart(spread, "Biomes")).toList(), notes);
showDetails(spreads.stream().map(spread -> (JComponent) noiseChart(spread, "Biomes", spread.title())).toList(), notes);
}
void showCoverage(String regionName, RegionHeightCoverage coverage, List<String> notes) {
@@ -115,22 +132,36 @@ final class PickInspectorPanel extends JPanel {
}
/**
* One section per region in view, each with how it picks its biomes, then notes about all of them.
* One collapsible section per region in view, each with how it picks its biomes, then notes about all of them.
* Until the user opens or closes one, only the first is open.
*/
void showRegionSections(String detailText, List<Section> sections, List<String> notes) {
showHeader(picksName, detailText);
nameFirstDisplay(picksName);
if (!sectionsChosenByUser) {
expandedSectionKeys.clear();
sections.stream().findFirst().ifPresent(first -> expandedSectionKeys.add(first.key()));
}
List<JComponent> components = new ArrayList<>();
for (Section section : sections) {
components.add(new SectionHeader(section));
components.addAll(section.charts());
if (!section.notes().isEmpty()) {
components.add(notesText(section.notes()));
}
components.add(new SectionPanel(section, expandedSectionKeys.contains(section.key())));
}
showDetails(components, notes);
}
/**
* A chart whose long table remembers, by this key, whether the user asked for all its rows.
*/
NoisePickChart noiseChart(NoisePickSpread spread, String optionNoun, String key) {
return new NoisePickChart(spread, optionNoun, fullTableKeys.contains(key), showsAll -> {
if (showsAll) {
fullTableKeys.add(key);
} else {
fullTableKeys.remove(key);
}
});
}
static RegionHeightChart heightChart(RegionHeightCoverage coverage) {
RegionHeightChart chart = new RegionHeightChart();
chart.setAlignmentX(LEFT_ALIGNMENT);
@@ -195,12 +226,25 @@ final class PickInspectorPanel extends JPanel {
detail.setText(detailText);
}
private void rememberSectionExpanded(String key, boolean expanded) {
sectionsChosenByUser = true;
if (expanded) {
expandedSectionKeys.add(key);
} else {
expandedSectionKeys.remove(key);
}
}
private void showMessage(String text) {
message.setText(text);
cards.show(cardPanel, MESSAGE_CARD);
}
// Measuring the same subject again keeps the scroll position, so panning the map doesn't jump the list.
private void showDetails(List<JComponent> charts, List<String> notes) {
boolean sameSubject = Objects.equals(title.getText(), shownDetailsTitle);
int scrollPosition = sameSubject ? detailsScroll.getVerticalScrollBar().getValue() : 0;
shownDetailsTitle = title.getText();
details.removeAll();
charts.forEach(details::add);
if (!notes.isEmpty()) {
@@ -208,8 +252,8 @@ final class PickInspectorPanel extends JPanel {
}
details.revalidate();
details.repaint();
details.scrollRectToVisible(new Rectangle());
cards.show(cardPanel, DETAILS_CARD);
SwingUtilities.invokeLater(() -> detailsScroll.getVerticalScrollBar().setValue(scrollPosition));
}
private static JTextArea notesText(List<String> notes) {
@@ -243,26 +287,73 @@ final class PickInspectorPanel extends JPanel {
/**
* One region of the biome view's inspector.
*
* @param share how much of the view the region covers
* @param share how much of the view the region covers
* @param summary what the closed section says about its content, like "7 biomes"
*/
record Section(String name, int color, double share, List<JComponent> charts, List<String> notes) {
record Section(String key, String name, int color, double share, String summary, List<JComponent> charts,
List<String> notes) {
}
/**
* A region's name in its colour above its charts, with how much of the view it covers.
* A region's header, which opens and closes the charts and notes below it.
*/
private final class SectionPanel extends JPanel {
private static final long serialVersionUID = 2318546098861530717L;
private final JPanel body = new JPanel();
private SectionPanel(Section section, boolean expanded) {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setOpaque(false);
setAlignmentX(LEFT_ALIGNMENT);
body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS));
body.setOpaque(false);
body.setAlignmentX(LEFT_ALIGNMENT);
section.charts().forEach(body::add);
if (!section.notes().isEmpty()) {
body.add(notesText(section.notes()));
}
body.setVisible(expanded);
add(new SectionHeader(section, body::isVisible, () -> toggle(section)));
add(body);
}
private void toggle(Section section) {
body.setVisible(!body.isVisible());
rememberSectionExpanded(section.key(), body.isVisible());
revalidate();
repaint();
}
}
/**
* A region's name in its colour behind an open or closed chevron, with what it holds and how much of the view it
* covers; clicking it opens or closes the region.
*/
private static final class SectionHeader extends JComponent {
private static final long serialVersionUID = -4730226930154581123L;
private static final int HEIGHT = 34;
private static final int HEIGHT = 32;
private static final int MARGIN = 16;
private static final int CHEVRON_WIDTH = 16;
private static final int SWATCH_SIZE = 12;
private static final int SWATCH_GAP = 8;
private static final int SUMMARY_GAP = 8;
private final Section section;
private final BooleanSupplier expanded;
private SectionHeader(Section section) {
private SectionHeader(Section section, BooleanSupplier expanded, Runnable onToggle) {
this.section = section;
this.expanded = expanded;
setAlignmentX(LEFT_ALIGNMENT);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
onToggle.run();
repaint();
}
});
}
@Override
@@ -283,24 +374,46 @@ final class PickInspectorPanel extends JPanel {
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
canvas.setColor(StudioTheme.SEPARATOR);
canvas.fillRect(0, 0, getWidth(), 1);
int margin = StudioTheme.scale(MARGIN);
int left = StudioTheme.scale(MARGIN) - StudioTheme.scale(4);
paintChevron(canvas, left);
left += StudioTheme.scale(CHEVRON_WIDTH);
int swatch = StudioTheme.scale(SWATCH_SIZE);
canvas.setColor(new Color(section.color()));
int arc = StudioTheme.scale(4);
canvas.fillRoundRect(margin, (getHeight() - swatch) / 2, swatch, swatch, arc, arc);
canvas.setFont(StudioTheme.boldFont());
FontMetrics metrics = canvas.getFontMetrics();
int baseline = (getHeight() + metrics.getAscent() - metrics.getDescent()) / 2;
canvas.setColor(StudioTheme.TEXT);
canvas.drawString(section.name(), margin + swatch + StudioTheme.scale(SWATCH_GAP), baseline);
String share = NoisePickChart.percent(section.share()) + " of view";
canvas.fillRoundRect(left, (getHeight() - swatch) / 2, swatch, swatch, arc, arc);
left += swatch + StudioTheme.scale(SWATCH_GAP);
canvas.setFont(StudioTheme.smallFont());
FontMetrics small = canvas.getFontMetrics();
String share = NoisePickChart.percent(section.share());
int shareLeft = getWidth() - StudioTheme.scale(MARGIN) - small.stringWidth(share);
canvas.setFont(StudioTheme.boldFont());
FontMetrics bold = canvas.getFontMetrics();
int baseline = (getHeight() + bold.getAscent() - bold.getDescent()) / 2;
canvas.setColor(StudioTheme.TEXT);
canvas.drawString(section.name(), left, baseline);
left += bold.stringWidth(section.name()) + StudioTheme.scale(SUMMARY_GAP);
canvas.setFont(StudioTheme.smallFont());
canvas.setColor(StudioTheme.MUTED_TEXT);
if (section.summary() != null && left + small.stringWidth(section.summary()) < shareLeft - StudioTheme.scale(SUMMARY_GAP)) {
canvas.drawString(section.summary(), left, baseline);
}
canvas.setColor(StudioTheme.SECONDARY_TEXT);
canvas.drawString(share, getWidth() - margin - canvas.getFontMetrics().stringWidth(share), baseline);
canvas.drawString(share, shareLeft, baseline);
} finally {
canvas.dispose();
}
}
private void paintChevron(Graphics2D canvas, int left) {
Icon chevron = UIManager.getIcon(expanded.getAsBoolean() ? "Tree.expandedIcon" : "Tree.collapsedIcon");
if (chevron == null) {
return;
}
chevron.paintIcon(this, canvas, left + (StudioTheme.scale(CHEVRON_WIDTH) - chevron.getIconWidth()) / 2,
(getHeight() - chevron.getIconHeight()) / 2);
}
}
/**
@@ -31,7 +31,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* Samples the map in view to measure how regions and biomes are picked, and puts what stands out in words; shared by
@@ -40,6 +39,7 @@ import java.util.stream.Collectors;
final class PickMeasuring {
static final List<InferredType> PLACED_TYPES = List.of(InferredType.LAND, InferredType.SHORE, InferredType.SEA);
private static final int SAMPLES_PER_SIDE = 96;
private static final int NAMED_IN_NOTES = 3;
private PickMeasuring() {
}
@@ -106,7 +106,6 @@ final class PickMeasuring {
return;
}
if (rows.size() == 1) {
notes.add(rows.getFirst().option().name() + " is the only " + noun + ", so the " + noun + " noise has nothing to pick.");
return;
}
if (spread.belowZeroShare() > 0D) {
@@ -120,7 +119,7 @@ final class PickMeasuring {
List<String> starved = rows.stream().filter(NoisePickSpread.Row::isFarBelowWeight).map(row -> row.option().name()).toList();
if (!starved.isEmpty()) {
boolean single = starved.size() == 1;
notes.add(String.join(", ", starved) + (single ? " gets" : " get") + " under half " + (single ? "its" : "their")
notes.add(namedFew(starved) + (single ? " gets" : " get") + " under half " + (single ? "its" : "their")
+ " weight in view (▼): the " + noun + " noise rarely reaches " + (single ? "its band." : "their bands."));
}
}
@@ -132,14 +131,14 @@ final class PickMeasuring {
}
static void addChildBiomeNote(List<String> notes, IrisComplex complex, IrisRegion region) {
String parents = PLACED_TYPES.stream()
List<String> parents = PLACED_TYPES.stream()
.flatMap(type -> complex.noisePickedBiomes(region, type).stream())
.filter(biome -> !biome.getChildren().isEmpty())
.map(IrisBiome::getName)
.distinct()
.collect(Collectors.joining(", "));
.toList();
if (!parents.isEmpty()) {
notes.add(parents + " then hand some spots to child biomes by a noise of their own.");
notes.add(namedFew(parents) + " then hand some spots to child biomes by a noise of their own.");
}
}
@@ -152,6 +151,14 @@ final class PickMeasuring {
: "Each weight is one over the rarity, so rarity 2 gets half the noise of rarity 1.");
}
// A long list of names buries the point of a note, so only the first few are named.
private static String namedFew(List<String> names) {
if (names.size() <= NAMED_IN_NOTES) {
return String.join(", ", names);
}
return String.join(", ", names.subList(0, NAMED_IN_NOTES)) + " and " + (names.size() - NAMED_IN_NOTES) + " more";
}
static String measuredNote() {
return "Measured on a " + SAMPLES_PER_SIDE + " × " + SAMPLES_PER_SIDE + " grid over the map in view.";
}