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.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 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(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 { 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); } }