diff --git a/build.gradle b/build.gradle index 8695593d..460f10ef 100644 --- a/build.gradle +++ b/build.gradle @@ -72,5 +72,9 @@ project(":core") { api "com.alibaba:fastjson:2.0.39" // Log4j api "apache-log4j:log4j:1.2.15" + // TiniPinyin + api 'com.github.promeg:tinypinyin:2.0.3' + // OpenCC4j + api 'com.github.houbb:opencc4j:1.8.1' } } diff --git a/core/src/cn/harryh/arkpets/ArkChar.java b/core/src/cn/harryh/arkpets/ArkChar.java index 97919d37..3fad7a5a 100644 --- a/core/src/cn/harryh/arkpets/ArkChar.java +++ b/core/src/cn/harryh/arkpets/ArkChar.java @@ -8,7 +8,7 @@ import cn.harryh.arkpets.animations.AnimClipGroup; import cn.harryh.arkpets.animations.AnimComposer; import cn.harryh.arkpets.animations.AnimData; -import cn.harryh.arkpets.assets.AssetItem.AssetAccessor; +import cn.harryh.arkpets.assets.ModelItem.ModelAssetAccessor; import cn.harryh.arkpets.transitions.EasingFunction; import cn.harryh.arkpets.transitions.TransitionFloat; import cn.harryh.arkpets.transitions.TransitionVector3; @@ -87,9 +87,9 @@ public ArkChar(ArkConfig config, float scale) { SkeletonData skeletonData; try { String assetLocation = config.character_asset; - AssetAccessor assetAccessor = new AssetAccessor(config.character_files); - String path2atlas = assetLocation + separator + assetAccessor.getFirstFileOf(".atlas"); - String path2skel = assetLocation + separator + assetAccessor.getFirstFileOf(".skel"); + ModelAssetAccessor modelAssetAccessor = new ModelAssetAccessor(config.character_files); + String path2atlas = assetLocation + separator + modelAssetAccessor.getFirstFileOf(".atlas"); + String path2skel = assetLocation + separator + modelAssetAccessor.getFirstFileOf(".skel"); // Load atlas TextureAtlas atlas = new TextureAtlas(Gdx.files.internal(path2atlas)); // Load skel (use SkeletonJson instead of SkeletonBinary if the file type is JSON) diff --git a/core/src/cn/harryh/arkpets/assets/AssetItemGroup.java b/core/src/cn/harryh/arkpets/assets/AssetItemGroup.java deleted file mode 100644 index 4fa07d5d..00000000 --- a/core/src/cn/harryh/arkpets/assets/AssetItemGroup.java +++ /dev/null @@ -1,212 +0,0 @@ -/** Copyright (c) 2022-2025, Harry Huang - * At GPL-3.0 License - */ -package cn.harryh.arkpets.assets; - -import cn.harryh.arkpets.assets.AssetItem.PropertyExtractor; - -import java.util.*; -import java.util.function.Predicate; - - -/** The class implements the Collection of {@link AssetItem}. - *
- * The structure of the root directory may be like what is shown below. - * Each {@code SubDir} represents an {@code AssetItem}. - * - *
- * +-RootDir
- * |-+-SubDir1 (whose name is the model name of xxx)
- * | |---xxx.atlas
- * | |---xxx.png
- * | |---xxx.skel
- * |---SubDir2
- * |---SubDir3
- * |---...
- *
- * - * @since ArkPets 2.4 - */ -public class AssetItemGroup implements Collection { - protected final ArrayList assetItemList; - - public AssetItemGroup(Collection assetItemList) { - this.assetItemList = new ArrayList<>(assetItemList); - } - - public AssetItemGroup() { - this(new ArrayList<>()); - } - - /** Searches the Asset Items whose {@code name} and {@code appellation} match the given keywords. - * @param keyWords The given keywords. Each keyword should be separated by a blank. - * @return An Asset Item Group. Returns {@code this} if the parameter {@code keyWords} is {@code null} or empty. - */ - public AssetItemGroup searchByKeyWords(String keyWords) { - if (keyWords == null || keyWords.isEmpty()) - return this; - String[] wordList = keyWords.split(" "); - AssetItemGroup result = new AssetItemGroup(); - for (AssetItem asset : this) { - for (String word : wordList) { - if (asset.name != null && - asset.name.toLowerCase().contains(word.toLowerCase())) { - result.add(asset); - } - } - for (String word : wordList) { - if (asset.appellation != null && - asset.appellation.toLowerCase().contains(word.toLowerCase())) { - if (!result.contains(asset)) - result.add(asset); - } - } - } - return result; - } - - /** Searches the Asset Item whose relative path provided by {@code getLocation} matches the given path string. - * @param relPath The given path string. - * @return The first matched Asset Item. Returns {@code null} if no one matched. - */ - public AssetItem searchByRelPath(String relPath) { - if (relPath == null || relPath.isEmpty()) - return null; - for (AssetItem asset : this) - if (asset.getLocation().equalsIgnoreCase(relPath)) - return asset; - return null; - } - - /** Collects the values of a specified property of the Asset Items. - * @param property A property extractor. - * @return A Set that contains all the possible values of the property. - * @param The type of the property value. - */ - public Set extract(PropertyExtractor property) { - HashSet result = new HashSet<>(); - for (AssetItem item : this) - result.addAll(property.apply(item)); - return result; - } - - /** Returns a new Asset Item Group consisting of the Asset Items that match the given predicate. - * @param predicate A predicate to apply to each Asset Item to determine if it should be included. - * @return An Asset Item Group. - */ - public AssetItemGroup filter(Predicate predicate) { - return new AssetItemGroup(assetItemList.stream().filter(predicate).toList()); - } - - /** Returns a new Asset Item Group consisting of the Asset Items whose property satisfied the requirements. - * @param property A property extractor. - * @param filterValues The property values to be matched. - * @param mode The {@link AssetItemGroup.FilterMode}. - * @return An Asset Item Group. - * @param The type of the property value. - */ - public AssetItemGroup filter(PropertyExtractor property, Set filterValues, int mode) { - final boolean TRUE = (mode & FilterMode.MATCH_REVERSE) == 0; - return filter(assetItem -> { - Set itemValues = property.apply(assetItem); - if ((mode & FilterMode.MATCH_ANY) != 0) { - for (T value : itemValues) - if (filterValues.contains(value)) - return TRUE; - } else { - if (itemValues.containsAll(filterValues)) - return TRUE; - } - return !TRUE; - }); - } - - /** Returns a new Asset Item Group consisting of the Asset Items whose property satisfied the requirements. - * @param property A property extractor. - * @param filterValues The property values to be matched. - * @return An Asset Item Group. - * @param The type of the property value. - */ - public AssetItemGroup filter(PropertyExtractor property, Set filterValues) { - return filter(property, filterValues, 0); - } - - /** Sorts the Asset Items by their {@code assetDir} in natural order. - */ - public void sort() { - assetItemList.sort(Comparator.comparing(asset -> asset.assetDir, Comparator.naturalOrder())); - } - - - public static class FilterMode { - public static final int MATCH_ANY = 0b1; - public static final int MATCH_REVERSE = 0b10; - } - - @Override - public Iterator iterator() { - return assetItemList.iterator(); - } - - @Override - public boolean add(AssetItem assetItem) { - return !assetItemList.contains(assetItem) && assetItemList.add(assetItem); - } - - @Override - public boolean addAll(Collection c) { - int size = size(); - c.forEach(this::add); - return size != size(); - } - - @Override - public boolean contains(Object o) { - return assetItemList.contains(o); - } - - @Override - public boolean containsAll(Collection c) { - return assetItemList.containsAll(c); - } - - @Override - public boolean remove(Object o) { - return assetItemList.remove(o); - } - - @Override - public boolean removeAll(Collection c) { - return assetItemList.removeAll(c); - } - - @Override - public boolean retainAll(Collection c) { - return assetItemList.retainAll(c); - } - - @Override - public void clear() { - assetItemList.clear(); - } - - @Override - public boolean isEmpty() { - return assetItemList.isEmpty(); - } - - @Override - public int size() { - return assetItemList.size(); - } - - @Override - public Object[] toArray() { - return assetItemList.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return assetItemList.toArray(a); - } -} diff --git a/core/src/cn/harryh/arkpets/assets/AssetItem.java b/core/src/cn/harryh/arkpets/assets/ModelItem.java similarity index 77% rename from core/src/cn/harryh/arkpets/assets/AssetItem.java rename to core/src/cn/harryh/arkpets/assets/ModelItem.java index 0c50a06e..9707a8d7 100644 --- a/core/src/cn/harryh/arkpets/assets/AssetItem.java +++ b/core/src/cn/harryh/arkpets/assets/ModelItem.java @@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONField; +import com.github.promeg.pinyinhelper.Pinyin; import java.io.File; import java.io.Serializable; @@ -14,9 +15,9 @@ import java.util.function.Function; -/** One Asset Item is corresponding to one certain local Spine asset. +/** One Model Item is corresponding to one certain local Spine model. */ -public class AssetItem implements Serializable { +public class ModelItem implements Serializable { @JSONField(serialize = false) public String key; @JSONField(serialize = false) @@ -42,11 +43,14 @@ public class AssetItem implements Serializable { /** @deprecated Legacy field in old version dataset */ @JSONField @Deprecated public JSONObject checksum; - private AssetAccessor accessor; + // Lazy generated fields: + private ModelAssetAccessor accessor; + private String pinyinQuanpin; + private String pinyinSuoxie; protected static final String[] extensions = {".atlas", ".png", ".skel"}; - private AssetItem() { + private ModelItem() { } /** Gets the directory where the asset files located in. @@ -57,20 +61,50 @@ public String getLocation() { return assetDir.toString(); } - /** Gets the Asset Accessor of this asset. - * @return An Asset Accessor instance. + /** Gets the Model Asset Accessor of the model's asset files. + * @return A Model Asset Accessor instance. */ @JSONField(serialize = false) - public AssetAccessor getAccessor() { + public ModelAssetAccessor getAccessor() { if (accessor == null) - accessor = new AssetAccessor(assetList); + accessor = new ModelAssetAccessor(assetList); return accessor; } - /** Verifies the integrity of the necessary fields of this {@code AssetItem}. + /** Gets the 拼音全拼 (Pinyin Quanpin, full Pinyin transcription) of the model's name. + * @return A String. + */ + @JSONField(serialize = false) + public String getPinyinQuanpin() { + if (pinyinQuanpin == null) + pinyinQuanpin = Pinyin.toPinyin(name, ""); + return pinyinQuanpin; + } + + /** Gets the 拼音缩写 (Pinyin Suoxie, abbreviate Pinyin transcription) of the model's name. + * @return A String. + */ + @JSONField(serialize = false) + public String getPinyinSuoxie() { + if (pinyinSuoxie == null) { + String quanpin = Pinyin.toPinyin(name, " ").trim(); + if (!quanpin.isEmpty()) { + StringBuilder builder = new StringBuilder(); + for (String word : quanpin.split("\\s+")) { + builder.append(word.charAt(0)); + } + pinyinSuoxie = builder.toString(); + } else { + pinyinSuoxie = ""; + } + } + return pinyinSuoxie; + } + + /** Verifies the integrity of the necessary fields of this {@code ModelItem}. * @return {@code true} if all the following conditions are satisfied, otherwise {@code false}: * 1. Both {@code assetDir} and {@code type} are not {@code null}. - * 2. The {@code AssetAccessor} is available. + * 2. The {@code ModelAssetAccessor} is available. */ @JSONField(serialize = false) public boolean isValid() { @@ -127,21 +161,21 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj instanceof AssetItem) { - return ((AssetItem)obj).assetDir.equals(assetDir); + if (obj instanceof ModelItem) { + return ((ModelItem)obj).assetDir.equals(assetDir); } return false; } - /** The Asset Accessor providing methods to get the resource files of the asset. + /** The Model Asset Accessor providing methods to get the resource files of the model's asset files. * @since ArkPets 2.2 */ - public static class AssetAccessor { + public static class ModelAssetAccessor { private final ArrayList list; private final HashMap> map; - public AssetAccessor(JSONObject fileMap) { + public ModelAssetAccessor(JSONObject fileMap) { ArrayList list = new ArrayList<>(); HashMap> map = new HashMap<>(); try { @@ -197,17 +231,17 @@ public boolean isAvailable() { } - /** The Asset Property Extractor specializing in extracting a specified property. + /** The Model Property Extractor specializing in extracting a specified property. * @param The type of the specified property, typically {@code String}. * @since ArkPets 2.2 */ - public interface PropertyExtractor extends Function> { - /** Extracts the specified property of the given Asset Item. - * @param assetItem The given Asset Item. + public interface PropertyExtractor extends Function> { + /** Extracts the specified property of the given Model Item. + * @param modelItem The given Model Item. * @return A value {@link Set} of the extracted property. */ @Override - Set apply(AssetItem assetItem); + Set apply(ModelItem modelItem); PropertyExtractor ASSET_ITEM_KEY = item -> item.key == null ? Set.of() : Set.of(item.key); PropertyExtractor ASSET_ITEM_TYPE = item -> item.type == null ? Set.of() : Set.of(item.type); @@ -216,7 +250,8 @@ public interface PropertyExtractor extends Function> { PropertyExtractor ASSET_ITEM_SORT_TAGS = item -> new HashSet<>(item.sortTags.toJavaList(String.class)); } - /** The Asset Prefab storing the user prefab of the specific asset. + + /** The Model Prefab storing the user prefab of the specific model. * @since ArkPets 3.5 */ public static class AssetPrefab { diff --git a/core/src/cn/harryh/arkpets/assets/ModelItemGroup.java b/core/src/cn/harryh/arkpets/assets/ModelItemGroup.java new file mode 100644 index 00000000..250e7eeb --- /dev/null +++ b/core/src/cn/harryh/arkpets/assets/ModelItemGroup.java @@ -0,0 +1,284 @@ +/** Copyright (c) 2022-2025, Harry Huang + * At GPL-3.0 License + */ +package cn.harryh.arkpets.assets; + +import cn.harryh.arkpets.assets.ModelItem.PropertyExtractor; +import com.github.houbb.opencc4j.util.ZhConverterUtil; + +import java.util.*; +import java.util.function.Predicate; + + +/** The class implements the Collection of {@link ModelItem}. + *
+ * The structure of the root directory may be like what is shown below. + * Each {@code SubDir} represents an {@code ModelItem}. + * + *
+ * +-RootDir
+ * |-+-SubDir1 (whose name is the model name of xxx)
+ * | |---xxx.atlas
+ * | |---xxx.png
+ * | |---xxx.skel
+ * |---SubDir2
+ * |---SubDir3
+ * |---...
+ *
+ * + * @since ArkPets 2.4 + */ +public class ModelItemGroup implements Collection { + protected final ArrayList modelItemList; + + public ModelItemGroup(Collection modelItemList) { + this.modelItemList = new ArrayList<>(modelItemList); + } + + public ModelItemGroup() { + this(new ArrayList<>()); + } + + /** Searches the Model Items whose {@code name} and {@code appellation} match the given keywords. + * @param keyWords The given keywords. Each keyword should be separated by a blank. + * @return A Model Item Group. Returns {@code this} if the parameter {@code keyWords} is {@code null} or empty. + */ + public ModelItemGroup searchByKeyWords(String keyWords) { + if (keyWords == null || keyWords.isEmpty()) + return this; + // Word list: uppercase and deduplicate + String[] wordList = deduplicateArray(keyWords.toUpperCase().split(" ")); + // Word list: extend with zh-Hans and zh-Hant conversions + String[] wordListST = deduplicateArray(concatArrays( + wordList, + ZhConverterUtil.toSimple(keyWords).toUpperCase().split(" "), + ZhConverterUtil.toTraditional(keyWords).toUpperCase().split(" ") + )); + ModelItemGroup result = new ModelItemGroup(); + + // Rule: match name + for (ModelItem model : this) { + if (!result.contains(model) && model.name != null) { + String upper = model.name.toUpperCase(); + for (String word : wordListST) { + if (upper.contains(word)) { + result.add(model); + break; + } + } + } + } + + // Rule: match appellation + for (ModelItem model : this) { + if (!result.contains(model) && model.appellation != null) { + String upper = model.appellation.toUpperCase(); + for (String word : wordList) { + if (upper.contains(word)) { + result.add(model); + break; + } + } + } + } + + // Rule: match Pinyin suoxie + for (ModelItem model : this) { + if (!result.contains(model) && model.getPinyinSuoxie() != null) { + String upper = model.getPinyinSuoxie().toUpperCase(); + for (String word : wordList) { + if (upper.contains(word)) { + result.add(model); + break; + } + } + } + } + + // Rule: match Pinyin quanpin + for (ModelItem model : this) { + if (!result.contains(model) && model.getPinyinQuanpin() != null) { + String upper = model.getPinyinQuanpin().toUpperCase(); + for (String word : wordList) { + if (upper.contains(word)) { + result.add(model); + break; + } + } + } + } + + // Rule: match skin group name + for (ModelItem model : this) { + if (!result.contains(model) && model.skinGroupName != null) { + String upper = model.skinGroupName.toUpperCase(); + for (String word : wordListST) { + if (upper.contains(word)) { + result.add(model); + break; + } + } + } + } + + return result; + } + + /** Searches the Model Item whose relative path provided by {@code getLocation} matches the given path string. + * @param relPath The given path string. + * @return The first matched Model Item. Returns {@code null} if no one matched. + */ + public ModelItem searchByRelPath(String relPath) { + if (relPath == null || relPath.isEmpty()) + return null; + for (ModelItem model : this) + if (model.getLocation().equalsIgnoreCase(relPath)) + return model; + return null; + } + + /** Collects the values of a specified property of the Model Items. + * @param property A property extractor. + * @return A Set that contains all the possible values of the property. + * @param The type of the property value. + */ + public Set extract(PropertyExtractor property) { + HashSet result = new HashSet<>(); + for (ModelItem item : this) + result.addAll(property.apply(item)); + return result; + } + + /** Returns a new Model Item Group consisting of the Model Items that match the given predicate. + * @param predicate A predicate to apply to each Model Item to determine if it should be included. + * @return A Model Item Group. + */ + public ModelItemGroup filter(Predicate predicate) { + return new ModelItemGroup(modelItemList.stream().filter(predicate).toList()); + } + + /** Returns a new Model Item Group consisting of the Model Items whose property satisfied the requirements. + * @param property A property extractor. + * @param filterValues The property values to be matched. + * @param mode The {@link ModelItemGroup.FilterMode}. + * @return A Model Item Group. + * @param The type of the property value. + */ + public ModelItemGroup filter(PropertyExtractor property, Set filterValues, int mode) { + final boolean TRUE = (mode & FilterMode.MATCH_REVERSE) == 0; + return filter(item -> { + Set itemValues = property.apply(item); + if ((mode & FilterMode.MATCH_ANY) != 0) { + for (T value : itemValues) + if (filterValues.contains(value)) + return TRUE; + } else { + if (itemValues.containsAll(filterValues)) + return TRUE; + } + return !TRUE; + }); + } + + /** Returns a new Model Item Group consisting of the Model Items whose property satisfied the requirements. + * @param property A property extractor. + * @param filterValues The property values to be matched. + * @return A Model Item Group. + * @param The type of the property value. + */ + public ModelItemGroup filter(PropertyExtractor property, Set filterValues) { + return filter(property, filterValues, 0); + } + + /** Sorts the Model Items by their {@code assetDir} in natural order. + */ + public void sort() { + modelItemList.sort(Comparator.comparing(model -> model.assetDir, Comparator.naturalOrder())); + } + + protected static String[] concatArrays(String[] array1, String[] array2, String[] array3) { + String[] result = new String[array1.length + array2.length + array3.length]; + System.arraycopy(array1, 0, result, 0, array1.length); + System.arraycopy(array2, 0, result, array1.length, array2.length); + System.arraycopy(array3, 0, result, array1.length + array2.length, array3.length); + return result; + } + + protected static String[] deduplicateArray(String[] array) { + return Arrays.stream(array).distinct().toArray(String[]::new); + } + + + public static class FilterMode { + public static final int MATCH_ANY = 0b1; + public static final int MATCH_REVERSE = 0b10; + } + + + @Override + public Iterator iterator() { + return modelItemList.iterator(); + } + + @Override + public boolean add(ModelItem modelItem) { + return !modelItemList.contains(modelItem) && modelItemList.add(modelItem); + } + + @Override + public boolean addAll(Collection c) { + int size = size(); + c.forEach(this::add); + return size != size(); + } + + @Override + public boolean contains(Object o) { + return modelItemList.contains(o); + } + + @Override + public boolean containsAll(Collection c) { + return modelItemList.containsAll(c); + } + + @Override + public boolean remove(Object o) { + return modelItemList.remove(o); + } + + @Override + public boolean removeAll(Collection c) { + return modelItemList.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return modelItemList.retainAll(c); + } + + @Override + public void clear() { + modelItemList.clear(); + } + + @Override + public boolean isEmpty() { + return modelItemList.isEmpty(); + } + + @Override + public int size() { + return modelItemList.size(); + } + + @Override + public Object[] toArray() { + return modelItemList.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return modelItemList.toArray(a); + } +} diff --git a/core/src/cn/harryh/arkpets/assets/ModelsDataset.java b/core/src/cn/harryh/arkpets/assets/ModelsDataset.java index 288fdd05..1840395c 100644 --- a/core/src/cn/harryh/arkpets/assets/ModelsDataset.java +++ b/core/src/cn/harryh/arkpets/assets/ModelsDataset.java @@ -19,7 +19,7 @@ public class ModelsDataset { public final HashMap sortTags; public final String gameDataVersionDescription; public final String gameDataServerRegion; - public final AssetItemGroup data; + public final ModelItemGroup data; public final Version arkPetsCompatibility; public ModelsDataset(JSONObject jsonObject) { @@ -41,23 +41,23 @@ protected ModelsDataset(ModelsDatasetBean bean) { if (bean.data == null || bean.data.isEmpty()) throw new DatasetKeyException("data"); - data = new AssetItemGroup(); + data = new ModelItemGroup(); for (String key : bean.data.keySet()) { // Pre deserialization - AssetItem assetItem = bean.data.get(key).toJavaObject(AssetItem.class); + ModelItem modelItem = bean.data.get(key).toJavaObject(ModelItem.class); // Make up for `assetDir` field - if (assetItem == null || !storageDirectory.containsKey(assetItem.type)) + if (modelItem == null || !storageDirectory.containsKey(modelItem.type)) throw new DatasetKeyException("type"); - assetItem.key = key; - assetItem.assetDir = Path.of(storageDirectory.get(assetItem.type).toString(), key).toFile(); + modelItem.key = key; + modelItem.assetDir = Path.of(storageDirectory.get(modelItem.type).toString(), key).toFile(); // Compatible to lower version dataset - if (assetItem.assetList == null && assetItem.assetId != null && assetItem.checksum != null) { + if (modelItem.assetList == null && modelItem.assetId != null && modelItem.checksum != null) { HashMap defaultFileMap = new HashMap<>(); - for (String fileType : AssetItem.extensions) - defaultFileMap.put(fileType, assetItem.assetId + fileType); - assetItem.assetList = new JSONObject(defaultFileMap); + for (String fileType : ModelItem.extensions) + defaultFileMap.put(fileType, modelItem.assetId + fileType); + modelItem.assetList = new JSONObject(defaultFileMap); } - data.add(assetItem); + data.add(modelItem); } data.sort(); diff --git a/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java b/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java index 21310f87..793d7175 100644 --- a/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java +++ b/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java @@ -4,8 +4,8 @@ package cn.harryh.arkpets.controllers; import cn.harryh.arkpets.ArkHomeFX; -import cn.harryh.arkpets.assets.AssetItem; -import cn.harryh.arkpets.assets.AssetItemGroup; +import cn.harryh.arkpets.assets.ModelItem; +import cn.harryh.arkpets.assets.ModelItemGroup; import cn.harryh.arkpets.assets.ModelsDataset; import cn.harryh.arkpets.guitasks.*; import cn.harryh.arkpets.utils.*; @@ -60,7 +60,7 @@ public final class ModelsModule implements Controller { @FXML private Label searchModelStatus; @FXML - private JFXListView> searchModelView; + private JFXListView> searchModelView; @FXML private Label selectedModelName; @FXML @@ -108,9 +108,9 @@ public final class ModelsModule implements Controller { @FXML private Label modelHelp; - private AssetItemGroup assetItemList; - private JFXListCell selectedModelCell; - private ArrayList> modelCellList = new ArrayList<>(); + private ModelItemGroup assetItemList; + private JFXListCell selectedModelCell; + private ArrayList> modelCellList = new ArrayList<>(); private ObservableSet filterTagSet = FXCollections.observableSet(); private GuiPrefabs.PeerNodeComposer infoPaneComposer; @@ -158,7 +158,7 @@ public boolean initModelsDataset(boolean doPopNotice) { IOUtils.FileUtil.readString(new File(PathConfig.fileModelsDataPath), charsetDefault) ) ); - app.modelsDataset.data.removeIf(Predicate.not(AssetItem::isValid)); + app.modelsDataset.data.removeIf(Predicate.not(ModelItem::isValid)); try { // Check the dataset compatibility Version compatibleVersion = app.modelsDataset.arkPetsCompatibility; @@ -415,7 +415,7 @@ private void initModelFavorite() { modelFavorite.setGraphic(favIcon); Logger.debug("ModelManager", "Remove favorite model " + key); } else { - app.config.character_favorites.put(key, new AssetItem.AssetPrefab()); + app.config.character_favorites.put(key, new ModelItem.AssetPrefab()); selectedModelCell.getStyleClass().add("Search-models-item-favorite"); modelFavorite.setGraphic(favFillIcon); Logger.debug("ModelManager", "Add favorite model " + key); @@ -433,9 +433,9 @@ private void initModelFavorite() { } filterFavorite = !filterFavorite; modelSearch(searchModelInput.getText()); - AssetItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset); + ModelItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset); if (recentSelected != null) - for (JFXListCell cell : searchModelView.getItems()) + for (JFXListCell cell : searchModelView.getItems()) if (recentSelected.equals(cell.getItem())) { searchModelView.scrollTo(cell); searchModelView.getSelectionModel().select(cell); @@ -447,21 +447,25 @@ public void modelSearch(String keyWords) { searchModelView.getItems().clear(); searchModelStatus.setText(""); if (assertModelLoaded(false)) { - // Filter and search assets + // Filter assets int rawSize = assetItemList.size(); - AssetItemGroup favoured = !filterFavorite ? assetItemList : - assetItemList.filter(AssetItem.PropertyExtractor.ASSET_ITEM_KEY, app.config.character_favorites.keySet(), AssetItemGroup.FilterMode.MATCH_ANY); - AssetItemGroup filtered = filterTagSet.isEmpty() ? favoured : - favoured.filter(AssetItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS, filterTagSet); - AssetItemGroup searched = filtered.searchByKeyWords(keyWords); + ModelItemGroup favoured = !filterFavorite ? assetItemList : + assetItemList.filter(ModelItem.PropertyExtractor.ASSET_ITEM_KEY, app.config.character_favorites.keySet(), ModelItemGroup.FilterMode.MATCH_ANY); + ModelItemGroup filtered = filterTagSet.isEmpty() ? favoured : + favoured.filter(ModelItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS, filterTagSet); + // Search assets + long tStart = System.nanoTime(); + ModelItemGroup searched = filtered.searchByKeyWords(keyWords); + long tEnd = System.nanoTime(); int curSize = searched.size(); searchModelStatus.setText((rawSize == curSize ? rawSize : curSize + " / " + rawSize) + " 个模型"); // Add cells - for (JFXListCell cell : modelCellList) + for (JFXListCell cell : modelCellList) if (searched.contains(cell.getItem())) searchModelView.getItems().add(cell); + Logger.info("ModelManager", "Search \"%s\" (%d results, %.1f ms)" + .formatted(keyWords, curSize, (tEnd - tStart) / 1000000f)); } - Logger.info("ModelManager", "Search \"" + keyWords + "\" (" + searchModelView.getItems().size() + ")"); searchModelView.refresh(); } @@ -479,19 +483,19 @@ public void modelReload(boolean doPopNotice) { Logger.info("ModelManager", "Reloading"); boolean willGc = modelCellList != null; modelCellList = new ArrayList<>(); - assetItemList = new AssetItemGroup(); + assetItemList = new ModelItemGroup(); if (initModelsDataset(doPopNotice)) { // 1. Update list cells and asset items: try { // Find every model assets. - assetItemList.addAll(app.modelsDataset.data.filter(AssetItem::isExisted)); + assetItemList.addAll(app.modelsDataset.data.filter(ModelItem::isExisted)); if (assetItemList.isEmpty()) throw new IOException("Found no assets in the target directories."); // Initialize list view. searchModelView.getSelectionModel().getSelectedItems().addListener( - (ListChangeListener>) (observable -> observable.getList().forEach( - (Consumer>) cell -> selectModel(cell.getItem(), cell)) + (ListChangeListener>) (observable -> observable.getList().forEach( + (Consumer>) cell -> selectModel(cell.getItem(), cell)) ) ); searchModelView.setFixedCellSize(30); @@ -502,7 +506,7 @@ public void modelReload(boolean doPopNotice) { // Explicitly set all lists to empty. Logger.error("ModelManager", "Failed to initialize model assets due to unknown reasons, details see below.", ex); modelCellList = new ArrayList<>(); - assetItemList = new AssetItemGroup(); + assetItemList = new ModelItemGroup(); if (doPopNotice) GuiPrefabs.Dialogs.createCommonDialog(app.body, GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_WARNING_ALT, GuiPrefabs.COLOR_WARNING), @@ -529,7 +533,7 @@ public void modelReload(boolean doPopNotice) { }); filterPaneTagFlow.getChildren().clear(); if (assetItemList != null && app.modelsDataset != null) { - ArrayList sortTags = new ArrayList<>(assetItemList.extract(AssetItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS)); + ArrayList sortTags = new ArrayList<>(assetItemList.extract(ModelItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS)); sortTags.sort(Comparator.naturalOrder()); sortTags.forEach(s -> { String t = app.modelsDataset.sortTags == null ? s : app.modelsDataset.sortTags.getOrDefault(s, s); @@ -553,9 +557,9 @@ public void modelReload(boolean doPopNotice) { if (assetItemList != null && !modelCellList.isEmpty() && app.config.character_asset != null && !app.config.character_asset.isEmpty()) { // Scroll to recent selected model and then select it. - AssetItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset); + ModelItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset); if (recentSelected != null) { - for (JFXListCell cell : searchModelView.getItems()) + for (JFXListCell cell : searchModelView.getItems()) if (recentSelected.equals(cell.getItem())) { searchModelView.scrollTo(cell); searchModelView.getSelectionModel().select(cell); @@ -577,20 +581,20 @@ public void modelReload(boolean doPopNotice) { }); } - private JFXListCell getMenuItem(AssetItem assetItem, JFXListView> container) { + private JFXListCell getMenuItem(ModelItem modelItem, JFXListView> container) { double width = container.getPrefWidth() - 50; double height = 30; double divide = 0.618; - JFXListCell item = new JFXListCell<>(); + JFXListCell item = new JFXListCell<>(); item.getStyleClass().addAll("Search-models-item"); - Label name = new Label(assetItem.toString()); + Label name = new Label(modelItem.toString()); name.getStyleClass().addAll("Search-models-label", "Search-models-label-primary"); - name.setPrefSize(assetItem.skinGroupName == null ? width : width * divide, height); + name.setPrefSize(modelItem.skinGroupName == null ? width : width * divide, height); name.setLayoutX(15); - Label alias1 = new Label(assetItem.skinGroupName); + Label alias1 = new Label(modelItem.skinGroupName); alias1.getStyleClass().addAll("Search-models-label", "Search-models-label-secondary"); alias1.setPrefSize(width * (1 - divide), height); - alias1.setLayoutX(assetItem.skinGroupName == null ? 0 : width * divide); + alias1.setLayoutX(modelItem.skinGroupName == null ? 0 : width * divide); SVGPath fav = GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_STAR_FILLED, GuiPrefabs.COLOR_WARNING); fav.getStyleClass().add("Search-models-star"); fav.setLayoutX(0); @@ -599,14 +603,14 @@ private JFXListCell getMenuItem(AssetItem assetItem, JFXListView item) { + private void selectModel(ModelItem asset, JFXListCell item) { // Reset if (selectedModelCell != null) { selectedModelCell.getStyleClass().setAll("Search-models-item"); diff --git a/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java b/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java index 30ee36a3..a841a0b9 100644 --- a/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java +++ b/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java @@ -3,8 +3,8 @@ */ package cn.harryh.arkpets.guitasks; -import cn.harryh.arkpets.assets.AssetItem; -import cn.harryh.arkpets.assets.AssetItemGroup; +import cn.harryh.arkpets.assets.ModelItem; +import cn.harryh.arkpets.assets.ModelItemGroup; import cn.harryh.arkpets.assets.ModelsDataset; import cn.harryh.arkpets.utils.GuiPrefabs; import cn.harryh.arkpets.utils.Logger; @@ -39,12 +39,12 @@ protected Task getTask() { return new Task<>() { @Override protected Boolean call() { - AssetItemGroup validModelAssets = modelsDataset.data.filter(AssetItem::isValid); + ModelItemGroup validModelAssets = modelsDataset.data.filter(ModelItem::isValid); int currentProgress = 0; int totalProgress = validModelAssets.size(); boolean flag = false; - for (AssetItem item : validModelAssets) { + for (ModelItem item : validModelAssets) { this.updateProgress(currentProgress++, totalProgress); if (this.isCancelled()) { // Cancelled: