PackSelectionScreen.java

net.minecraft.client.gui.screens.packs.PackSelectionScreen

信息

  • 全限定名:net.minecraft.client.gui.screens.packs.PackSelectionScreen
  • 类型:public class
  • 包:net.minecraft.client.gui.screens.packs
  • 源码路径:src/main/java/net/minecraft/client/gui/screens/packs/PackSelectionScreen.java
  • 起始行号:L60
  • 继承:Screen
  • 职责:

    TODO

字段/常量

  • LOGGER

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

      TODO

  • AVAILABLE_TITLE

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

      TODO

  • SELECTED_TITLE

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

      TODO

  • OPEN_PACK_FOLDER_TITLE

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

      TODO

  • SEARCH

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

      TODO

  • LIST_WIDTH

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

      TODO

  • HEADER_ELEMENT_SPACING

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

      TODO

  • SEARCH_BOX_HEIGHT

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

      TODO

  • DRAG_AND_DROP

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

      TODO

  • DIRECTORY_BUTTON_TOOLTIP

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

      TODO

  • RELOAD_COOLDOWN

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

      TODO

  • DEFAULT_ICON

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

      TODO

  • layout

    • 类型: HeaderAndFooterLayout
    • 修饰符: private final
    • 源码定位: L73
    • 说明:

      TODO

  • model

    • 类型: PackSelectionModel
    • 修饰符: private final
    • 源码定位: L74
    • 说明:

      TODO

  • watcher

    • 类型: PackSelectionScreen.Watcher
    • 修饰符: private
    • 源码定位: L75
    • 说明:

      TODO

  • ticksToReload

    • 类型: long
    • 修饰符: private
    • 源码定位: L76
    • 说明:

      TODO

  • availablePackList

    • 类型: TransferableSelectionList
    • 修饰符: private
    • 源码定位: L77
    • 说明:

      TODO

  • selectedPackList

    • 类型: TransferableSelectionList
    • 修饰符: private
    • 源码定位: L78
    • 说明:

      TODO

  • search

    • 类型: EditBox
    • 修饰符: private
    • 源码定位: L79
    • 说明:

      TODO

  • packDir

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

      TODO

  • doneButton

    • 类型: Button
    • 修饰符: private
    • 源码定位: L81
    • 说明:

      TODO

  • packIcons

    • 类型: Map<String,Identifier>
    • 修饰符: private final
    • 源码定位: L82
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.gui.screens.packs.PackSelectionScreen.Watcher
    • 类型: class
    • 修饰符: private static
    • 源码定位: L343
    • 说明:

      TODO

构造器

public PackSelectionScreen(PackRepository repository, Consumer<PackRepository> output, Path packDir, Component title) @ L84

  • 构造器名:PackSelectionScreen
  • 源码定位:L84
  • 修饰符:public

参数:

  • repository: PackRepository
  • output: Consumer
  • packDir: Path
  • title: Component

说明:

TODO

方法

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

public void onClose() @ L91

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

参数:

说明:

TODO

private void closeWatcher() @ L97

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

参数:

说明:

TODO

protected void init() @ L107

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

参数:

说明:

TODO

protected void setInitialFocus() @ L131

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

参数:

说明:

TODO

private void updateFilteredEntries(String value) @ L140

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

参数:

  • value: String

说明:

TODO

private void filterEntries(String value, Stream<PackSelectionModel.Entry> oldEntries, TransferableSelectionList listToUpdate) @ L145

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

参数:

  • value: String
  • oldEntries: Stream<PackSelectionModel.Entry>
  • listToUpdate: TransferableSelectionList

说明:

TODO

protected void repositionElements() @ L158

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

参数:

说明:

TODO

public void tick() @ L170

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

参数:

说明:

TODO

private void populateLists(PackSelectionModel.EntryBase transferredEntry) @ L188

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

参数:

  • transferredEntry: PackSelectionModel.EntryBase

