WorldSelectionList.java

net.minecraft.client.gui.screens.worldselection.WorldSelectionList

信息

  • 全限定名:net.minecraft.client.gui.screens.worldselection.WorldSelectionList
  • 类型:public class
  • 包:net.minecraft.client.gui.screens.worldselection
  • 源码路径:src/main/java/net/minecraft/client/gui/screens/worldselection/WorldSelectionList.java
  • 起始行号:L76
  • 继承:ObjectSelectionList<WorldSelectionList.Entry>
  • 职责:

    TODO

字段/常量

  • DATE_FORMAT

    • 类型: DateTimeFormatter
    • 修饰符: public static final
    • 源码定位: L77
    • 说明:

      TODO

  • ERROR_HIGHLIGHTED_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L78
    • 说明:

      TODO

  • ERROR_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L79
    • 说明:

      TODO

  • MARKED_JOIN_HIGHLIGHTED_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L80
    • 说明:

      TODO

  • MARKED_JOIN_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L81
    • 说明:

      TODO

  • WARNING_HIGHLIGHTED_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L82
    • 说明:

      TODO

  • WARNING_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L83
    • 说明:

      TODO

  • JOIN_HIGHLIGHTED_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L84
    • 说明:

      TODO

  • JOIN_SPRITE

    • 类型: Identifier
    • 修饰符: private static final
    • 源码定位: L85
    • 说明:

      TODO

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L86
    • 说明:

      TODO

  • FROM_NEWER_TOOLTIP_1

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L87
    • 说明:

      TODO

  • FROM_NEWER_TOOLTIP_2

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L88
    • 说明:

      TODO

  • SNAPSHOT_TOOLTIP_1

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L89
    • 说明:

      TODO

  • SNAPSHOT_TOOLTIP_2

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L90
    • 说明:

      TODO

  • WORLD_LOCKED_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L91
    • 说明:

      TODO

  • WORLD_REQUIRES_CONVERSION

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L92
    • 说明:

      TODO

  • INCOMPATIBLE_VERSION_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L93
    • 说明:

      TODO

  • WORLD_EXPERIMENTAL

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L94
    • 说明:

      TODO

  • screen

    • 类型: Screen
    • 修饰符: private final
    • 源码定位: L95
    • 说明:

      TODO

  • pendingLevels

    • 类型: CompletableFuture<List<LevelSummary>>
    • 修饰符: private
    • 源码定位: L96
    • 说明:

      TODO

  • currentlyDisplayedLevels

    • 类型: List<LevelSummary>
    • 修饰符: private
    • 源码定位: L97
    • 说明:

      TODO

  • loadingHeader

    • 类型: WorldSelectionList.LoadingHeader
    • 修饰符: private final
    • 源码定位: L98
    • 说明:

      TODO

  • entryType

    • 类型: WorldSelectionList.EntryType
    • 修饰符: private final
    • 源码定位: L99
    • 说明:

      TODO

  • filter

    • 类型: String
    • 修饰符: private
    • 源码定位: L100
    • 说明:

      TODO

  • hasPolled

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L101
    • 说明:

      TODO

  • onEntrySelect

    • 类型: Consumer<LevelSummary>
    • 修饰符: private final
    • 源码定位: L102
    • 说明:

      TODO

  • onEntryInteract

    • 类型: Consumer<WorldSelectionList.WorldListEntry>
    • 修饰符: private final
    • 源码定位: L103
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.gui.screens.worldselection.WorldSelectionList.Builder

    • 类型: class
    • 修饰符: public static
    • 源码定位: L286
    • 说明:

      TODO

  • net.minecraft.client.gui.screens.worldselection.WorldSelectionList.Entry

    • 类型: class
    • 修饰符: public abstract static
    • 源码定位: L345
    • 说明:

      TODO

  • net.minecraft.client.gui.screens.worldselection.WorldSelectionList.EntryType

    • 类型: enum
    • 修饰符: public static
    • 源码定位: L356
    • 说明:

      TODO

  • net.minecraft.client.gui.screens.worldselection.WorldSelectionList.LoadingHeader

    • 类型: class
    • 修饰符: public static
    • 源码定位: L362
    • 说明:

      TODO

  • net.minecraft.client.gui.screens.worldselection.WorldSelectionList.NoWorldsEntry

    • 类型: class
    • 修饰符: public static final
    • 源码定位: L388
    • 说明:

      TODO

  • net.minecraft.client.gui.screens.worldselection.WorldSelectionList.WorldListEntry

    • 类型: class
    • 修饰符: public final
    • 源码定位: L409
    • 说明:

      TODO

构造器

private WorldSelectionList(Screen screen, Minecraft minecraft, int width, int height, String filter, WorldSelectionList oldList, Consumer<LevelSummary> onEntrySelect, Consumer<WorldSelectionList.WorldListEntry> onEntryInteract, WorldSelectionList.EntryType entryType) @ L105

  • 构造器名:WorldSelectionList
  • 源码定位:L105
  • 修饰符:private

参数:

  • screen: Screen
  • minecraft: Minecraft
  • width: int
  • height: int
  • filter: String
  • oldList: WorldSelectionList
  • onEntrySelect: Consumer
  • onEntryInteract: Consumer<WorldSelectionList.WorldListEntry>
  • entryType: WorldSelectionList.EntryType

说明:

TODO

