291 lines
12 KiB
Java
291 lines
12 KiB
Java
package de.lucas.litematica.gui;
|
|
|
|
import de.lucas.litematica.NeoLitematica;
|
|
import de.lucas.litematica.config.ModConfig;
|
|
import de.lucas.litematica.config.RenderMode;
|
|
import de.lucas.litematica.placement.PlacementManager;
|
|
import de.lucas.litematica.placement.SchematicPlacement;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.client.gui.GuiGraphics;
|
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
|
import net.minecraft.client.gui.components.Button;
|
|
import net.minecraft.client.gui.components.CycleButton;
|
|
import net.minecraft.client.gui.components.ObjectSelectionList;
|
|
import net.minecraft.client.gui.components.Tooltip;
|
|
import net.minecraft.client.gui.screens.ConfirmScreen;
|
|
import net.minecraft.client.gui.screens.Screen;
|
|
import net.minecraft.client.gui.screens.options.controls.KeyBindsScreen;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Vec3i;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.util.Mth;
|
|
import net.neoforged.fml.ModList;
|
|
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/** Overview of all placements in this dimension plus quick render settings. Opened with Alt+M. */
|
|
public class MainScreen extends Screen {
|
|
private static final int SIDE_WIDTH = 150;
|
|
|
|
@Nullable
|
|
private final Screen parent;
|
|
private PlacementList list;
|
|
private Button editButton;
|
|
private Button toggleButton;
|
|
private Button moveHereButton;
|
|
private Button removeButton;
|
|
|
|
public MainScreen(@Nullable Screen parent) {
|
|
super(Component.translatable("neolitematica.screen.main.title"));
|
|
this.parent = parent;
|
|
}
|
|
|
|
@Override
|
|
protected void init() {
|
|
int bottom = this.height - 80;
|
|
int listWidth = this.width - SIDE_WIDTH - 30;
|
|
this.list = new PlacementList(this.minecraft, listWidth, bottom - 26, 24);
|
|
this.list.setX(10);
|
|
this.addRenderableWidget(this.list);
|
|
|
|
int sx = this.width - SIDE_WIDTH - 10;
|
|
int y = 24;
|
|
this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.load_schematic"),
|
|
b -> this.minecraft.setScreen(new SchematicBrowserScreen(this))).bounds(sx, y, SIDE_WIDTH, 20).build());
|
|
y += 28;
|
|
this.editButton = this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.edit"),
|
|
b -> this.withSelected(p -> this.minecraft.setScreen(new PlacementScreen(this, p)))).bounds(sx, y, SIDE_WIDTH, 20).build());
|
|
y += 22;
|
|
this.toggleButton = this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.toggle_placement"),
|
|
b -> this.withSelected(p -> PlacementManager.get().setEnabled(p, !p.enabled()))).bounds(sx, y, SIDE_WIDTH, 20).build());
|
|
y += 22;
|
|
this.moveHereButton = this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.move_here"),
|
|
b -> this.withSelected(p -> PlacementManager.get().setOrigin(p, this.minecraft.player.blockPosition())))
|
|
.tooltip(Tooltip.create(Component.translatable("neolitematica.button.move_here.tooltip")))
|
|
.bounds(sx, y, SIDE_WIDTH, 20).build());
|
|
y += 22;
|
|
this.removeButton = this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.remove"),
|
|
b -> this.withSelected(this::confirmRemove)).bounds(sx, y, SIDE_WIDTH, 20).build());
|
|
|
|
int cx = this.width / 2;
|
|
int row = bottom + 6;
|
|
this.addRenderableWidget(CycleButton.onOffBuilder(ModConfig.RENDERING_ENABLED.get())
|
|
.create(cx - 155, row, 150, 20, Component.translatable("neolitematica.option.rendering"),
|
|
(b, value) -> ModConfig.set(ModConfig.RENDERING_ENABLED, value)));
|
|
this.addRenderableWidget(CycleButton.<RenderMode>builder(RenderMode::getTranslatedName)
|
|
.withValues(RenderMode.values())
|
|
.withInitialValue(ModConfig.RENDER_MODE.get())
|
|
.withTooltip(mode -> Tooltip.create(Component.translatable("neolitematica.render_mode." + mode.name().toLowerCase() + ".tooltip")))
|
|
.create(cx + 5, row, 150, 20, Component.translatable("neolitematica.option.render_mode"),
|
|
(b, value) -> ModConfig.set(ModConfig.RENDER_MODE, value)));
|
|
row += 22;
|
|
this.addRenderableWidget(new ConfigSlider(cx - 155, row, "neolitematica.option.ghost_alpha",
|
|
0.15, 1.0, ModConfig.GHOST_ALPHA.get(), true) {
|
|
@Override
|
|
protected void apply(double value) {
|
|
ModConfig.GHOST_ALPHA.set(value);
|
|
}
|
|
});
|
|
this.addRenderableWidget(new ConfigSlider(cx + 5, row, "neolitematica.option.render_distance",
|
|
2, 48, ModConfig.RENDER_DISTANCE.get(), false) {
|
|
@Override
|
|
protected void apply(double value) {
|
|
ModConfig.RENDER_DISTANCE.set((int) Math.round(value));
|
|
}
|
|
});
|
|
row += 22;
|
|
this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.more_settings"), b -> this.openConfig())
|
|
.bounds(cx - 155, row, 100, 20).build());
|
|
this.addRenderableWidget(Button.builder(Component.translatable("neolitematica.button.controls"),
|
|
b -> this.minecraft.setScreen(new KeyBindsScreen(this, this.minecraft.options))).bounds(cx - 50, row, 100, 20).build());
|
|
this.addRenderableWidget(Button.builder(Component.translatable("gui.done"), b -> this.onClose())
|
|
.bounds(cx + 55, row, 100, 20).build());
|
|
|
|
this.list.refresh();
|
|
this.updateButtons();
|
|
}
|
|
|
|
private void openConfig() {
|
|
ModList.get().getModContainerById(NeoLitematica.MOD_ID).ifPresent(container ->
|
|
this.minecraft.setScreen(new ConfigurationScreen(container, this)));
|
|
}
|
|
|
|
private void confirmRemove(SchematicPlacement placement) {
|
|
this.minecraft.setScreen(new ConfirmScreen(confirmed -> {
|
|
if (confirmed) {
|
|
PlacementManager.get().remove(placement);
|
|
}
|
|
this.minecraft.setScreen(this);
|
|
}, Component.translatable("neolitematica.screen.remove.title"),
|
|
Component.translatable("neolitematica.screen.remove.message", placement.name())));
|
|
}
|
|
|
|
private void withSelected(java.util.function.Consumer<SchematicPlacement> action) {
|
|
PlacementList.Entry entry = this.list.getSelected();
|
|
if (entry != null) {
|
|
action.accept(entry.placement);
|
|
this.updateButtons();
|
|
}
|
|
}
|
|
|
|
private void updateButtons() {
|
|
boolean selected = this.list.getSelected() != null;
|
|
this.editButton.active = selected;
|
|
this.toggleButton.active = selected;
|
|
this.moveHereButton.active = selected && this.minecraft.player != null;
|
|
this.removeButton.active = selected;
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
this.updateButtons();
|
|
}
|
|
|
|
@Override
|
|
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
|
|
super.render(graphics, mouseX, mouseY, partialTick);
|
|
graphics.drawCenteredString(this.font, this.title, this.width / 2, 8, 0xFFFFFF);
|
|
if (this.list.children().isEmpty()) {
|
|
graphics.drawCenteredString(this.font, Component.translatable("neolitematica.screen.main.empty"),
|
|
10 + this.list.getWidth() / 2, 60, 0xA0A0A0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isPauseScreen() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void onClose() {
|
|
this.minecraft.setScreen(this.parent);
|
|
}
|
|
|
|
/** List of placements in the current dimension. */
|
|
class PlacementList extends ObjectSelectionList<PlacementList.Entry> {
|
|
PlacementList(Minecraft minecraft, int width, int height, int y) {
|
|
super(minecraft, width, height, y, 24);
|
|
}
|
|
|
|
void refresh() {
|
|
this.clearEntries();
|
|
SchematicPlacement selected = PlacementManager.get().selected();
|
|
for (SchematicPlacement p : PlacementManager.get().inCurrentDimension()) {
|
|
Entry entry = new Entry(p);
|
|
this.addEntry(entry);
|
|
if (p == selected) {
|
|
this.setSelected(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setSelected(@Nullable Entry entry) {
|
|
super.setSelected(entry);
|
|
if (entry != null) {
|
|
PlacementManager.get().select(entry.placement);
|
|
}
|
|
MainScreen.this.updateButtons();
|
|
}
|
|
|
|
@Override
|
|
public int getRowWidth() {
|
|
return this.width - 16;
|
|
}
|
|
|
|
@Override
|
|
protected int getScrollbarPosition() {
|
|
return this.getX() + this.width - 6;
|
|
}
|
|
|
|
class Entry extends ObjectSelectionList.Entry<Entry> {
|
|
final SchematicPlacement placement;
|
|
private long lastClick;
|
|
|
|
Entry(SchematicPlacement placement) {
|
|
this.placement = placement;
|
|
}
|
|
|
|
@Override
|
|
public void render(GuiGraphics g, int index, int top, int left, int width, int height, int mouseX, int mouseY,
|
|
boolean hovering, float partialTick) {
|
|
SchematicPlacement p = this.placement;
|
|
int nameColor = p.enabled() ? 0xFFFFFF : 0x808080;
|
|
String marker = p.enabled() ? "● " : "○ ";
|
|
g.drawString(MainScreen.this.font, marker + p.name(), left + 2, top + 2, nameColor, false);
|
|
Component detail;
|
|
if (p.loadError() != null) {
|
|
detail = Component.literal(p.loadError()).withStyle(ChatFormatting.RED);
|
|
} else if (p.schematic() == null) {
|
|
detail = Component.translatable("neolitematica.hud.loading").withStyle(ChatFormatting.GRAY);
|
|
} else {
|
|
BlockPos o = p.origin();
|
|
Vec3i s = p.placedSize();
|
|
detail = Component.translatable("neolitematica.list.placement_detail", o.getX(), o.getY(), o.getZ(),
|
|
s.getX(), s.getY(), s.getZ(), p.transform().degrees()).withStyle(ChatFormatting.GRAY);
|
|
}
|
|
g.drawString(MainScreen.this.font, MainScreen.this.font.substrByWidth(detail, width - 6).getString(),
|
|
left + 2, top + 12, 0xA0A0A0, false);
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
long now = net.minecraft.Util.getMillis();
|
|
if (now - this.lastClick < 250L && PlacementList.this.getSelected() == this) {
|
|
MainScreen.this.minecraft.setScreen(new PlacementScreen(MainScreen.this, this.placement));
|
|
}
|
|
this.lastClick = now;
|
|
PlacementList.this.setSelected(this);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public Component getNarration() {
|
|
return Component.literal(this.placement.name());
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Slider bound to a numeric config value; applies live while dragging and writes the file when released. */
|
|
abstract static class ConfigSlider extends AbstractSliderButton {
|
|
private final String key;
|
|
private final double min;
|
|
private final double max;
|
|
private final boolean percent;
|
|
|
|
ConfigSlider(int x, int y, String key, double min, double max, double value, boolean percent) {
|
|
super(x, y, 150, 20, Component.empty(), (value - min) / (max - min));
|
|
this.key = key;
|
|
this.min = min;
|
|
this.max = max;
|
|
this.percent = percent;
|
|
this.updateMessage();
|
|
}
|
|
|
|
private double current() {
|
|
return this.min + Mth.clamp(this.value, 0.0, 1.0) * (this.max - this.min);
|
|
}
|
|
|
|
@Override
|
|
protected void updateMessage() {
|
|
double v = this.current();
|
|
String text = this.percent ? Math.round(v * 100) + "%" : String.valueOf(Math.round(v));
|
|
this.setMessage(Component.translatable(this.key, text));
|
|
}
|
|
|
|
@Override
|
|
protected void applyValue() {
|
|
this.apply(this.current());
|
|
}
|
|
|
|
@Override
|
|
public void onRelease(double mouseX, double mouseY) {
|
|
super.onRelease(mouseX, mouseY);
|
|
ModConfig.SPEC.save();
|
|
}
|
|
|
|
protected abstract void apply(double value);
|
|
}
|
|
}
|