说明:

TODO

private void reload() @ L206

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

参数:

说明:

TODO

protected static void copyPacks(Minecraft minecraft, List<Path> files, Path targetDir) @ L213

  • 方法名:copyPacks
  • 源码定位:L213
  • 返回类型:void
  • 修饰符:protected static

参数:

  • minecraft: Minecraft
  • files: List
  • targetDir: Path

说明:

TODO

public void onFilesDrop(List<Path> files) @ L235

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

参数:

  • files: List

说明:

TODO

private static Stream<String> extractPackNames(Collection<Path> files) @ L306

  • 方法名:extractPackNames
  • 源码定位:L306
  • 返回类型:Stream
  • 修饰符:private static

参数:

  • files: Collection

说明:

TODO

private Identifier loadPackIcon(TextureManager textureManager, Pack pack) @ L310

  • 方法名:loadPackIcon
  • 源码定位:L310
  • 返回类型:Identifier
  • 修饰符:private

参数:

  • textureManager: TextureManager
  • pack: Pack

说明:

TODO

private Identifier getPackIcon(Pack pack) @ L338

  • 方法名:getPackIcon
  • 源码定位:L338
  • 返回类型:Identifier
  • 修饰符:private

参数:

  • pack: Pack

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class PackSelectionScreen extends Screen {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final Component AVAILABLE_TITLE = Component.translatable("pack.available.title");
    private static final Component SELECTED_TITLE = Component.translatable("pack.selected.title");
    private static final Component OPEN_PACK_FOLDER_TITLE = Component.translatable("pack.openFolder");
    private static final Component SEARCH = Component.translatable("gui.packSelection.search").withStyle(EditBox.SEARCH_HINT_STYLE);
    private static final int LIST_WIDTH = 200;
    private static final int HEADER_ELEMENT_SPACING = 4;
    private static final int SEARCH_BOX_HEIGHT = 15;
    private static final Component DRAG_AND_DROP = Component.translatable("pack.dropInfo").withStyle(ChatFormatting.GRAY);
    private static final Component DIRECTORY_BUTTON_TOOLTIP = Component.translatable("pack.folderInfo");
    private static final int RELOAD_COOLDOWN = 20;
    private static final Identifier DEFAULT_ICON = Identifier.withDefaultNamespace("textures/misc/unknown_pack.png");
    private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this);
    private final PackSelectionModel model;
    private PackSelectionScreen.@Nullable Watcher watcher;
    private long ticksToReload;
    private @Nullable TransferableSelectionList availablePackList;
    private @Nullable TransferableSelectionList selectedPackList;
    private @Nullable EditBox search;
    private final Path packDir;
    private @Nullable Button doneButton;
    private final Map<String, Identifier> packIcons = Maps.newHashMap();
 
    public PackSelectionScreen(PackRepository repository, Consumer<PackRepository> output, Path packDir, Component title) {
        super(title);
        this.model = new PackSelectionModel(this::populateLists, this::getPackIcon, repository, output);
        this.packDir = packDir;
        this.watcher = PackSelectionScreen.Watcher.create(packDir);
    }
 
    @Override
    public void onClose() {
        this.model.commit();
        this.closeWatcher();
    }
 
    private void closeWatcher() {
        if (this.watcher != null) {
            try {
                this.watcher.close();
                this.watcher = null;
            } catch (Exception var2) {
            }
        }
    }
 
    @Override
    protected void init() {
        this.layout.setHeaderHeight(4 + 9 + 4 + 9 + 4 + 15 + 4);
        LinearLayout header = this.layout.addToHeader(LinearLayout.vertical().spacing(4));
        header.defaultCellSetting().alignHorizontallyCenter();
        header.addChild(new StringWidget(this.getTitle(), this.font));
        header.addChild(new StringWidget(DRAG_AND_DROP, this.font));
        this.search = header.addChild(new EditBox(this.font, 0, 0, 200, 15, Component.empty()));
        this.search.setHint(SEARCH);
        this.search.setResponder(this::updateFilteredEntries);
        this.availablePackList = this.layout.addToContents(new TransferableSelectionList(this.minecraft, this, 200, this.height - 66, AVAILABLE_TITLE));
        this.selectedPackList = this.layout.addToContents(new TransferableSelectionList(this.minecraft, this, 200, this.height - 66, SELECTED_TITLE));
        LinearLayout footer = this.layout.addToFooter(LinearLayout.horizontal().spacing(8));
        footer.addChild(
            Button.builder(OPEN_PACK_FOLDER_TITLE, button -> Util.getPlatform().openPath(this.packDir))
                .tooltip(Tooltip.create(DIRECTORY_BUTTON_TOOLTIP))
                .build()
        );
        this.doneButton = footer.addChild(Button.builder(CommonComponents.GUI_DONE, button -> this.onClose()).build());
        this.layout.visitWidgets(x$0 -> this.addRenderableWidget(x$0));
        this.repositionElements();
        this.reload();
    }
 
    @Override
    protected void setInitialFocus() {
        if (this.search != null) {
            this.setInitialFocus(this.search);
        } else {
            super.setInitialFocus();
        }
    }
 
    private void updateFilteredEntries(String value) {
        this.filterEntries(value, this.model.getSelected(), this.selectedPackList);
        this.filterEntries(value, this.model.getUnselected(), this.availablePackList);
    }
 
    private void filterEntries(String value, Stream<PackSelectionModel.Entry> oldEntries, @Nullable TransferableSelectionList listToUpdate) {
        if (listToUpdate != null) {
            String lowerCaseValue = value.toLowerCase(Locale.ROOT);
            Stream<PackSelectionModel.Entry> filteredEntries = oldEntries.filter(
                packEntry -> value.isBlank()
                    || packEntry.getId().toLowerCase(Locale.ROOT).contains(lowerCaseValue)
                    || packEntry.getTitle().getString().toLowerCase(Locale.ROOT).contains(lowerCaseValue)
                    || packEntry.getDescription().getString().toLowerCase(Locale.ROOT).contains(lowerCaseValue)
            );
            listToUpdate.updateList(filteredEntries, null);
        }
    }
 
    @Override
    protected void repositionElements() {
        this.layout.arrangeElements();
        if (this.availablePackList != null) {
            this.availablePackList.updateSizeAndPosition(200, this.layout.getContentHeight(), this.width / 2 - 15 - 200, this.layout.getHeaderHeight());
        }
 
        if (this.selectedPackList != null) {
            this.selectedPackList.updateSizeAndPosition(200, this.layout.getContentHeight(), this.width / 2 + 15, this.layout.getHeaderHeight());
        }
    }
 
    @Override
    public void tick() {
        if (this.watcher != null) {
            try {
                if (this.watcher.pollForChanges()) {
                    this.ticksToReload = 20L;
                }
            } catch (IOException var2) {
                LOGGER.warn("Failed to poll for directory {} changes, stopping", this.packDir);
                this.closeWatcher();
            }
        }
 
        if (this.ticksToReload > 0L && --this.ticksToReload == 0L) {
            this.reload();
        }
    }
 
    private void populateLists(PackSelectionModel.@Nullable EntryBase transferredEntry) {
        if (this.selectedPackList != null) {
            this.selectedPackList.updateList(this.model.getSelected(), transferredEntry);
        }
 
        if (this.availablePackList != null) {
            this.availablePackList.updateList(this.model.getUnselected(), transferredEntry);
        }
 
        if (this.search != null) {
            this.updateFilteredEntries(this.search.getValue());
        }
 
        if (this.doneButton != null) {
            this.doneButton.active = !this.selectedPackList.children().isEmpty();
        }
    }
 
    private void reload() {
        this.model.findNewPacks();
        this.populateLists(null);
        this.ticksToReload = 0L;
        this.packIcons.clear();
    }
 
    protected static void copyPacks(Minecraft minecraft, List<Path> files, Path targetDir) {
        MutableBoolean showErrorToast = new MutableBoolean();
        files.forEach(pack -> {
            try (Stream<Path> contents = Files.walk(pack)) {
                contents.forEach(path -> {
                    try {
                        Util.copyBetweenDirs(pack.getParent(), targetDir, path);
                    } catch (IOException var5) {
                        LOGGER.warn("Failed to copy datapack file  from {} to {}", path, targetDir, var5);
                        showErrorToast.setTrue();
                    }
                });
            } catch (IOException var8) {
                LOGGER.warn("Failed to copy datapack file from {} to {}", pack, targetDir);
                showErrorToast.setTrue();
            }
        });
        if (showErrorToast.isTrue()) {
            SystemToast.onPackCopyFailure(minecraft, targetDir.toString());
        }
    }
 
    @Override
    public void onFilesDrop(List<Path> files) {
        String names = extractPackNames(files).collect(Collectors.joining(", "));
        this.minecraft
            .setScreen(
                new ConfirmScreen(
                    result -> {
                        if (result) {
                            List<Path> packCandidates = new ArrayList<>(files.size());
                            Set<Path> leftoverPacks = new HashSet<>(files);
                            PackDetector<Path> packDetector = new PackDetector<Path>(this.minecraft.directoryValidator()) {
                                {
                                    Objects.requireNonNull(PackSelectionScreen.this);
                                }
 
                                protected Path createZipPack(Path content) {
                                    return content;
                                }
 
                                protected Path createDirectoryPack(Path content) {
                                    return content;
                                }
                            };
                            List<ForbiddenSymlinkInfo> issues = new ArrayList<>();
 
                            for (Path path : files) {
                                try {
                                    Path candidate = packDetector.detectPackResources(path, issues);
                                    if (candidate == null) {
                                        LOGGER.warn("Path {} does not seem like pack", path);
                                    } else {
                                        packCandidates.add(candidate);
                                        leftoverPacks.remove(candidate);
                                    }
                                } catch (IOException var10) {
                                    LOGGER.warn("Failed to check {} for packs", path, var10);
                                }
                            }
 
                            if (!issues.isEmpty()) {
                                this.minecraft.setScreen(NoticeWithLinkScreen.createPackSymlinkWarningScreen(() -> this.minecraft.setScreen(this)));
                                return;
                            }
 
                            if (!packCandidates.isEmpty()) {
                                copyPacks(this.minecraft, packCandidates, this.packDir);
                                this.reload();
                            }
 
                            if (!leftoverPacks.isEmpty()) {
                                String leftoverNames = extractPackNames(leftoverPacks).collect(Collectors.joining(", "));
                                this.minecraft
                                    .setScreen(
                                        new AlertScreen(
                                            () -> this.minecraft.setScreen(this),
                                            Component.translatable("pack.dropRejected.title"),
                                            Component.translatable("pack.dropRejected.message", leftoverNames)
                                        )
                                    );
                                return;
                            }
                        }
 
                        this.minecraft.setScreen(this);
                    },
                    Component.translatable("pack.dropConfirm"),
                    Component.literal(names)
                )
            );
    }
 
    private static Stream<String> extractPackNames(Collection<Path> files) {
        return files.stream().map(Path::getFileName).map(Path::toString);
    }
 
    private Identifier loadPackIcon(TextureManager textureManager, Pack pack) {
        try {
            Identifier var9;
            try (PackResources packResources = pack.open()) {
                IoSupplier<InputStream> resource = packResources.getRootResource("pack.png");
                if (resource == null) {
                    return DEFAULT_ICON;
                }
 
                String id = pack.getId();
                Identifier location = Identifier.withDefaultNamespace(
                    "pack/" + Util.sanitizeName(id, Identifier::validPathChar) + "/" + Hashing.sha1().hashUnencodedChars(id) + "/icon"
                );
 
                try (InputStream stream = resource.get()) {
                    NativeImage iconImage = NativeImage.read(stream);
                    textureManager.register(location, new DynamicTexture(location::toString, iconImage));
                    var9 = location;
                }
            }
 
            return var9;
        } catch (Exception var14) {
            LOGGER.warn("Failed to load icon from pack {}", pack.getId(), var14);
            return DEFAULT_ICON;
        }
    }
 
    private Identifier getPackIcon(Pack pack) {
        return this.packIcons.computeIfAbsent(pack.getId(), s -> this.loadPackIcon(this.minecraft.getTextureManager(), pack));
    }
 
    @OnlyIn(Dist.CLIENT)
    private static class Watcher implements AutoCloseable {
        private final WatchService watcher;
        private final Path packPath;
 
        public Watcher(Path packPath) throws IOException {
            this.packPath = packPath;
            this.watcher = packPath.getFileSystem().newWatchService();
 
            try {
                this.watchDir(packPath);
 
                try (DirectoryStream<Path> paths = Files.newDirectoryStream(packPath)) {
                    for (Path path : paths) {
                        if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
                            this.watchDir(path);
                        }
                    }
                }
            } catch (Exception var7) {
                this.watcher.close();
                throw var7;
            }
        }
 
        public static PackSelectionScreen.@Nullable Watcher create(Path packDir) {
            try {
                return new PackSelectionScreen.Watcher(packDir);
            } catch (IOException var2) {
                PackSelectionScreen.LOGGER.warn("Failed to initialize pack directory {} monitoring", packDir, var2);
                return null;
            }
        }
 
        private void watchDir(Path packPath) throws IOException {
            packPath.register(this.watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
        }
 
        public boolean pollForChanges() throws IOException {
            boolean hasChanges = false;
 
            WatchKey key;
            while ((key = this.watcher.poll()) != null) {
                for (WatchEvent<?> watchEvent : key.pollEvents()) {
                    hasChanges = true;
                    if (key.watchable() == this.packPath && watchEvent.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                        Path newPath = this.packPath.resolve((Path)watchEvent.context());
                        if (Files.isDirectory(newPath, LinkOption.NOFOLLOW_LINKS)) {
                            this.watchDir(newPath);
                        }
                    }
                }
 
                key.reset();
            }
 
            return hasChanges;
        }
 
        @Override
        public void close() throws IOException {
            this.watcher.close();
        }
    }
}

引用的其他类

  • NativeImage

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

    • 引用位置: 参数
  • Button

    • 引用位置: 字段/方法调用
    • 关联成员: Button.builder()
  • EditBox

    • 引用位置: 字段/构造调用
    • 关联成员: EditBox()
  • StringWidget

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

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

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

    • 引用位置: 字段/构造调用
    • 关联成员: HeaderAndFooterLayout()
  • LinearLayout

    • 引用位置: 方法调用
    • 关联成员: LinearLayout.horizontal(), LinearLayout.vertical()
  • AlertScreen

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

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

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

    • 引用位置: 继承
  • PackSelectionModel

    • 引用位置: 参数/字段/构造调用
    • 关联成员: PackSelectionModel()
  • TransferableSelectionList

    • 引用位置: 参数/字段/构造调用
    • 关联成员: TransferableSelectionList()
  • DynamicTexture

    • 引用位置: 构造调用
    • 关联成员: DynamicTexture()
  • TextureManager

    • 引用位置: 参数
  • Component

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

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

    • 引用位置: 参数
  • PackRepository

    • 引用位置: 参数
  • Util

    • 引用位置: 方法调用
    • 关联成员: Util.copyBetweenDirs(), Util.getPlatform(), Util.sanitizeName()