方法

下面的方法块按源码顺序生成。

protected void clearEntries() @ L133

  • 方法名:clearEntries
  • 源码定位:L133
  • 返回类型:void
  • 修饰符:protected

参数:

说明:

TODO

private List<LevelSummary> pollLevelsIgnoreErrors() @ L139

  • 方法名:pollLevelsIgnoreErrors
  • 源码定位:L139
  • 返回类型:List
  • 修饰符:private

参数:

说明:

TODO

public void reloadWorldList() @ L157

  • 方法名:reloadWorldList
  • 源码定位:L157
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) @ L161

  • 方法名:extractWidgetRenderState
  • 源码定位:L161
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • mouseX: int
  • mouseY: int
  • a: float

说明:

TODO

private void handleNewLevels(List<LevelSummary> levels) @ L171

  • 方法名:handleNewLevels
  • 源码定位:L171
  • 返回类型:void
  • 修饰符:private

参数:

  • levels: List

说明:

TODO

public void updateFilter(String newFilter) @ L189

  • 方法名:updateFilter
  • 源码定位:L189
  • 返回类型:void
  • 修饰符:public

参数:

  • newFilter: String

说明:

TODO

private CompletableFuture<List<LevelSummary>> loadLevels() @ L197

  • 方法名:loadLevels
  • 源码定位:L197
  • 返回类型:CompletableFuture<List>
  • 修饰符:private

参数:

说明:

TODO

private void fillLevels(String filter, List<LevelSummary> levels) @ L213

  • 方法名:fillLevels
  • 源码定位:L213
  • 返回类型:void
  • 修饰符:private

参数:

  • filter: String
  • levels: List

说明:

TODO

private boolean filterAccepts(String filter, LevelSummary level) @ L237

  • 方法名:filterAccepts
  • 源码定位:L237
  • 返回类型:boolean
  • 修饰符:private

参数:

  • filter: String
  • level: LevelSummary

说明:

TODO

private void notifyListUpdated() @ L241

  • 方法名:notifyListUpdated
  • 源码定位:L241
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private void handleLevelLoadFailure(Component message) @ L246

  • 方法名:handleLevelLoadFailure
  • 源码定位:L246
  • 返回类型:void
  • 修饰符:private

参数:

  • message: Component

说明:

TODO

public int getRowWidth() @ L250

  • 方法名:getRowWidth
  • 源码定位:L250
  • 返回类型:int
  • 修饰符:public

参数:

说明:

TODO

public void setSelected(WorldSelectionList.Entry selected) @ L255

  • 方法名:setSelected
  • 源码定位:L255
  • 返回类型:void
  • 修饰符:public

参数:

  • selected: WorldSelectionList.Entry

说明:

TODO

public Optional<WorldSelectionList.WorldListEntry> getSelectedOpt() @ L262

  • 方法名:getSelectedOpt
  • 源码定位:L262
  • 返回类型:Optional<WorldSelectionList.WorldListEntry>
  • 修饰符:public

参数:

说明:

TODO

public void returnToScreen() @ L267

  • 方法名:returnToScreen
  • 源码定位:L267
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public Screen getScreen() @ L272

  • 方法名:getScreen
  • 源码定位:L272
  • 返回类型:Screen
  • 修饰符:public

参数:

说明:

TODO

public void updateWidgetNarration(NarrationElementOutput output) @ L276

  • 方法名:updateWidgetNarration
  • 源码定位:L276
  • 返回类型:void
  • 修饰符:public

