From 161234dc52430d0bf3fb223a72e52ceb147ad5e0 Mon Sep 17 00:00:00 2001 From: GaLi <3096147684@qq.com> Date: Mon, 2 Mar 2026 12:00:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=A0=B7=E6=9D=BF=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=99=A8=E9=80=89=E6=8B=A9=E7=95=8C=E9=9D=A2=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8F=B3=E9=94=AE=E7=BD=AE=E9=A1=B6=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=99=A8=E6=9D=A1=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/screen/ProviderSelectScreen.java | 184 +++++++++++++++++- 1 file changed, 183 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java b/src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java index 8f7df89..aa2588b 100644 --- a/src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java +++ b/src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java @@ -2,13 +2,20 @@ package com.extendedae_plus.client.screen; import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket; import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil; +import com.google.gson.*; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; +import net.neoforged.fml.loading.FMLPaths; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * 简单的供应器选择弹窗。 @@ -19,6 +26,21 @@ public class ProviderSelectScreen extends Screen { // 优先使用 JEC 的拼音匹配,否则回退到大小写不敏感子串匹配 private static Boolean JEC_AVAILABLE = null; private static java.lang.reflect.Method JEC_CONTAINS = null; + + // 置顶的供应器名称集合(静态变量,持久化到配置文件) + private static final Set pinnedProviders = new HashSet<>(); + private static final String PINNED_CONFIG_PATH = "extendedae_plus/pinned_providers.json"; + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + + // 静态初始化块:加载置顶配置 + static { + try { + loadPinnedProviders(); + } catch (Throwable t) { + // 加载失败时静默处理,不影响界面使用 + } + } + private final Screen parent; // 原始数据 private final List ids; @@ -121,8 +143,12 @@ public class ProviderSelectScreen extends Screen { String name = this.fNames.get(idx); int totalSlots = this.fTotalSlots.get(idx); int count = this.fCount.get(idx); + + // 如果是置顶条目,在最左侧添加星星标志 + String prefix = pinnedProviders.contains(name) ? "★ " : ""; + // 不显示具体 id,显示合并统计:名称(总空位)x数量 - return name + " (" + totalSlots + ") x" + count; + return prefix + name + " (" + totalSlots + ") x" + count; } private void onChoose(int idx) { @@ -176,6 +202,44 @@ public class ProviderSelectScreen extends Screen { this.fCount.add(this.gCount.get(i)); } } + + // 对 fNames 进行排序,置顶的条目排在前面,然后按自然排序 + List indices = new ArrayList<>(); + for (int i = 0; i < this.fNames.size(); i++) indices.add(i); + indices.sort((i1, i2) -> { + String name1 = this.fNames.get(i1); + String name2 = this.fNames.get(i2); + boolean pinned1 = pinnedProviders.contains(name1); + boolean pinned2 = pinnedProviders.contains(name2); + + // 置顶的排在前面 + if (pinned1 && !pinned2) return -1; + if (!pinned1 && pinned2) return 1; + + // 都置顶或都不置顶,按自然排序 + return compareNatural(name1, name2); + }); + + List sortedIds = new ArrayList<>(); + List sortedNames = new ArrayList<>(); + List sortedSlots = new ArrayList<>(); + List sortedCount = new ArrayList<>(); + + for (int idx : indices) { + sortedIds.add(this.fIds.get(idx)); + sortedNames.add(this.fNames.get(idx)); + sortedSlots.add(this.fTotalSlots.get(idx)); + sortedCount.add(this.fCount.get(idx)); + } + + this.fIds.clear(); + this.fIds.addAll(sortedIds); + this.fNames.clear(); + this.fNames.addAll(sortedNames); + this.fTotalSlots.clear(); + this.fTotalSlots.addAll(sortedSlots); + this.fCount.clear(); + this.fCount.addAll(sortedCount); } @Override @@ -319,6 +383,29 @@ public class ProviderSelectScreen extends Screen { return true; } } + + // 右键点击条目按钮时,切换置顶状态 + if (button == 1) { + for (int i = 0; i < this.entryButtons.size(); i++) { + Button btn = this.entryButtons.get(i); + if (btn.visible && btn.active) { + int x = btn.getX(); + int y = btn.getY(); + int w = btn.getWidth(); + int h = btn.getHeight(); + if (mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h) { + // 计算实际索引 + int start = this.page * PAGE_SIZE; + int actualIdx = start + i; + if (actualIdx >= 0 && actualIdx < this.fNames.size()) { + togglePin(actualIdx); + return true; + } + } + } + } + } + return super.mouseClicked(mouseX, mouseY, button); } @@ -378,6 +465,101 @@ public class ProviderSelectScreen extends Screen { } } + // 切换供应器的置顶状态 + private void togglePin(int idx) { + if (idx < 0 || idx >= this.fNames.size()) return; + String name = this.fNames.get(idx); + + if (pinnedProviders.contains(name)) { + pinnedProviders.remove(name); + } else { + pinnedProviders.add(name); + } + + // 保存到配置文件 + savePinnedProviders(); + + // 重新应用过滤和排序 + this.applyFilter(); + this.needsRefresh = true; + } + + /** + * 从配置文件加载置顶的供应器名称列表 + */ + private static synchronized void loadPinnedProviders() { + try { + Path cfgPath = FMLPaths.CONFIGDIR.get().resolve(PINNED_CONFIG_PATH); + if (!Files.exists(cfgPath)) { + return; // 文件不存在时不做处理 + } + + String json = Files.readString(cfgPath); + JsonObject obj = GSON.fromJson(json, JsonObject.class); + if (obj == null) return; + + JsonElement pinnedElement = obj.get("pinned"); + if (pinnedElement != null && pinnedElement.isJsonArray()) { + JsonArray arr = pinnedElement.getAsJsonArray(); + pinnedProviders.clear(); + for (JsonElement elem : arr) { + if (elem.isJsonPrimitive()) { + String name = elem.getAsString(); + if (name != null && !name.isBlank()) { + pinnedProviders.add(name); + } + } + } + } + } catch (IOException | JsonSyntaxException e) { + // 加载失败时静默处理 + } + } + + /** + * 保存置顶的供应器名称列表到配置文件 + */ + private static synchronized void savePinnedProviders() { + try { + Path cfgPath = FMLPaths.CONFIGDIR.get().resolve(PINNED_CONFIG_PATH); + Files.createDirectories(cfgPath.getParent()); + + JsonObject obj = new JsonObject(); + JsonArray arr = new JsonArray(); + for (String name : pinnedProviders) { + arr.add(name); + } + obj.add("pinned", arr); + + Files.writeString(cfgPath, GSON.toJson(obj)); + } catch (IOException e) { + // 保存失败时静默处理 + } + } + + // 自然排序比较方法 + private static final Pattern NATURAL_PATTERN = Pattern.compile("(\\D*)(\\d*)"); + private static int compareNatural(String s1, String s2) { + Matcher m1 = NATURAL_PATTERN.matcher(s1); + Matcher m2 = NATURAL_PATTERN.matcher(s2); + + while (m1.find() && m2.find()) { + // 比较非数字部分 + int cmp = m1.group(1).compareTo(m2.group(1)); + if (cmp != 0) return cmp; + + // 比较数字部分 + String num1 = m1.group(2); + String num2 = m2.group(2); + if (!num1.isEmpty() || !num2.isEmpty()) { + int n1 = num1.isEmpty() ? 0 : Integer.parseInt(num1); + int n2 = num2.isEmpty() ? 0 : Integer.parseInt(num2); + if (n1 != n2) return Integer.compare(n1, n2); + } + } + return s1.length() - s2.length(); + } + private static class Group { long bestId = Long.MIN_VALUE; int bestSlots = Integer.MIN_VALUE;