Choose procedural seeds by typing or reroll

Seed box lists tried seeds; bar keeps its size.
This commit is contained in:
piexel committed 2026-10-21 14:00:36 +02:00
1 parent f30f761ae6
commit 93f5615f00
3 files changed
+186 -32

No files matched your search

@@ -214,6 +214,7 @@ final class ObjectExplorer {
int variantIndex = reloadingShownObject ? shownVariantIndex : 0;
shownKey = key;
if (!reloadingShownObject) {
variantPicker.forgetTriedSeeds();
showVariantList(List.of(), OptionalLong.empty(), OptionalLong.empty(), 0);
}
long revision = startLoading(key);
@@ -364,8 +365,7 @@ final class ObjectExplorer {
}
private String galleryHeading() {
String seed = shownSeed.isPresent() ? " · seed " + shownSeed.getAsLong() : "";
return String.format(Locale.ROOT, "%s · %d variants%s", shownKey, shownVariants.size(), seed);
return String.format(Locale.ROOT, "%s · %d variants%s", shownKey, shownVariants.size(), seedNote());
}
private static ObjectMesh.Bounds largestSize(List<ObjectMesh> meshes) {
@@ -426,9 +426,17 @@ final class ObjectExplorer {
String cut = layerCut.isCutting()
? String.format(Locale.ROOT, " · showing layers 1-%d of %d", layerCut.shownLayerCount(), layerCut.layerCount())
: "";
String seed = shownSeed.isPresent() ? " · seed " + shownSeed.getAsLong() : "";
statusBar.showSummary(String.format(Locale.ROOT, "%s%s · %,d blocks · %,d faces drawn%s",
summary.key(), seed, summary.blockCount(), mesh.faceCount(), cut));
summary.key(), seedNote(), summary.blockCount(), mesh.faceCount(), cut));
}
// A seed being tried is not in the pack until its "seed" field says so.
private String seedNote() {
if (shownSeed.isEmpty()) {
return "";
}
String note = " · seed " + shownSeed.getAsLong();
return shownSeed.equals(packSeed) ? note : note + " (the pack has " + packSeed.getAsLong() + ")";
}
private void showSeeThrough(Set<String> blockTypes) {
@@ -0,0 +1,160 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.core.gui;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.UIManager;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalLong;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.LongConsumer;
/**
* Where the seed of a procedural object's variants is chosen: typed in, picked again from the seeds tried on this
* object with the pack's own first, or rerolled at random. It keeps its size whatever it shows, so the bar it sits in
* never shifts under the mouse.
*/
final class ObjectSeedChooser extends JPanel {
private static final long serialVersionUID = 3127745038254406118L;
private static final int FIELD_WIDTH = 120;
private static final int TRIED_SEEDS_KEPT = 12;
// Small enough to read back and type, and far more seeds than anyone tries.
private static final int RANDOM_SEED_BOUND = 1_000_000;
private final JComboBox<Long> seeds = new JComboBox<>();
private final LongConsumer onSeedChosen;
private final List<Long> triedSeedsNewestFirst = new ArrayList<>();
private OptionalLong packSeed = OptionalLong.empty();
private long shownSeed;
private boolean showingSeeds;
/**
* @param onSeedChosen told the seed to bake the variants from whenever the user chooses another one
*/
ObjectSeedChooser(LongConsumer onSeedChosen) {
super(new BorderLayout(StudioTheme.scale(4), 0));
this.onSeedChosen = onSeedChosen;
setOpaque(false);
JLabel label = new JLabel("Seed");
label.setFont(StudioTheme.smallFont());
label.setForeground(StudioTheme.SECONDARY_TEXT);
seeds.setEditable(true);
seeds.setFont(StudioTheme.smallFont());
seeds.setToolTipText("Type a seed and press Enter, or pick one tried before");
seeds.setRenderer(new PackSeedMarker());
seeds.setPreferredSize(new Dimension(StudioTheme.scale(FIELD_WIDTH), seeds.getPreferredSize().height));
seeds.addActionListener(event -> {
if (!showingSeeds) {
EventQueue.invokeLater(this::chooseEnteredSeed);
}
});
add(label, BorderLayout.WEST);
add(seeds, BorderLayout.CENTER);
add(StudioToolbar.button("Reroll", "Bake the variants from a random seed", this::reroll), BorderLayout.EAST);
}
/** Starts a new object's list of tried seeds. */
void forgetTriedSeeds() {
triedSeedsNewestFirst.clear();
}
void showSeed(long shown, OptionalLong pack) {
shownSeed = shown;
packSeed = pack;
if (!isPackSeed(shown)) {
rememberTried(shown);
}
DefaultComboBoxModel<Long> choices = new DefaultComboBoxModel<>();
pack.ifPresent(choices::addElement);
triedSeedsNewestFirst.forEach(choices::addElement);
showingSeeds = true;
try {
seeds.setModel(choices);
seeds.setSelectedItem(shown);
} finally {
showingSeeds = false;
}
}
private void rememberTried(long seed) {
triedSeedsNewestFirst.remove(seed);
triedSeedsNewestFirst.add(0, seed);
while (triedSeedsNewestFirst.size() > TRIED_SEEDS_KEPT) {
triedSeedsNewestFirst.remove(triedSeedsNewestFirst.size() - 1);
}
}
// A pick from the list arrives as a seed, anything typed as text.
private void chooseEnteredSeed() {
OptionalLong entered = parse(seeds.getSelectedItem());
if (entered.isEmpty()) {
UIManager.getLookAndFeel().provideErrorFeedback(seeds);
showSeed(shownSeed, packSeed);
return;
}
if (entered.getAsLong() != shownSeed) {
onSeedChosen.accept(entered.getAsLong());
}
}
private static OptionalLong parse(Object entered) {
if (entered instanceof Long seed) {
return OptionalLong.of(seed);
}
try {
return OptionalLong.of(Long.parseLong(String.valueOf(entered).trim()));
} catch (NumberFormatException notASeed) {
return OptionalLong.empty();
}
}
private void reroll() {
long seed;
do {
seed = ThreadLocalRandom.current().nextInt(RANDOM_SEED_BOUND);
} while (seed == shownSeed);
onSeedChosen.accept(seed);
}
private boolean isPackSeed(Object seed) {
return packSeed.isPresent() && seed instanceof Long value && value == packSeed.getAsLong();
}
private final class PackSeedMarker extends DefaultListCellRenderer {
private static final long serialVersionUID = -6061513307766393251L;
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean selected, boolean focused) {
super.getListCellRendererComponent(list, value, index, selected, focused);
if (index >= 0 && isPackSeed(value)) {
setText(value + " (pack)");
}
return this;
}
}
}
@@ -43,48 +43,38 @@ final class ObjectVariantPicker extends JPanel {
private static final long serialVersionUID = -2716301839488217455L;
private static final int BOTTOM_MARGIN = 14;
private static final int VARIANT_LABEL_WIDTH = 110;
private static final int SEED_LABEL_WIDTH = 100;
private static final int SECTION_GAP = 10;
private static final String VARIANT_SHORTCUT_HINT = " · [ and ]";
private static final String COMPARE_SHORTCUT_HINT = " · G";
private final JLabel shownVariant = label(VARIANT_LABEL_WIDTH);
private final JLabel shownSeed = label(SEED_LABEL_WIDTH);
private final JComponent variantStepper;
private final JComponent seedStepper;
private final JButton packSeedButton;
private final ObjectSeedChooser seedChooser;
private final JButton compareButton;
private final IntConsumer onVariantChosen;
private final LongConsumer onSeedChosen;
private int variantCount;
private int shownIndex;
private OptionalLong seed = OptionalLong.empty();
private OptionalLong packSeed = OptionalLong.empty();
private boolean seeded;
private int coveredLeftWidth;
/**
* @param onVariantChosen told the index of the variant to show whenever the user steps to another one
* @param onSeedChosen told the seed to bake the variants from whenever the user steps to another one
* @param onSeedChosen told the seed to bake the variants from whenever the user chooses another one
* @param onCompareToggled told when the user switches between one variant and all of them
*/
ObjectVariantPicker(IntConsumer onVariantChosen, LongConsumer onSeedChosen, Runnable onCompareToggled) {
this.onVariantChosen = onVariantChosen;
this.onSeedChosen = onSeedChosen;
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setOpaque(false);
setBorder(StudioTheme.emptyBorder(3, 4, 3, 4));
variantStepper = stepper(shownVariant,
StudioToolbar.button("‹", "Previous variant" + VARIANT_SHORTCUT_HINT, () -> stepBy(-1)),
StudioToolbar.button("›", "Next variant" + VARIANT_SHORTCUT_HINT, () -> stepBy(1)));
seedStepper = stepper(shownSeed,
StudioToolbar.button("‹", "Bake the variants from the previous seed", () -> stepSeedBy(-1)),
StudioToolbar.button("›", "Bake the variants from the next seed", () -> stepSeedBy(1)));
packSeedButton = StudioToolbar.button("Pack seed", "", () -> packSeed.ifPresent(onSeedChosen::accept));
seedChooser = new ObjectSeedChooser(onSeedChosen);
compareButton = StudioToolbar.button("", "", onCompareToggled);
add(variantStepper);
add(Box.createHorizontalStrut(StudioTheme.scale(SECTION_GAP)));
add(seedStepper);
add(packSeedButton);
add(seedChooser);
add(Box.createHorizontalStrut(StudioTheme.scale(SECTION_GAP)));
add(compareButton);
showComparing(false);
@@ -153,19 +143,19 @@ final class ObjectVariantPicker extends JPanel {
}
/**
* Shows the seed the variants are baked from, with a way back to the pack's own seed once another is chosen.
* Shows the seed the variants are baked from; empty for objects not generated from a seed.
*/
void showSeed(OptionalLong shown, OptionalLong pack) {
seed = shown;
packSeed = pack;
shownSeed.setText(shown.isPresent() ? "Seed " + shown.getAsLong() : " ");
seedStepper.setVisible(shown.isPresent());
boolean explored = shown.isPresent() && !shown.equals(pack);
packSeedButton.setVisible(explored);
pack.ifPresent(value -> packSeedButton.setToolTipText("Back to the seed the pack sets, " + value));
seeded = shown.isPresent();
seedChooser.setVisible(seeded);
shown.ifPresent(seed -> seedChooser.showSeed(seed, pack));
showWhenUseful();
}
void forgetTriedSeeds() {
seedChooser.forgetTriedSeeds();
}
void showComparing(boolean comparing) {
compareButton.setText(comparing ? "3D view" : "All variants");
compareButton.setToolTipText((comparing ? "Show the chosen variant in 3D" : "Show every variant side by side") + COMPARE_SHORTCUT_HINT);
@@ -181,12 +171,8 @@ final class ObjectVariantPicker extends JPanel {
onVariantChosen.accept(shownIndex);
}
private void stepSeedBy(int step) {
seed.ifPresent(current -> onSeedChosen.accept(current + step));
}
private void showWhenUseful() {
setVisible(variantCount > 1 || seed.isPresent());
setVisible(variantCount > 1 || seeded);
relayout();
}