diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaMetadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaMetadata.java index dbc597249..83ae9a613 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaMetadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaMetadata.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.terracotta.provider.GeneralProvider; import org.jackhuang.hmcl.terracotta.provider.ITerracottaProvider; import org.jackhuang.hmcl.terracotta.provider.MacOSProvider; import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.i18n.LocalizedText; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OSVersion; @@ -45,7 +46,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -54,6 +54,9 @@ public final class TerracottaMetadata { private TerracottaMetadata() { } + public record Link(@SerializedName("desc") LocalizedText description, String link) { + } + private record Config( @SerializedName("version_legacy") String legacy, @SerializedName("version_recent") List recent, @@ -61,7 +64,7 @@ public final class TerracottaMetadata { @SerializedName("classifiers") Map classifiers, @SerializedName("downloads") List downloads, - @SerializedName("links") List links + @SerializedName("links") List links ) { private TerracottaNative of(String classifier) { List links = new ArrayList<>(this.downloads.size()); @@ -87,7 +90,7 @@ public final class TerracottaMetadata { public static final ITerracottaProvider PROVIDER; public static final String PACKAGE_NAME; - private static final List PACKAGE_LINKS; + private static final List PACKAGE_LINKS; private static final Pattern LEGACY; private static final List RECENT; @@ -110,12 +113,14 @@ public final class TerracottaMetadata { PACKAGE_NAME = context != null ? String.format("terracotta-%s-%s-%s-pkg.tar.gz", config.latest, context.system, context.arch) : null; if (context != null) { - List packageLinks = new ArrayList<>(config.links.size()); - for (String link : config.links) { - packageLinks.add(link.replace("${version}", LATEST) - .replace("${system}", context.system) - .replace("${arch}", context.arch) - ); + List packageLinks = new ArrayList<>(config.links.size()); + for (Link link : config.links) { + packageLinks.add(new Link( + link.description, + link.link.replace("${version}", LATEST) + .replace("${system}", context.system) + .replace("${arch}", context.arch) + )); } PACKAGE_LINKS = Collections.unmodifiableList(packageLinks); @@ -127,8 +132,8 @@ public final class TerracottaMetadata { private record ProviderContext(ITerracottaProvider provider, String system, String arch) { } - public static String getPackageLink() { - return PACKAGE_LINKS.get(ThreadLocalRandom.current().nextInt(0, PACKAGE_LINKS.size())); + public static List getPackageLinks() { + return PACKAGE_LINKS; } @Nullable diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index 5603f158d..70a6c92ff 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -73,8 +73,7 @@ final class ComponentListCell extends StackPane { @SuppressWarnings("unchecked") private void updateLayout() { - if (content instanceof ComponentList) { - ComponentList list = (ComponentList) content; + if (content instanceof ComponentList list) { content.getStyleClass().remove("options-list"); content.getStyleClass().add("options-sublist"); @@ -130,7 +129,10 @@ final class ComponentListCell extends StackPane { groupNode.getChildren().add(headerRippler); VBox container = new VBox(); - container.setPadding(new Insets(8, 16, 10, 16)); + boolean hasPadding = !content.getProperties().containsKey("ComponentSubList.noPadding"); + if (hasPadding) { + container.setPadding(new Insets(8, 16, 10, 16)); + } FXUtils.setLimitHeight(container, 0); FXUtils.setOverflowHidden(container); container.getChildren().setAll(content); @@ -149,7 +151,7 @@ final class ComponentListCell extends StackPane { } Platform.runLater(() -> { - double newAnimatedHeight = (list.prefHeight(list.getWidth()) + 8 + 10) * (expanded ? 1 : -1); + double newAnimatedHeight = (list.prefHeight(list.getWidth()) + (hasPadding ? 8 + 10 : 0)) * (expanded ? 1 : -1); double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(list.getWidth()); double contentHeight = expanded ? newAnimatedHeight : 0; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java index 2cb7e4ea0..1c0d3052f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java @@ -59,10 +59,12 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.ComponentSublist; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.ui.versions.Versions; +import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; import java.nio.file.Files; @@ -182,14 +184,7 @@ public class TerracottaControllerPage extends StackPane { } }); - LineButton local = LineButton.of(); - local.setLeftImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); - local.setTitle(i18n("terracotta.from_local.title")); - local.setSubtitle(i18n("terracotta.from_local.desc")); - local.setRightIcon(SVG.OPEN_IN_NEW); - FXUtils.onClicked(local, () -> FXUtils.openLink(TerracottaMetadata.getPackageLink())); - - nodesProperty.setAll(body, download, local); + nodesProperty.setAll(body, download, getThirdPartyDownloadNodes()); } else if (state instanceof TerracottaState.Preparing) { statusProperty.set(i18n("terracotta.status.preparing")); progressProperty.bind(((TerracottaState.Preparing) state).progressProperty()); @@ -432,13 +427,7 @@ public class TerracottaControllerPage extends StackPane { }); if (fatal.getType() == TerracottaState.Fatal.Type.NETWORK) { - LineButton local = LineButton.of(); - local.setLeftImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); - local.setTitle(i18n("terracotta.from_local.title")); - local.setSubtitle(i18n("terracotta.from_local.desc")); - local.setRightIcon(SVG.OPEN_IN_NEW); - FXUtils.onClicked(local, () -> FXUtils.openLink(TerracottaMetadata.getPackageLink())); - nodesProperty.setAll(retry, local); + nodesProperty.setAll(retry, getThirdPartyDownloadNodes()); } else { nodesProperty.setAll(retry); } @@ -486,6 +475,35 @@ public class TerracottaControllerPage extends StackPane { getChildren().setAll(scrollPane); } + private ComponentList getThirdPartyDownloadNodes() { + ComponentSublist locals = new ComponentSublist(); + locals.getProperties().put("ComponentSubList.noPadding", true); + + LineButton header = LineButton.of(false); + header.setLeftImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); + header.setTitle(i18n("terracotta.from_local.title")); + header.setSubtitle(i18n("terracotta.from_local.desc")); + locals.setHeaderLeft(header); + + for (TerracottaMetadata.Link link : TerracottaMetadata.getPackageLinks()) { + HBox node = new HBox(); + node.setAlignment(Pos.CENTER_LEFT); + node.setPadding(new Insets(10, 16, 10, 16)); + + Label description = new Label(link.description().getText(I18n.getLocale().getCandidateLocales())); + HBox placeholder = new HBox(); + HBox.setHgrow(placeholder, Priority.ALWAYS); + Node icon = SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16); + node.getChildren().setAll(description, placeholder, icon); + + RipplerContainer container = new RipplerContainer(node); + container.setOnMouseClicked(ev -> FXUtils.openLink(link.link())); + container.getProperties().put("ComponentList.noPadding", true); + locals.getContent().add(container); + } + return locals; + } + private static final class LineButton extends RipplerContainer { private final WeakListenerHolder holder = new WeakListenerHolder(); @@ -494,8 +512,14 @@ public class TerracottaControllerPage extends StackPane { private final ObjectProperty right = new SimpleObjectProperty<>(); public static LineButton of() { + return of(true); + } + + public static LineButton of(boolean padding) { HBox container = new HBox(); - container.setPadding(new Insets(10, 16, 10, 16)); + if (padding) { + container.setPadding(new Insets(10, 16, 10, 16)); + } container.setAlignment(Pos.CENTER_LEFT); container.setCursor(Cursor.HAND); container.setSpacing(16); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index f7bec7742..079fa8527 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1419,7 +1419,7 @@ terracotta.status=Lobby terracotta.back=Exit terracotta.network_warning=Terracotta | Multiplayer is based on P2P. The final experience depends greatly on your network. terracotta.sudo_installing=HMCL must verify your password before installing Multiplayer Core -terracotta.from_local.title=Open third-party download link for Multiplayer Core +terracotta.from_local.title=Third-party download links for Multiplayer Core terracotta.from_local.desc=In some regions, the default download channel for Multiplayer Core may be unstable. terracotta.from_local.file_name_mismatch=You should download the Multiplayer Core package named %1$s instead of %2$s terracotta.status.bootstrap=Gathering information diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 0c2c6dc2c..8c0fa2071 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1207,7 +1207,7 @@ terracotta.status=聯機大廳 terracotta.back=退出 terracotta.network_warning=多人連線基於 p2p,最終線上體驗和您的網路情況有較大關係。 terracotta.sudo_installing=HMCL 需要驗證您的密碼才能安裝線上核心 -terracotta.from_local.title=開啟線上核心第三方下載鏈接 +terracotta.from_local.title=線上核心第三方下載鏈接 terracotta.from_local.desc=在部分地區,線上核心預設下載管道可能不穩定 terracotta.from_local.file_name_mismatch=您應該下載名為 %1$s 的線上核心包,而非 %2$s terracotta.status.bootstrap=正在收集信息 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index a5a17408f..136c87060 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1218,7 +1218,7 @@ terracotta.status=联机大厅 terracotta.back=退出 terracotta.network_warning=多人联机基于 p2p,最终联机体验和您的网络情况有较大关系。 terracotta.sudo_installing=HMCL 需要验证您的密码才能安装联机核心 -terracotta.from_local.title=打开联机核心第三方下载链接 +terracotta.from_local.title=联机核心第三方下载链接 terracotta.from_local.desc=在部分地区,联机核心默认下载渠道可能不稳定 terracotta.from_local.file_name_mismatch=您应当下载名为 %1$s 的联机核心包,而非 %2$s terracotta.status.bootstrap=正在收集信息 diff --git a/HMCL/src/main/resources/assets/terracotta.json b/HMCL/src/main/resources/assets/terracotta.json index 2243737ce..16b004cbd 100644 --- a/HMCL/src/main/resources/assets/terracotta.json +++ b/HMCL/src/main/resources/assets/terracotta.json @@ -25,6 +25,13 @@ "https://cp.zkitefly.eu.org/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}" ], "links": [ - "https://github.com/burningtnt/Terracotta/releases/tag/v${version}" + { + "desc": { + "default": "GitHub Release", + "zh": "GitHub 发布页", + "zh-Hant": "GitHub 發布頁" + }, + "link": "https://github.com/burningtnt/Terracotta/releases/tag/v${version}" + } ] } \ No newline at end of file