diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index 38e6217b5..6b8010424 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -77,7 +77,7 @@ public final class HMCLModpackInstallTask extends Task { } catch (JsonParseException | IOException ignore) { } dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/minecraft", it -> !"pack.json".equals(it), config)); - dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); + dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index b7af7e002..2cc9ea024 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -17,15 +17,19 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.control.Label; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.util.StringUtils; public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private final StringProperty title = new SimpleStringProperty(this, "title"); + private final StringProperty tag = new SimpleStringProperty(this, "tag"); private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle"); public TwoLineListItem(String titleString, String subtitleString) { @@ -37,15 +41,28 @@ public class TwoLineListItem extends VBox { public TwoLineListItem() { setMouseTransparent(true); + + HBox firstLine = new HBox(); + Label lblTitle = new Label(); lblTitle.getStyleClass().add("title"); lblTitle.textProperty().bind(title); + Label lblTag = new Label(); + lblTag.getStyleClass().add("tag"); + lblTag.textProperty().bind(tag); + + lblTag.visibleProperty().bind(Bindings.createBooleanBinding( + () -> StringUtils.isNotBlank(tag.getValue()), + tag)); + + firstLine.getChildren().addAll(lblTitle, lblTag); + Label lblSubtitle = new Label(); lblSubtitle.getStyleClass().add("subtitle"); lblSubtitle.textProperty().bind(subtitle); - getChildren().setAll(lblTitle, lblSubtitle); + getChildren().setAll(firstLine, lblSubtitle); getStyleClass().add(DEFAULT_STYLE_CLASS); } @@ -73,6 +90,18 @@ public class TwoLineListItem extends VBox { this.subtitle.set(subtitle); } + public String getTag() { + return tag.get(); + } + + public StringProperty tagProperty() { + return tag; + } + + public void setTag(String tag) { + this.tag.set(tag); + } + @Override public String toString() { return getTitle(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java index 32864df62..b1c42c4d3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java @@ -121,7 +121,7 @@ public final class LocalModpackPage extends StackPane implements WizardPage { lblModpackLocation.setText(selectedFile.getAbsolutePath()); if (!name.isPresent()) { - txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion())); + txtModpackName.setText(manifest.getName()); txtModpackName.getValidators().addAll( new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)), new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java index e84ae018e..ed8e224fb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java @@ -101,7 +101,7 @@ public class RemoteModpackPage extends StackPane implements WizardPage { lblAuthor.setText(manifest.getAuthor()); if (!name.isPresent()) { - txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion())); + txtModpackName.setText(manifest.getName()); txtModpackName.getValidators().addAll( new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)), new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java index 365e1e50e..51e105bed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java @@ -27,16 +27,20 @@ import javafx.scene.control.Skin; import javafx.scene.image.Image; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.GameVersion; +import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.util.i18n.I18n; +import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT; import static org.jackhuang.hmcl.util.Lang.handleUncaught; import static org.jackhuang.hmcl.util.Lang.threadPool; +import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -48,6 +52,7 @@ public class GameItem extends Control { private final Profile profile; private final String version; private final StringProperty title = new SimpleStringProperty(); + private final StringProperty tag = new SimpleStringProperty(); private final StringProperty subtitle = new SimpleStringProperty(); private final ObjectProperty image = new SimpleObjectProperty<>(); @@ -75,6 +80,17 @@ public class GameItem extends Control { }, Platform::runLater) .exceptionally(handleUncaught); + CompletableFuture.runAsync(() -> { + try { + ModpackConfiguration config = profile.getRepository().readModpackConfiguration(version); + if (config == null) return; + tag.set(config.getVersion()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to read modpack configuration from ", e); + } + }, Platform::runLater) + .exceptionally(handleUncaught); + title.set(id); image.set(profile.getRepository().getVersionIconImage(version)); } @@ -96,6 +112,10 @@ public class GameItem extends Control { return title; } + public StringProperty tagProperty() { + return tag; + } + public StringProperty subtitleProperty() { return subtitle; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItemSkin.java index 2f950dd41..16638033a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItemSkin.java @@ -45,6 +45,7 @@ public class GameItemSkin extends SkinBase { TwoLineListItem item = new TwoLineListItem(); item.titleProperty().bind(skinnable.titleProperty()); + item.tagProperty().bind(skinnable.tagProperty()); item.subtitleProperty().bind(skinnable.subtitleProperty()); BorderPane.setAlignment(item, Pos.CENTER); center.getChildren().setAll(imageView, item); diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c3fe72dd2..e4c8ebe67 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -114,15 +114,26 @@ -fx-padding: 4 0 4 0; } -.two-line-list-item > .title { +.two-line-list-item HBox { + -fx-spacing: 8; + -fx-alignment: center-left; +} + +.two-line-list-item .title { -fx-text-fill: #292929; -fx-font-size: 15px; } -.two-line-list-item > .subtitle { +.two-line-list-item .subtitle { -fx-text-fill: rgba(0, 0, 0, 0.5); } +.two-line-list-item .tag { + -fx-text-fill: -fx-base-color; + -fx-background-color: -fx-base-rippler-color; + -fx-padding: 2; +} + .bubble { -fx-background-color: rgba(0, 0, 0, 0.5); -fx-background-radius: 2px; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 01a0255e0..926ce7e09 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.event.Event; @@ -29,11 +30,13 @@ import org.jackhuang.hmcl.event.RefreshingVersionsEvent; import org.jackhuang.hmcl.event.RemoveVersionEvent; import org.jackhuang.hmcl.event.RenameVersionEvent; import org.jackhuang.hmcl.mod.ModManager; +import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -437,6 +440,22 @@ public class DefaultGameRepository implements GameRepository { return new File(getVersionRoot(version), "modpack.json"); } + /** + * read modpack configuration for a version. + * @param version version installed as modpack + * @param manifest type of ModpackConfiguration + * @return modpack configuration object, or null if this version is not a modpack. + * @throws VersionNotFoundException if version does not exist. + * @throws IOException if an i/o error occurs. + */ + @Nullable + public ModpackConfiguration readModpackConfiguration(String version) throws IOException, VersionNotFoundException { + if (!hasVersion(version)) throw new VersionNotFoundException(version); + File file = getModpackConfiguration(version); + if (!file.exists()) return null; + return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken>(){}.getType()); + } + public boolean isModpack(String version) { return getModpackConfiguration(version).exists(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java index 17caf14cd..b59c0011d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java @@ -41,14 +41,18 @@ public final class MinecraftInstanceTask extends Task { private final File jsonFile; private final T manifest; private final String type; + private final String name; + private final String version; - public MinecraftInstanceTask(File zipFile, Charset encoding, String subDirectory, T manifest, String type, File jsonFile) { + public MinecraftInstanceTask(File zipFile, Charset encoding, String subDirectory, T manifest, String type, String name, String version, File jsonFile) { this.zipFile = zipFile; this.encoding = encoding; this.subDirectory = FileUtils.normalizePath(subDirectory); this.manifest = manifest; this.jsonFile = jsonFile; this.type = type; + this.name = name; + this.version = version; } @Override @@ -69,6 +73,6 @@ public final class MinecraftInstanceTask extends Task { }); } - FileUtils.writeText(jsonFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(manifest, type, overrides))); + FileUtils.writeText(jsonFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(manifest, type, name, version, overrides))); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java index 9e4d597f1..611aeba20 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.mod; import com.google.gson.JsonParseException; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.Validation; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -30,15 +31,19 @@ public final class ModpackConfiguration implements Validation { private final T manifest; private final String type; + private final String name; + private final String version; private final List overrides; public ModpackConfiguration() { - this(null, null, Collections.emptyList()); + this(null, null, "", null, Collections.emptyList()); } - public ModpackConfiguration(T manifest, String type, List overrides) { + public ModpackConfiguration(T manifest, String type, String name, String version, List overrides) { this.manifest = manifest; this.type = type; + this.name = name; + this.version = version; this.overrides = new ArrayList<>(overrides); } @@ -50,12 +55,25 @@ public final class ModpackConfiguration implements Validation { return type; } + public String getName() { + return name; + } + + @Nullable + public String getVersion() { + return version; + } + public ModpackConfiguration setManifest(T manifest) { - return new ModpackConfiguration<>(manifest, type, overrides); + return new ModpackConfiguration<>(manifest, type, name, version, overrides); } public ModpackConfiguration setOverrides(List overrides) { - return new ModpackConfiguration<>(manifest, type, overrides); + return new ModpackConfiguration<>(manifest, type, name, version, overrides); + } + + public ModpackConfiguration setVersion(String version) { + return new ModpackConfiguration<>(manifest, type, name, version, overrides); } public List getOverrides() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index 721f25bbf..7313c833a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -107,7 +107,7 @@ public final class CurseInstallTask extends Task { } this.config = config; dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), manifest.getOverrides(), any -> true, config).withStage("hmcl.modpack")); - dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), manifest.getOverrides(), manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); + dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), manifest.getOverrides(), manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest).withStage("hmcl.modpack.download")); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 4a08b2fe3..1918d0fb2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -131,7 +131,7 @@ public final class MultiMCModpackInstallTask extends Task { dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", any -> true, config).withStage("hmcl.modpack")); } - dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); + dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, manifest.getName(), null, repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java index b4d45f982..7a5023291 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java @@ -158,6 +158,6 @@ public class ServerModpackCompletionTask extends Task { public void postExecute() throws Exception { if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return; File manifestFile = repository.getModpackConfiguration(version); - FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), remoteManifest.getFiles()))); + FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), this.manifest.getName(), this.manifest.getVersion(), remoteManifest.getFiles()))); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java index 8eefe29e3..8406c9fbc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java @@ -82,7 +82,7 @@ public class ServerModpackLocalInstallTask extends Task { } catch (JsonParseException | IOException ignore) { } dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/overrides", any -> true, config).withStage("hmcl.modpack")); - dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); + dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java index 547118643..0b7e652d5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java @@ -88,7 +88,7 @@ public class ServerModpackRemoteInstallTask extends Task { @Override public void execute() throws Exception { - dependencies.add(new ServerModpackCompletionTask(dependency, name, new ModpackConfiguration<>(manifest, MODPACK_TYPE, Collections.emptyList()))); + dependencies.add(new ServerModpackCompletionTask(dependency, name, new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList()))); } public static final String MODPACK_TYPE = "Server";