参数:

  • output: NarrationElementOutput

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class WorldSelectionList extends ObjectSelectionList<WorldSelectionList.Entry> {
    public static final DateTimeFormatter DATE_FORMAT = Util.localizedDateFormatter(FormatStyle.SHORT);
    private static final Identifier ERROR_HIGHLIGHTED_SPRITE = Identifier.withDefaultNamespace("world_list/error_highlighted");
    private static final Identifier ERROR_SPRITE = Identifier.withDefaultNamespace("world_list/error");
    private static final Identifier MARKED_JOIN_HIGHLIGHTED_SPRITE = Identifier.withDefaultNamespace("world_list/marked_join_highlighted");
    private static final Identifier MARKED_JOIN_SPRITE = Identifier.withDefaultNamespace("world_list/marked_join");
    private static final Identifier WARNING_HIGHLIGHTED_SPRITE = Identifier.withDefaultNamespace("world_list/warning_highlighted");
    private static final Identifier WARNING_SPRITE = Identifier.withDefaultNamespace("world_list/warning");
    private static final Identifier JOIN_HIGHLIGHTED_SPRITE = Identifier.withDefaultNamespace("world_list/join_highlighted");
    private static final Identifier JOIN_SPRITE = Identifier.withDefaultNamespace("world_list/join");
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final Component FROM_NEWER_TOOLTIP_1 = Component.translatable("selectWorld.tooltip.fromNewerVersion1").withStyle(ChatFormatting.RED);
    private static final Component FROM_NEWER_TOOLTIP_2 = Component.translatable("selectWorld.tooltip.fromNewerVersion2").withStyle(ChatFormatting.RED);
    private static final Component SNAPSHOT_TOOLTIP_1 = Component.translatable("selectWorld.tooltip.snapshot1").withStyle(ChatFormatting.GOLD);
    private static final Component SNAPSHOT_TOOLTIP_2 = Component.translatable("selectWorld.tooltip.snapshot2").withStyle(ChatFormatting.GOLD);
    private static final Component WORLD_LOCKED_TOOLTIP = Component.translatable("selectWorld.locked").withStyle(ChatFormatting.RED);
    private static final Component WORLD_REQUIRES_CONVERSION = Component.translatable("selectWorld.conversion.tooltip").withStyle(ChatFormatting.RED);
    private static final Component INCOMPATIBLE_VERSION_TOOLTIP = Component.translatable("selectWorld.incompatible.tooltip").withStyle(ChatFormatting.RED);
    private static final Component WORLD_EXPERIMENTAL = Component.translatable("selectWorld.experimental");
    private final Screen screen;
    private CompletableFuture<List<LevelSummary>> pendingLevels;
    private @Nullable List<LevelSummary> currentlyDisplayedLevels;
    private final WorldSelectionList.LoadingHeader loadingHeader;
    private final WorldSelectionList.EntryType entryType;
    private String filter;
    private boolean hasPolled;
    private final @Nullable Consumer<LevelSummary> onEntrySelect;
    private final @Nullable Consumer<WorldSelectionList.WorldListEntry> onEntryInteract;
 
    private WorldSelectionList(
        Screen screen,
        Minecraft minecraft,
        int width,
        int height,
        String filter,
        @Nullable WorldSelectionList oldList,
        @Nullable Consumer<LevelSummary> onEntrySelect,
        @Nullable Consumer<WorldSelectionList.WorldListEntry> onEntryInteract,
        WorldSelectionList.EntryType entryType
    ) {
        super(minecraft, width, height, 0, 36);
        this.screen = screen;
        this.loadingHeader = new WorldSelectionList.LoadingHeader(minecraft);
        this.filter = filter;
        this.onEntrySelect = onEntrySelect;
        this.onEntryInteract = onEntryInteract;
        this.entryType = entryType;
        if (oldList != null) {
            this.pendingLevels = oldList.pendingLevels;
        } else {
            this.pendingLevels = this.loadLevels();
        }
 
        this.addEntry(this.loadingHeader);
        this.handleNewLevels(this.pollLevelsIgnoreErrors());
    }
 
    @Override
    protected void clearEntries() {
        this.children().forEach(WorldSelectionList.Entry::close);
        super.clearEntries();
    }
 
    private @Nullable List<LevelSummary> pollLevelsIgnoreErrors() {
        try {
            List<LevelSummary> completedLevels = this.pendingLevels.getNow(null);
            if (this.entryType == WorldSelectionList.EntryType.UPLOAD_WORLD) {
                if (completedLevels == null || this.hasPolled) {
                    return null;
                }
 
                this.hasPolled = true;
                completedLevels = completedLevels.stream().filter(LevelSummary::canUpload).toList();
            }
 
            return completedLevels;
        } catch (CancellationException | CompletionException var2) {
            return null;
        }
    }
 
    public void reloadWorldList() {
        this.pendingLevels = this.loadLevels();
    }
 
    @Override
    public void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) {
        List<LevelSummary> newLevels = this.pollLevelsIgnoreErrors();
        if (newLevels != this.currentlyDisplayedLevels) {
            this.handleNewLevels(newLevels);
        }
 
        super.extractWidgetRenderState(graphics, mouseX, mouseY, a);
    }
 
    private void handleNewLevels(@Nullable List<LevelSummary> levels) {
        if (levels != null) {
            if (levels.isEmpty()) {
                switch (this.entryType) {
                    case SINGLEPLAYER:
                        CreateWorldScreen.openFresh(this.minecraft, () -> this.minecraft.setScreen(null));
                        break;
                    case UPLOAD_WORLD:
                        this.clearEntries();
                        this.addEntry(new WorldSelectionList.NoWorldsEntry(Component.translatable("mco.upload.select.world.none"), this.screen.getFont()));
                }
            } else {
                this.fillLevels(this.filter, levels);
                this.currentlyDisplayedLevels = levels;
            }
        }
    }
 
    public void updateFilter(String newFilter) {
        if (this.currentlyDisplayedLevels != null && !newFilter.equals(this.filter)) {
            this.fillLevels(newFilter, this.currentlyDisplayedLevels);
        }
 
        this.filter = newFilter;
    }
 
    private CompletableFuture<List<LevelSummary>> loadLevels() {
        LevelStorageSource.LevelCandidates levelCandidates;
        try {
            levelCandidates = this.minecraft.getLevelSource().findLevelCandidates();
        } catch (LevelStorageException var3) {
            LOGGER.error("Couldn't load level list", (Throwable)var3);
            this.handleLevelLoadFailure(var3.getMessageComponent());
            return CompletableFuture.completedFuture(List.of());
        }
 
        return this.minecraft.getLevelSource().loadLevelSummaries(levelCandidates).exceptionally(throwable -> {
            this.minecraft.delayCrash(CrashReport.forThrowable(throwable, "Couldn't load level list"));
            return List.of();
        });
    }
 
    private void fillLevels(String filter, List<LevelSummary> levels) {
        List<WorldSelectionList.Entry> worldEntries = new ArrayList<>();
        Optional<WorldSelectionList.WorldListEntry> selectedOpt = this.getSelectedOpt();
        WorldSelectionList.WorldListEntry entryToSelect = null;
 
        for (LevelSummary level : levels.stream().filter(levelx -> this.filterAccepts(filter.toLowerCase(Locale.ROOT), levelx)).toList()) {
            WorldSelectionList.WorldListEntry worldListEntry = new WorldSelectionList.WorldListEntry(this, level);
            if (selectedOpt.isPresent() && selectedOpt.get().getLevelSummary().getLevelId().equals(worldListEntry.getLevelSummary().getLevelId())) {
                entryToSelect = worldListEntry;
            }
 
            worldEntries.add(worldListEntry);
        }
 
        this.removeEntries(this.children().stream().filter(entry -> !worldEntries.contains(entry)).toList());
        worldEntries.forEach(entry -> {
            if (!this.children().contains(entry)) {
                this.addEntry(entry);
            }
        });
        this.setSelected((WorldSelectionList.Entry)entryToSelect);
        this.notifyListUpdated();
    }
 
    private boolean filterAccepts(String filter, LevelSummary level) {
        return level.getLevelName().toLowerCase(Locale.ROOT).contains(filter) || level.getLevelId().toLowerCase(Locale.ROOT).contains(filter);
    }
 
    private void notifyListUpdated() {
        this.refreshScrollAmount();
        this.screen.triggerImmediateNarration(true);
    }
 
    private void handleLevelLoadFailure(Component message) {
        this.minecraft.setScreen(new ErrorScreen(Component.translatable("selectWorld.unable_to_load"), message));
    }
 
    @Override
    public int getRowWidth() {
        return 270;
    }
 
    public void setSelected(WorldSelectionList.@Nullable Entry selected) {
        super.setSelected(selected);
        if (this.onEntrySelect != null) {
            this.onEntrySelect.accept(selected instanceof WorldSelectionList.WorldListEntry entry ? entry.summary : null);
        }
    }
 
    public Optional<WorldSelectionList.WorldListEntry> getSelectedOpt() {
        WorldSelectionList.Entry selected = this.getSelected();
        return selected instanceof WorldSelectionList.WorldListEntry worldEntry ? Optional.of(worldEntry) : Optional.empty();
    }
 
    public void returnToScreen() {
        this.reloadWorldList();
        this.minecraft.setScreen(this.screen);
    }
 
    public Screen getScreen() {
        return this.screen;
    }
 
    @Override
    public void updateWidgetNarration(NarrationElementOutput output) {
        if (this.children().contains(this.loadingHeader)) {
            this.loadingHeader.updateNarration(output);
        } else {
            super.updateWidgetNarration(output);
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public static class Builder {
        private final Minecraft minecraft;
        private final Screen screen;
        private int width;
        private int height;
        private String filter = "";
        private WorldSelectionList.EntryType type = WorldSelectionList.EntryType.SINGLEPLAYER;
        private @Nullable WorldSelectionList oldList = null;
        private @Nullable Consumer<LevelSummary> onEntrySelect = null;
        private @Nullable Consumer<WorldSelectionList.WorldListEntry> onEntryInteract = null;
 
        public Builder(Minecraft minecraft, Screen screen) {
            this.minecraft = minecraft;
            this.screen = screen;
        }
 
        public WorldSelectionList.Builder width(int width) {
            this.width = width;
            return this;
        }
 
        public WorldSelectionList.Builder height(int height) {
            this.height = height;
            return this;
        }
 
        public WorldSelectionList.Builder filter(String filter) {
            this.filter = filter;
            return this;
        }
 
        public WorldSelectionList.Builder oldList(@Nullable WorldSelectionList oldList) {
            this.oldList = oldList;
            return this;
        }
 
        public WorldSelectionList.Builder onEntrySelect(Consumer<LevelSummary> onEntrySelect) {
            this.onEntrySelect = onEntrySelect;
            return this;
        }
 
        public WorldSelectionList.Builder onEntryInteract(Consumer<WorldSelectionList.WorldListEntry> onEntryInteract) {
            this.onEntryInteract = onEntryInteract;
            return this;
        }
 
        public WorldSelectionList.Builder uploadWorld() {
            this.type = WorldSelectionList.EntryType.UPLOAD_WORLD;
            return this;
        }
 
        public WorldSelectionList build() {
            return new WorldSelectionList(
                this.screen, this.minecraft, this.width, this.height, this.filter, this.oldList, this.onEntrySelect, this.onEntryInteract, this.type
            );
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public abstract static class Entry extends ObjectSelectionList.Entry<WorldSelectionList.Entry> implements AutoCloseable {
        @Override
        public void close() {
        }
 
        public @Nullable LevelSummary getLevelSummary() {
            return null;
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public static enum EntryType {
        SINGLEPLAYER,
        UPLOAD_WORLD;
    }
 
    @OnlyIn(Dist.CLIENT)
    public static class LoadingHeader extends WorldSelectionList.Entry {
        private static final Component LOADING_LABEL = Component.translatable("selectWorld.loading_list");
        private final Minecraft minecraft;
 
        public LoadingHeader(Minecraft minecraft) {
            this.minecraft = minecraft;
        }
 
        @Override
        public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            int labelX = (this.minecraft.screen.width - this.minecraft.font.width(LOADING_LABEL)) / 2;
            int labelY = this.getContentY() + (this.getContentHeight() - 9) / 2;
            graphics.text(this.minecraft.font, LOADING_LABEL, labelX, labelY, -1);
            String dots = LoadingDotsText.get(Util.getMillis());
            int dotsX = (this.minecraft.screen.width - this.minecraft.font.width(dots)) / 2;
            int dotsY = labelY + 9;
            graphics.text(this.minecraft.font, dots, dotsX, dotsY, -8355712);
        }
 
        @Override
        public Component getNarration() {
            return LOADING_LABEL;
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public static final class NoWorldsEntry extends WorldSelectionList.Entry {
        private final StringWidget stringWidget;
 
        public NoWorldsEntry(Component component, Font font) {
            this.stringWidget = new StringWidget(component, font);
        }
 
        @Override
        public Component getNarration() {
            return this.stringWidget.getMessage();
        }
 
        @Override
        public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            this.stringWidget
                .setPosition(this.getContentXMiddle() - this.stringWidget.getWidth() / 2, this.getContentYMiddle() - this.stringWidget.getHeight() / 2);
            this.stringWidget.extractRenderState(graphics, mouseX, mouseY, a);
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public final class WorldListEntry extends WorldSelectionList.Entry implements SelectableEntry {
        private static final int ICON_SIZE = 32;
        private final WorldSelectionList list;
        private final Minecraft minecraft;
        private final Screen screen;
        private final LevelSummary summary;
        private final FaviconTexture icon;
        private final StringWidget worldNameText;
        private final StringWidget idAndLastPlayedText;
        private final StringWidget infoText;
        private @Nullable Path iconFile;
 
        public WorldListEntry(WorldSelectionList list, LevelSummary summary) {
            Objects.requireNonNull(WorldSelectionList.this);
            super();
            this.list = list;
            this.minecraft = list.minecraft;
            this.screen = list.getScreen();
            this.summary = summary;
            this.icon = FaviconTexture.forWorld(this.minecraft.getTextureManager(), summary.getLevelId());
            this.iconFile = summary.getIcon();
            int maxTextWidth = list.getRowWidth() - this.getTextX() - 2;
            Component worldNameComponent = Component.literal(summary.getLevelName());
            this.worldNameText = new StringWidget(worldNameComponent, this.minecraft.font);
            this.worldNameText.setMaxWidth(maxTextWidth);
            if (this.minecraft.font.width(worldNameComponent) > maxTextWidth) {
                this.worldNameText.setTooltip(Tooltip.create(worldNameComponent));
            }
 
            String levelIdAndDate = summary.getLevelId();
            long lastPlayed = summary.getLastPlayed();
            if (lastPlayed != -1L) {
                ZonedDateTime lastPlayedTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastPlayed), ZoneId.systemDefault());
                levelIdAndDate = levelIdAndDate + " (" + WorldSelectionList.DATE_FORMAT.format(lastPlayedTime) + ")";
            }
 
            Component levelIdAndDateComponent = Component.literal(levelIdAndDate).withColor(-8355712);
            this.idAndLastPlayedText = new StringWidget(levelIdAndDateComponent, this.minecraft.font);
            this.idAndLastPlayedText.setMaxWidth(maxTextWidth);
            if (this.minecraft.font.width(levelIdAndDate) > maxTextWidth) {
                this.idAndLastPlayedText.setTooltip(Tooltip.create(levelIdAndDateComponent));
            }
 
            Component info = ComponentUtils.mergeStyles(summary.getInfo(), Style.EMPTY.withColor(-8355712));
            this.infoText = new StringWidget(info, this.minecraft.font);
            this.infoText.setMaxWidth(maxTextWidth);
            if (this.minecraft.font.width(info) > maxTextWidth) {
                this.infoText.setTooltip(Tooltip.create(info));
            }
 
            this.validateIconFile();
            this.loadIcon();
        }
 
        private void validateIconFile() {
            if (this.iconFile != null) {
                try {
                    BasicFileAttributes attributes = Files.readAttributes(this.iconFile, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
                    if (attributes.isSymbolicLink()) {
                        List<ForbiddenSymlinkInfo> issues = this.minecraft.directoryValidator().validateSymlink(this.iconFile);
                        if (!issues.isEmpty()) {
                            WorldSelectionList.LOGGER.warn("{}", ContentValidationException.getMessage(this.iconFile, issues));
                            this.iconFile = null;
                        } else {
                            attributes = Files.readAttributes(this.iconFile, BasicFileAttributes.class);
                        }
                    }
 
                    if (!attributes.isRegularFile()) {
                        this.iconFile = null;
                    }
                } catch (NoSuchFileException var3) {
                    this.iconFile = null;
                } catch (IOException var4) {
                    WorldSelectionList.LOGGER.error("could not validate symlink", (Throwable)var4);
                    this.iconFile = null;
                }
            }
        }
 
        @Override
        public Component getNarration() {
            Component entryNarration = Component.translatable(
                "narrator.select.world_info",
                this.summary.getLevelName(),
                Component.translationArg(new Date(this.summary.getLastPlayed())),
                this.summary.getInfo()
            );
            if (this.summary.isLocked()) {
                entryNarration = CommonComponents.joinForNarration(entryNarration, WorldSelectionList.WORLD_LOCKED_TOOLTIP);
            }
 
            if (this.summary.isExperimental()) {
                entryNarration = CommonComponents.joinForNarration(entryNarration, WorldSelectionList.WORLD_EXPERIMENTAL);
            }
 
            return Component.translatable("narrator.select", entryNarration);
        }
 
        @Override
        public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) {
            int textX = this.getTextX();
            this.worldNameText.setPosition(textX, this.getContentY() + 1);
            this.worldNameText.extractRenderState(graphics, mouseX, mouseY, a);
            this.idAndLastPlayedText.setPosition(textX, this.getContentY() + 9 + 3);
            this.idAndLastPlayedText.extractRenderState(graphics, mouseX, mouseY, a);
            this.infoText.setPosition(textX, this.getContentY() + 9 + 9 + 3);
            this.infoText.extractRenderState(graphics, mouseX, mouseY, a);
            graphics.blit(RenderPipelines.GUI_TEXTURED, this.icon.textureLocation(), this.getContentX(), this.getContentY(), 0.0F, 0.0F, 32, 32, 32, 32);
            if (this.list.entryType == WorldSelectionList.EntryType.SINGLEPLAYER && (this.minecraft.options.touchscreen().get() || hovered)) {
                graphics.fill(this.getContentX(), this.getContentY(), this.getContentX() + 32, this.getContentY() + 32, -1601138544);
                int relX = mouseX - this.getContentX();
                int relY = mouseY - this.getContentY();
                boolean isOverIcon = this.mouseOverIcon(relX, relY, 32);
                Identifier joinSprite = isOverIcon ? WorldSelectionList.JOIN_HIGHLIGHTED_SPRITE : WorldSelectionList.JOIN_SPRITE;
                Identifier warningSprite = isOverIcon ? WorldSelectionList.WARNING_HIGHLIGHTED_SPRITE : WorldSelectionList.WARNING_SPRITE;
                Identifier errorSprite = isOverIcon ? WorldSelectionList.ERROR_HIGHLIGHTED_SPRITE : WorldSelectionList.ERROR_SPRITE;
                Identifier joinWithErrorSprite = isOverIcon ? WorldSelectionList.MARKED_JOIN_HIGHLIGHTED_SPRITE : WorldSelectionList.MARKED_JOIN_SPRITE;
                if (this.summary instanceof LevelSummary.SymlinkLevelSummary || this.summary instanceof LevelSummary.CorruptedLevelSummary) {
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, errorSprite, this.getContentX(), this.getContentY(), 32, 32);
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, joinWithErrorSprite, this.getContentX(), this.getContentY(), 32, 32);
                    return;
                }
 
                if (this.summary.isLocked()) {
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, errorSprite, this.getContentX(), this.getContentY(), 32, 32);
                    if (isOverIcon) {
                        graphics.setTooltipForNextFrame(this.minecraft.font.split(WorldSelectionList.WORLD_LOCKED_TOOLTIP, 175), mouseX, mouseY);
                    }
                } else if (this.summary.requiresManualConversion()) {
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, errorSprite, this.getContentX(), this.getContentY(), 32, 32);
                    if (isOverIcon) {
                        graphics.setTooltipForNextFrame(this.minecraft.font.split(WorldSelectionList.WORLD_REQUIRES_CONVERSION, 175), mouseX, mouseY);
                    }
                } else if (!this.summary.isCompatible()) {
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, errorSprite, this.getContentX(), this.getContentY(), 32, 32);
                    if (isOverIcon) {
                        graphics.setTooltipForNextFrame(this.minecraft.font.split(WorldSelectionList.INCOMPATIBLE_VERSION_TOOLTIP, 175), mouseX, mouseY);
                    }
                } else if (this.summary.shouldBackup()) {
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, joinWithErrorSprite, this.getContentX(), this.getContentY(), 32, 32);
                    if (this.summary.isDowngrade()) {
                        graphics.blitSprite(RenderPipelines.GUI_TEXTURED, errorSprite, this.getContentX(), this.getContentY(), 32, 32);
                        if (isOverIcon) {
                            graphics.setTooltipForNextFrame(
                                ImmutableList.of(
                                    WorldSelectionList.FROM_NEWER_TOOLTIP_1.getVisualOrderText(), WorldSelectionList.FROM_NEWER_TOOLTIP_2.getVisualOrderText()
                                ),
                                mouseX,
                                mouseY
                            );
                        }
                    } else if (!SharedConstants.getCurrentVersion().stable()) {
                        graphics.blitSprite(RenderPipelines.GUI_TEXTURED, warningSprite, this.getContentX(), this.getContentY(), 32, 32);
                        if (isOverIcon) {
                            graphics.setTooltipForNextFrame(
                                ImmutableList.of(
                                    WorldSelectionList.SNAPSHOT_TOOLTIP_1.getVisualOrderText(), WorldSelectionList.SNAPSHOT_TOOLTIP_2.getVisualOrderText()
                                ),
                                mouseX,
                                mouseY
                            );
                        }
                    }
 
                    if (isOverIcon) {
                        WorldSelectionList.this.handleCursor(graphics);
                    }
                } else {
                    graphics.blitSprite(RenderPipelines.GUI_TEXTURED, joinSprite, this.getContentX(), this.getContentY(), 32, 32);
                    if (isOverIcon) {
                        WorldSelectionList.this.handleCursor(graphics);
                    }
                }
            }
        }
 
        private int getTextX() {
            return this.getContentX() + 32 + 3;
        }
 
        @Override
        public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
            if (this.canInteract()) {
                int relX = (int)event.x() - this.getContentX();
                int relY = (int)event.y() - this.getContentY();
                if (doubleClick || this.mouseOverIcon(relX, relY, 32) && this.list.entryType == WorldSelectionList.EntryType.SINGLEPLAYER) {
                    this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
                    Consumer<WorldSelectionList.WorldListEntry> onEntryInteract = this.list.onEntryInteract;
                    if (onEntryInteract != null) {
                        onEntryInteract.accept(this);
                        return true;
                    }
                }
            }
 
            return super.mouseClicked(event, doubleClick);
        }
 
        @Override
        public boolean keyPressed(KeyEvent event) {
            if (event.isSelection() && this.canInteract()) {
                this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
                Consumer<WorldSelectionList.WorldListEntry> onEntryInteract = this.list.onEntryInteract;
                if (onEntryInteract != null) {
                    onEntryInteract.accept(this);
                    return true;
                }
            }
 
            return super.keyPressed(event);
        }
 
        public boolean canInteract() {
            return this.summary.primaryActionActive() || this.list.entryType == WorldSelectionList.EntryType.UPLOAD_WORLD;
        }
 
        public void joinWorld() {
            if (this.summary.primaryActionActive()) {
                if (this.summary instanceof LevelSummary.SymlinkLevelSummary) {
                    this.minecraft.setScreen(NoticeWithLinkScreen.createWorldSymlinkWarningScreen(() -> this.minecraft.setScreen(this.screen)));
                } else {
                    this.minecraft.createWorldOpenFlows().openWorld(this.summary.getLevelId(), this.list::returnToScreen);
                }
            }
        }
 
        public void deleteWorld() {
            this.minecraft
                .setScreen(
                    new ConfirmScreen(
                        result -> {
                            if (result) {
                                this.minecraft.setScreen(new ProgressScreen(true));
                                this.doDeleteWorld();
                            }
 
                            this.list.returnToScreen();
                        },
                        Component.translatable("selectWorld.deleteQuestion"),
                        Component.translatable("selectWorld.deleteWarning", this.summary.getLevelName()),
                        Component.translatable("selectWorld.deleteButton"),
                        CommonComponents.GUI_CANCEL
                    )
                );
        }
 
        public void doDeleteWorld() {
            LevelStorageSource levelSource = this.minecraft.getLevelSource();
            String levelId = this.summary.getLevelId();
 
            try (LevelStorageSource.LevelStorageAccess access = levelSource.createAccess(levelId)) {
                access.deleteLevel();
            } catch (IOException var8) {
                SystemToast.onWorldDeleteFailure(this.minecraft, levelId);
                WorldSelectionList.LOGGER.error("Failed to delete world {}", levelId, var8);
            }
        }
 
        public void editWorld() {
            this.queueLoadScreen();
            String levelId = this.summary.getLevelId();
 
            LevelStorageSource.LevelStorageAccess access;
            try {
                access = this.minecraft.getLevelSource().validateAndCreateAccess(levelId);
            } catch (IOException var6) {
                SystemToast.onWorldAccessFailure(this.minecraft, levelId);
                WorldSelectionList.LOGGER.error("Failed to access level {}", levelId, var6);
                this.list.reloadWorldList();
                return;
            } catch (ContentValidationException var7) {
                WorldSelectionList.LOGGER.warn("{}", var7.getMessage());
                this.minecraft.setScreen(NoticeWithLinkScreen.createWorldSymlinkWarningScreen(() -> this.minecraft.setScreen(this.screen)));
                return;
            }
 
            EditWorldScreen editScreen;
            try {
                editScreen = EditWorldScreen.create(this.minecraft, access, result -> {
                    access.safeClose();
                    this.list.returnToScreen();
                });
            } catch (NbtException | ReportedNbtException | IOException var5) {
                access.safeClose();
                SystemToast.onWorldAccessFailure(this.minecraft, levelId);
                WorldSelectionList.LOGGER.error("Failed to load world data {}", levelId, var5);
                this.list.reloadWorldList();
                return;
            }
 
            this.minecraft.setScreen(editScreen);
        }
 
        public void recreateWorld() {
            this.queueLoadScreen();
 
            try (LevelStorageSource.LevelStorageAccess access = this.minecraft.getLevelSource().validateAndCreateAccess(this.summary.getLevelId())) {
                Pair<LevelSettings, WorldCreationContext> recreatedSettings = this.minecraft.createWorldOpenFlows().recreateWorldData(access);
                LevelSettings levelSettings = recreatedSettings.getFirst();
                WorldCreationContext creationContext = recreatedSettings.getSecond();
                Path dataPackDir = CreateWorldScreen.createTempDataPackDirFromExistingWorld(access.getLevelPath(LevelResource.DATAPACK_DIR), this.minecraft);
                creationContext.validate();
                if (creationContext.options().isOldCustomizedWorld()) {
                    this.minecraft
                        .setScreen(
                            new ConfirmScreen(
                                result -> this.minecraft
                                    .setScreen(
                                        (Screen)(result
                                            ? CreateWorldScreen.createFromExisting(
                                                this.minecraft, this.list::returnToScreen, levelSettings, creationContext, dataPackDir
                                            )
                                            : this.screen)
                                    ),
                                Component.translatable("selectWorld.recreate.customized.title"),
                                Component.translatable("selectWorld.recreate.customized.text"),
                                CommonComponents.GUI_PROCEED,
                                CommonComponents.GUI_CANCEL
                            )
                        );
                } else {
                    this.minecraft
                        .setScreen(CreateWorldScreen.createFromExisting(this.minecraft, this.list::returnToScreen, levelSettings, creationContext, dataPackDir));
                }
            } catch (ContentValidationException var8) {
                WorldSelectionList.LOGGER.warn("{}", var8.getMessage());
                this.minecraft.setScreen(NoticeWithLinkScreen.createWorldSymlinkWarningScreen(() -> this.minecraft.setScreen(this.screen)));
            } catch (Exception var9) {
                WorldSelectionList.LOGGER.error("Unable to recreate world", (Throwable)var9);
                this.minecraft
                    .setScreen(
                        new AlertScreen(
                            () -> this.minecraft.setScreen(this.screen),
                            Component.translatable("selectWorld.recreate.error.title"),
                            Component.translatable("selectWorld.recreate.error.text")
                        )
                    );
            }
        }
 
        private void queueLoadScreen() {
            this.minecraft.setScreenAndShow(new GenericMessageScreen(Component.translatable("selectWorld.data_read")));
        }
 
        private void loadIcon() {
            boolean shouldHaveIcon = this.iconFile != null && Files.isRegularFile(this.iconFile);
            if (shouldHaveIcon) {
                try (InputStream stream = Files.newInputStream(this.iconFile)) {
                    this.icon.upload(NativeImage.read(stream));
                } catch (Throwable var7) {
                    WorldSelectionList.LOGGER.error("Invalid icon for world {}", this.summary.getLevelId(), var7);
                    this.iconFile = null;
                }
            } else {
                this.icon.clear();
            }
        }
 
        @Override
        public void close() {
            if (!this.icon.isClosed()) {
                this.icon.close();
            }
        }
 
        public String getLevelName() {
            return this.summary.getLevelName();
        }
 
        @Override
        public LevelSummary getLevelSummary() {
            return this.summary;
        }
    }
}

引用的其他类

  • NativeImage

    • 引用位置: 方法调用
    • 关联成员: NativeImage.read()
  • CrashReport

    • 引用位置: 方法调用
    • 关联成员: CrashReport.forThrowable()
  • SharedConstants

    • 引用位置: 方法调用
    • 关联成员: SharedConstants.getCurrentVersion()
  • Minecraft

    • 引用位置: 参数
  • GuiGraphicsExtractor

    • 引用位置: 参数
  • ObjectSelectionList

    • 引用位置: 继承
  • StringWidget

    • 引用位置: 构造调用
    • 关联成员: StringWidget()
  • Tooltip

    • 引用位置: 方法调用
    • 关联成员: Tooltip.create()
  • SystemToast

    • 引用位置: 方法调用
    • 关联成员: SystemToast.onWorldAccessFailure(), SystemToast.onWorldDeleteFailure()
  • NarrationElementOutput

    • 引用位置: 参数
  • AlertScreen

    • 引用位置: 构造调用
    • 关联成员: AlertScreen()
  • ConfirmScreen

    • 引用位置: 构造调用
    • 关联成员: ConfirmScreen()
  • ErrorScreen

    • 引用位置: 构造调用
    • 关联成员: ErrorScreen()
  • FaviconTexture

    • 引用位置: 方法调用
    • 关联成员: FaviconTexture.forWorld()
  • GenericMessageScreen

    • 引用位置: 构造调用
    • 关联成员: GenericMessageScreen()
  • LoadingDotsText

    • 引用位置: 方法调用
    • 关联成员: LoadingDotsText.get()
  • NoticeWithLinkScreen

    • 引用位置: 方法调用
    • 关联成员: NoticeWithLinkScreen.createWorldSymlinkWarningScreen()
  • ProgressScreen

    • 引用位置: 构造调用
    • 关联成员: ProgressScreen()
  • Screen

    • 引用位置: 参数/字段/返回值
  • CreateWorldScreen

    • 引用位置: 方法调用
    • 关联成员: CreateWorldScreen.createFromExisting(), CreateWorldScreen.createTempDataPackDirFromExistingWorld(), CreateWorldScreen.openFresh()
  • EditWorldScreen

    • 引用位置: 方法调用
    • 关联成员: EditWorldScreen.create()
  • SimpleSoundInstance

    • 引用位置: 方法调用
    • 关联成员: SimpleSoundInstance.forUI()
  • CommonComponents

    • 引用位置: 方法调用
    • 关联成员: CommonComponents.joinForNarration()
  • Component

    • 引用位置: 参数/字段/方法调用
    • 关联成员: Component.literal(), Component.translatable(), Component.translationArg()
  • ComponentUtils

    • 引用位置: 方法调用
    • 关联成员: ComponentUtils.mergeStyles()
  • Identifier

    • 引用位置: 字段/方法调用
    • 关联成员: Identifier.withDefaultNamespace()
  • Util

    • 引用位置: 方法调用
    • 关联成员: Util.getMillis(), Util.localizedDateFormatter()
  • LevelSummary

    • 引用位置: 参数/字段/返回值
  • ContentValidationException

    • 引用位置: 方法调用
    • 关联成员: ContentValidationException.getMessage()