mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-10 04:16:02 -04:00
fix: cannot display all game versions in some cases of DownloadPage. Closes #1007.
This commit is contained in:
parent
97f08bf74e
commit
d7f5109a65
@ -272,9 +272,9 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
|||||||
});
|
});
|
||||||
|
|
||||||
HBox box = new HBox(8);
|
HBox box = new HBox(8);
|
||||||
Hyperlink birthLink = new Hyperlink(i18n("account.methods.microsoft.birth"));
|
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
|
||||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/zh-cn/account-billing/如何更改-microsoft-帐户上的出生日期-837badbc-999e-54d2-2617-d19206b9540a"));
|
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/zh-cn/account-billing/如何更改-microsoft-帐户上的出生日期-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||||
Hyperlink profileLink = new Hyperlink(i18n("account.methods.microsoft.profile"));
|
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||||
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
||||||
box.getChildren().setAll(profileLink, birthLink);
|
box.getChildren().setAll(profileLink, birthLink);
|
||||||
GridPane.setColumnSpan(box, 2);
|
GridPane.setColumnSpan(box, 2);
|
||||||
@ -415,7 +415,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
|||||||
|
|
||||||
if (factory instanceof YggdrasilAccountFactory) {
|
if (factory instanceof YggdrasilAccountFactory) {
|
||||||
HBox box = new HBox();
|
HBox box = new HBox();
|
||||||
Hyperlink migrationLink = new Hyperlink(i18n("account.methods.yggdrasil.migration"));
|
JFXHyperlink migrationLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration"));
|
||||||
migrationLink.setOnAction(e -> FXUtils.openLink("https://help.minecraft.net/hc/en-us/articles/360050865492-JAVA-Account-Migration-FAQ"));
|
migrationLink.setOnAction(e -> FXUtils.openLink("https://help.minecraft.net/hc/en-us/articles/360050865492-JAVA-Account-Migration-FAQ"));
|
||||||
GridPane.setColumnSpan(box, 2);
|
GridPane.setColumnSpan(box, 2);
|
||||||
box.getChildren().setAll(migrationLink);
|
box.getChildren().setAll(migrationLink);
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
|
import javafx.scene.control.Hyperlink;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
|
|
||||||
|
public class JFXHyperlink extends Hyperlink {
|
||||||
|
public JFXHyperlink(String text) {
|
||||||
|
super(text);
|
||||||
|
|
||||||
|
setGraphic(SVG.launchOutline(Theme.blackFillBinding(), 16, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,10 +22,14 @@ import javafx.beans.property.SimpleStringProperty;
|
|||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.FlowPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.util.AggregatedObservableList;
|
||||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
|
|
||||||
public class TwoLineListItem extends VBox {
|
public class TwoLineListItem extends VBox {
|
||||||
@ -35,7 +39,8 @@ public class TwoLineListItem extends VBox {
|
|||||||
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
||||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
|
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
|
||||||
|
|
||||||
private final ObservableList<Label> tagLabels;
|
private final ObservableList<Node> tagLabels;
|
||||||
|
private final AggregatedObservableList<Node> firstLineChildren;
|
||||||
|
|
||||||
public TwoLineListItem(String titleString, String subtitleString) {
|
public TwoLineListItem(String titleString, String subtitleString) {
|
||||||
this();
|
this();
|
||||||
@ -47,23 +52,24 @@ public class TwoLineListItem extends VBox {
|
|||||||
public TwoLineListItem() {
|
public TwoLineListItem() {
|
||||||
setMouseTransparent(true);
|
setMouseTransparent(true);
|
||||||
|
|
||||||
HBox firstLine = new HBox();
|
FlowPane firstLine = new FlowPane();
|
||||||
|
firstLine.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
Label lblTitle = new Label();
|
Label lblTitle = new Label();
|
||||||
lblTitle.getStyleClass().add("title");
|
lblTitle.getStyleClass().add("title");
|
||||||
lblTitle.textProperty().bind(title);
|
lblTitle.textProperty().bind(title);
|
||||||
|
|
||||||
HBox tagContainer = new HBox();
|
|
||||||
|
|
||||||
tagLabels = MappedObservableList.create(tags, tag -> {
|
tagLabels = MappedObservableList.create(tags, tag -> {
|
||||||
Label tagLabel = new Label();
|
Label tagLabel = new Label();
|
||||||
tagLabel.getStyleClass().add("tag");
|
tagLabel.getStyleClass().add("tag");
|
||||||
tagLabel.setText(tag);
|
tagLabel.setText(tag);
|
||||||
|
FlowPane.setMargin(tagLabel, new Insets(0, 8, 0, 0));
|
||||||
return tagLabel;
|
return tagLabel;
|
||||||
});
|
});
|
||||||
Bindings.bindContent(tagContainer.getChildren(), tagLabels);
|
firstLineChildren = new AggregatedObservableList<>();
|
||||||
|
firstLineChildren.appendList(FXCollections.singletonObservableList(lblTitle));
|
||||||
firstLine.getChildren().addAll(lblTitle, tagContainer);
|
firstLineChildren.appendList(tagLabels);
|
||||||
|
Bindings.bindContent(firstLine.getChildren(), firstLineChildren.getAggregatedList());
|
||||||
|
|
||||||
Label lblSubtitle = new Label();
|
Label lblSubtitle = new Label();
|
||||||
lblSubtitle.getStyleClass().add("subtitle");
|
lblSubtitle.getStyleClass().add("subtitle");
|
||||||
|
@ -17,17 +17,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import com.jfoenix.controls.*;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXComboBox;
|
||||||
|
import com.jfoenix.controls.JFXListView;
|
||||||
|
import com.jfoenix.controls.JFXTextField;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.HPos;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
@ -47,7 +52,9 @@ import org.jackhuang.hmcl.util.StringUtils;
|
|||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
@ -61,6 +68,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
private final ListProperty<CurseAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
private final ListProperty<CurseAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||||
private final DownloadPage.DownloadCallback callback;
|
private final DownloadPage.DownloadCallback callback;
|
||||||
private boolean searchInitialized = false;
|
private boolean searchInitialized = false;
|
||||||
|
protected final BooleanProperty supportChinese = new SimpleBooleanProperty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MODPACK
|
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MODPACK
|
||||||
@ -121,6 +129,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
|
|
||||||
public void search(String userGameVersion, int category, int pageOffset, String searchFilter, int sort) {
|
public void search(String userGameVersion, int category, int pageOffset, String searchFilter, int sort) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setFailed(false);
|
||||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||||
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
||||||
: null;
|
: null;
|
||||||
@ -194,11 +203,10 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
}
|
}
|
||||||
|
|
||||||
JFXTextField nameField = new JFXTextField();
|
JFXTextField nameField = new JFXTextField();
|
||||||
nameField.setPromptText(i18n("mods.name"));
|
nameField.setPromptText(getSkinnable().supportChinese.get() ? i18n("search.hint.chinese") : i18n("search.hint.english"));
|
||||||
|
|
||||||
JFXTextField gameVersionField = new JFXTextField();
|
JFXTextField gameVersionField = new JFXTextField();
|
||||||
Label lblGameVersion = new Label(i18n("world.game_version"));
|
Label lblGameVersion = new Label(i18n("world.game_version"));
|
||||||
gameVersionField.setPromptText(i18n("world.game_version"));
|
|
||||||
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
|
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
|
||||||
|
|
||||||
ObjectBinding<Boolean> hasVersion = BindingMapping.of(getSkinnable().version)
|
ObjectBinding<Boolean> hasVersion = BindingMapping.of(getSkinnable().version)
|
||||||
@ -239,7 +247,6 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
sortStackPane.getChildren().setAll(sortComboBox);
|
sortStackPane.getChildren().setAll(sortComboBox);
|
||||||
sortComboBox.prefWidthProperty().bind(sortStackPane.widthProperty());
|
sortComboBox.prefWidthProperty().bind(sortStackPane.widthProperty());
|
||||||
sortComboBox.getStyleClass().add("fit-width");
|
sortComboBox.getStyleClass().add("fit-width");
|
||||||
sortComboBox.setPromptText(i18n("search.sort"));
|
|
||||||
sortComboBox.getItems().setAll(
|
sortComboBox.getItems().setAll(
|
||||||
i18n("curse.sort.date_created"),
|
i18n("curse.sort.date_created"),
|
||||||
i18n("curse.sort.popularity"),
|
i18n("curse.sort.popularity"),
|
||||||
@ -252,8 +259,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
|
|
||||||
JFXButton searchButton = new JFXButton();
|
JFXButton searchButton = new JFXButton();
|
||||||
searchButton.setText(i18n("search"));
|
searchButton.setText(i18n("search"));
|
||||||
GridPane.setHalignment(searchButton, HPos.LEFT);
|
searchButton.getStyleClass().add("jfx-button-raised");
|
||||||
searchPane.addRow(rowIndex++, searchButton);
|
searchButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||||
|
HBox searchBox = new HBox(searchButton);
|
||||||
|
GridPane.setColumnSpan(searchBox, 4);
|
||||||
|
searchBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
searchPane.addRow(rowIndex++, searchBox);
|
||||||
|
|
||||||
EventHandler<ActionEvent> searchAction = e -> getSkinnable()
|
EventHandler<ActionEvent> searchAction = e -> getSkinnable()
|
||||||
.search(gameVersionField.getText(),
|
.search(gameVersionField.getText(),
|
||||||
|
@ -35,6 +35,7 @@ import javafx.scene.layout.Priority;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import org.jackhuang.hmcl.game.GameVersion;
|
import org.jackhuang.hmcl.game.GameVersion;
|
||||||
|
import org.jackhuang.hmcl.mod.ModManager;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
@ -45,6 +46,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
|||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
|
||||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
@ -70,11 +72,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||||
private final CurseAddon addon;
|
private final CurseAddon addon;
|
||||||
|
private final ModTranslations.Mod mod;
|
||||||
private final Profile.ProfileVersion version;
|
private final Profile.ProfileVersion version;
|
||||||
private final DownloadCallback callback;
|
private final DownloadCallback callback;
|
||||||
|
|
||||||
public DownloadPage(CurseAddon addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
public DownloadPage(CurseAddon addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
||||||
this.addon = addon;
|
this.addon = addon;
|
||||||
|
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|
||||||
@ -183,6 +187,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
descriptionPane.getStyleClass().add("card");
|
descriptionPane.getStyleClass().add("card");
|
||||||
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
||||||
|
|
||||||
|
ImageView imageView = new ImageView();
|
||||||
|
for (CurseAddon.Attachment attachment : getSkinnable().addon.getAttachments()) {
|
||||||
|
if (attachment.isDefault()) {
|
||||||
|
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
||||||
|
|
||||||
TwoLineListItem content = new TwoLineListItem();
|
TwoLineListItem content = new TwoLineListItem();
|
||||||
HBox.setHgrow(content, Priority.ALWAYS);
|
HBox.setHgrow(content, Priority.ALWAYS);
|
||||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug());
|
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug());
|
||||||
@ -191,20 +203,23 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
||||||
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
descriptionPane.getChildren().add(content);
|
||||||
|
|
||||||
ImageView imageView = new ImageView();
|
if (getSkinnable().mod != null) {
|
||||||
for (CurseAddon.Attachment attachment : getSkinnable().addon.getAttachments()) {
|
JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod"));
|
||||||
if (attachment.isDefault()) {
|
openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod())));
|
||||||
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
|
descriptionPane.getChildren().add(openMcmodButton);
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||||
|
JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||||
|
openMcbbsButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs())));
|
||||||
|
descriptionPane.getChildren().add(openMcbbsButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JFXButton openUrlButton = new JFXButton();
|
JFXHyperlink openUrlButton = new JFXHyperlink(i18n("mods.curseforge"));
|
||||||
openUrlButton.getStyleClass().add("toggle-icon4");
|
|
||||||
openUrlButton.setGraphic(SVG.launchOutline(Theme.blackFillBinding(), -1, -1));
|
|
||||||
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getWebsiteUrl()));
|
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getWebsiteUrl()));
|
||||||
|
descriptionPane.getChildren().add(openUrlButton);
|
||||||
descriptionPane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content, openUrlButton);
|
|
||||||
|
|
||||||
|
|
||||||
SpinnerPane spinnerPane = new SpinnerPane();
|
SpinnerPane spinnerPane = new SpinnerPane();
|
||||||
|
@ -26,6 +26,8 @@ import java.util.List;
|
|||||||
public class ModDownloadListPage extends DownloadListPage {
|
public class ModDownloadListPage extends DownloadListPage {
|
||||||
public ModDownloadListPage(int section, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
public ModDownloadListPage(int section, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||||
super(section, callback, versionSelection);
|
super(section, callback, versionSelection);
|
||||||
|
|
||||||
|
supportChinese.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class AggregatedObservableList<T> {
|
||||||
|
|
||||||
|
protected final List<ObservableList<T>> lists = new ArrayList<>();
|
||||||
|
final private List<Integer> sizes = new ArrayList<>();
|
||||||
|
final private List<InternalListModificationListener> listeners = new ArrayList<>();
|
||||||
|
final protected ObservableList<T> aggregatedList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
public AggregatedObservableList() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Aggregated Observable List. This list is unmodifiable, because sorting this list would mess up the entire bookkeeping we do here.
|
||||||
|
*
|
||||||
|
* @return an unmodifiable view of the aggregatedList
|
||||||
|
*/
|
||||||
|
public ObservableList<T> getAggregatedList() {
|
||||||
|
return FXCollections.unmodifiableObservableList(aggregatedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendList(@NotNull ObservableList<T> list) {
|
||||||
|
assert !lists.contains(list) : "List is already contained: " + list;
|
||||||
|
lists.add(list);
|
||||||
|
final InternalListModificationListener listener = new InternalListModificationListener(list);
|
||||||
|
list.addListener(listener);
|
||||||
|
//System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
|
||||||
|
sizes.add(list.size());
|
||||||
|
aggregatedList.addAll(list);
|
||||||
|
listeners.add(listener);
|
||||||
|
assert lists.size() == sizes.size() && lists.size() == listeners.size() :
|
||||||
|
"lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prependList(@NotNull ObservableList<T> list) {
|
||||||
|
assert !lists.contains(list) : "List is already contained: " + list;
|
||||||
|
lists.add(0, list);
|
||||||
|
final InternalListModificationListener listener = new InternalListModificationListener(list);
|
||||||
|
list.addListener(listener);
|
||||||
|
//System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
|
||||||
|
sizes.add(0, list.size());
|
||||||
|
aggregatedList.addAll(0, list);
|
||||||
|
listeners.add(0, listener);
|
||||||
|
assert lists.size() == sizes.size() && lists.size() == listeners.size() :
|
||||||
|
"lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeList(@NotNull ObservableList<T> list) {
|
||||||
|
assert lists.size() == sizes.size() && lists.size() == listeners.size() :
|
||||||
|
"lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
|
||||||
|
final int index = lists.indexOf(list);
|
||||||
|
if (index < 0) {
|
||||||
|
throw new IllegalArgumentException("Cannot remove a list that is not contained: " + list + " lists=" + lists);
|
||||||
|
}
|
||||||
|
final int startIndex = getStartIndex(list);
|
||||||
|
final int endIndex = getEndIndex(list, startIndex);
|
||||||
|
// we want to find the start index of this list inside the aggregated List. End index will be start + size - 1.
|
||||||
|
lists.remove(list);
|
||||||
|
sizes.remove(index);
|
||||||
|
final InternalListModificationListener listener = listeners.remove(index);
|
||||||
|
list.removeListener(listener);
|
||||||
|
aggregatedList.remove(startIndex, endIndex + 1); // end + 1 because end is exclusive
|
||||||
|
assert lists.size() == sizes.size() && lists.size() == listeners.size() :
|
||||||
|
"lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the start index of this list inside the aggregated List.
|
||||||
|
* This is a private function. we can safely asume, that the list is in the map.
|
||||||
|
*
|
||||||
|
* @param list the list in question
|
||||||
|
* @return the start index of this list in the aggregated List
|
||||||
|
*/
|
||||||
|
private int getStartIndex(@NotNull ObservableList<T> list) {
|
||||||
|
int startIndex = 0;
|
||||||
|
//System.out.println("=== searching startIndex of " + list);
|
||||||
|
assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
|
||||||
|
final int listIndex = lists.indexOf(list);
|
||||||
|
for (int i = 0; i < listIndex; i++) {
|
||||||
|
final Integer size = sizes.get(i);
|
||||||
|
startIndex += size;
|
||||||
|
//System.out.println(" startIndex = " + startIndex + " added=" + size);
|
||||||
|
}
|
||||||
|
//System.out.println("startIndex = " + startIndex);
|
||||||
|
return startIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the end index of this list inside the aggregated List.
|
||||||
|
* This is a private function. we can safely asume, that the list is in the map.
|
||||||
|
*
|
||||||
|
* @param list the list in question
|
||||||
|
* @param startIndex the start of the list (retrieve with {@link #getStartIndex(ObservableList)}
|
||||||
|
* @return the end index of this list in the aggregated List
|
||||||
|
*/
|
||||||
|
private int getEndIndex(@NotNull ObservableList<T> list, int startIndex) {
|
||||||
|
assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
|
||||||
|
final int index = lists.indexOf(list);
|
||||||
|
return startIndex + sizes.get(index) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InternalListModificationListener implements ListChangeListener<T> {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final ObservableList<T> list;
|
||||||
|
|
||||||
|
public InternalListModificationListener(@NotNull ObservableList<T> list) {
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after a change has been made to an ObservableList.
|
||||||
|
*
|
||||||
|
* @param change an object representing the change that was done
|
||||||
|
* @see Change
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onChanged(Change<? extends T> change) {
|
||||||
|
final ObservableList<? extends T> changedList = change.getList();
|
||||||
|
final int startIndex = getStartIndex(list);
|
||||||
|
final int index = lists.indexOf(list);
|
||||||
|
final int newSize = changedList.size();
|
||||||
|
//System.out.println("onChanged for list=" + list + " aggregate=" + aggregatedList);
|
||||||
|
while (change.next()) {
|
||||||
|
final int from = change.getFrom();
|
||||||
|
final int to = change.getTo();
|
||||||
|
//System.out.println(" startIndex=" + startIndex + " from=" + from + " to=" + to);
|
||||||
|
if (change.wasPermutated()) {
|
||||||
|
final ArrayList<T> copy = new ArrayList<>(aggregatedList.subList(startIndex + from, startIndex + to));
|
||||||
|
//System.out.println(" permutating sublist=" + copy);
|
||||||
|
for (int oldIndex = from; oldIndex < to; oldIndex++) {
|
||||||
|
int newIndex = change.getPermutation(oldIndex);
|
||||||
|
copy.set(newIndex - from, aggregatedList.get(startIndex + oldIndex));
|
||||||
|
}
|
||||||
|
//System.out.println(" permutating done sublist=" + copy);
|
||||||
|
aggregatedList.subList(startIndex + from, startIndex + to).clear();
|
||||||
|
aggregatedList.addAll(startIndex + from, copy);
|
||||||
|
} else if (change.wasUpdated()) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
if (change.wasRemoved()) {
|
||||||
|
List<? extends T> removed = change.getRemoved();
|
||||||
|
//System.out.println(" removed= " + removed);
|
||||||
|
// IMPORTANT! FROM == TO when removing items.
|
||||||
|
aggregatedList.remove(startIndex + from, startIndex + from + removed.size());
|
||||||
|
}
|
||||||
|
if (change.wasAdded()) {
|
||||||
|
List<? extends T> added = change.getAddedSubList();
|
||||||
|
//System.out.println(" added= " + added);
|
||||||
|
//add those elements to your data
|
||||||
|
aggregatedList.addAll(startIndex + from, added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update the size of the list in the map
|
||||||
|
//System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
|
||||||
|
sizes.set(index, newSize);
|
||||||
|
//System.out.println("listSizesMap = " + sizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String dump(Function<T, Object> function) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("[");
|
||||||
|
aggregatedList.forEach(el -> sb.append(function.apply(el)).append(","));
|
||||||
|
final int length = sb.length();
|
||||||
|
sb.replace(length - 1, length, "");
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -94,7 +94,7 @@
|
|||||||
-fx-padding: 0 0 0 10;
|
-fx-padding: 0 0 0 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-list-item > .rippler-container > .container > .two-line-list-item > HBox > .title {
|
.advanced-list-item > .rippler-container > .container > .two-line-list-item > FlowPane > .title {
|
||||||
-fx-font-size: 13;
|
-fx-font-size: 13;
|
||||||
-fx-text-alignment: justify;
|
-fx-text-alignment: justify;
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@
|
|||||||
-fx-background-color: -fx-base-rippler-color;
|
-fx-background-color: -fx-base-rippler-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > HBox > .title {
|
.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > FlowPane > .title {
|
||||||
-fx-text-fill: -fx-base-color;
|
-fx-text-fill: -fx-base-color;
|
||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@
|
|||||||
-fx-padding: 0 0 0 0;
|
-fx-padding: 0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > HBox > .title {
|
.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > FlowPane > .title {
|
||||||
-fx-font-size: 13;
|
-fx-font-size: 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@
|
|||||||
-fx-background-color: -fx-base-rippler-color;
|
-fx-background-color: -fx-base-rippler-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > HBox .title {
|
.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > FlowPane > .title {
|
||||||
-fx-text-fill: -fx-base-color;
|
-fx-text-fill: -fx-base-color;
|
||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
@ -205,9 +205,10 @@
|
|||||||
-fx-alignment: center-left;
|
-fx-alignment: center-left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.two-line-list-item > HBox > .title {
|
.two-line-list-item > FlowPane > .title {
|
||||||
-fx-text-fill: #292929;
|
-fx-text-fill: #292929;
|
||||||
-fx-font-size: 15px;
|
-fx-font-size: 15px;
|
||||||
|
-fx-padding: 0 8 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.two-line-list-item > HBox > .subtitle {
|
.two-line-list-item > HBox > .subtitle {
|
||||||
@ -216,11 +217,7 @@
|
|||||||
-fx-font-size: 12px;
|
-fx-font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.two-line-list-item > HBox > HBox {
|
.two-line-list-item > FlowPane > .tag {
|
||||||
-fx-spacing: 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.two-line-list-item > HBox > HBox > .tag {
|
|
||||||
-fx-text-fill: -fx-base-color;
|
-fx-text-fill: -fx-base-color;
|
||||||
-fx-background-color: -fx-base-rippler-color;
|
-fx-background-color: -fx-base-rippler-color;
|
||||||
-fx-padding: 2;
|
-fx-padding: 2;
|
||||||
@ -234,7 +231,7 @@
|
|||||||
-fx-text-fill: white;
|
-fx-text-fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble > HBox > .two-line-list-item > HBox > .title,
|
.bubble > HBox > .two-line-list-item > FlowPane > .title,
|
||||||
.bubble > HBox > .two-line-list-item > HBox > .subtitle {
|
.bubble > HBox > .two-line-list-item > HBox > .subtitle {
|
||||||
-fx-text-fill: white;
|
-fx-text-fill: white;
|
||||||
}
|
}
|
||||||
|
@ -491,13 +491,16 @@ mods.add.failed=Failed to install mods %s.
|
|||||||
mods.add.success=Successfully installed mods %s.
|
mods.add.success=Successfully installed mods %s.
|
||||||
mods.category=Category
|
mods.category=Category
|
||||||
mods.choose_mod=Choose your mods
|
mods.choose_mod=Choose your mods
|
||||||
|
mods.curseforge=CurseForge
|
||||||
mods.disable=Disable
|
mods.disable=Disable
|
||||||
mods.download=Mod Downloads
|
mods.download=Mod Downloads
|
||||||
mods.download.title=Mod Downloads - %1s
|
mods.download.title=Mod Downloads - %1s
|
||||||
mods.enable=Enable
|
mods.enable=Enable
|
||||||
mods.manage=Mods
|
mods.manage=Mods
|
||||||
mods.mcmod.page=MCWiki
|
mods.mcbbs=MCBBS
|
||||||
mods.mcmod.search=Search in MCWiki
|
mods.mcmod=MCMOD
|
||||||
|
mods.mcmod.page=MCMOD
|
||||||
|
mods.mcmod.search=Search in MCMOD
|
||||||
mods.name=Name
|
mods.name=Name
|
||||||
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
||||||
mods.url=Official Page
|
mods.url=Official Page
|
||||||
|
@ -498,11 +498,14 @@ mods.add.failed=新增模組 %s 失敗。
|
|||||||
mods.add.success=成功新增模組 %s。
|
mods.add.success=成功新增模組 %s。
|
||||||
mods.category=類別
|
mods.category=類別
|
||||||
mods.choose_mod=選擇模組
|
mods.choose_mod=選擇模組
|
||||||
|
mods.curseforge=CurseForge
|
||||||
mods.disable=停用
|
mods.disable=停用
|
||||||
mods.download=模組下載
|
mods.download=模組下載
|
||||||
mods.download.title=模組下載 - %1s
|
mods.download.title=模組下載 - %1s
|
||||||
mods.enable=啟用
|
mods.enable=啟用
|
||||||
mods.manage=模組管理
|
mods.manage=模組管理
|
||||||
|
mods.mcbbs=MCBBS
|
||||||
|
mods.mcmod=MC 百科
|
||||||
mods.mcmod.page=MC 百科頁面
|
mods.mcmod.page=MC 百科頁面
|
||||||
mods.mcmod.search=MC 百科蒐索
|
mods.mcmod.search=MC 百科蒐索
|
||||||
mods.name=名稱
|
mods.name=名稱
|
||||||
|
@ -494,11 +494,14 @@ mods.add.failed=添加模组 %s 失败。
|
|||||||
mods.add.success=成功添加模组 %s。
|
mods.add.success=成功添加模组 %s。
|
||||||
mods.category=类别
|
mods.category=类别
|
||||||
mods.choose_mod=选择模组
|
mods.choose_mod=选择模组
|
||||||
|
mods.curseforge=CurseForge
|
||||||
mods.disable=禁用
|
mods.disable=禁用
|
||||||
mods.download=模组下载
|
mods.download=模组下载
|
||||||
mods.download.title=模组下载 - %1s
|
mods.download.title=模组下载 - %1s
|
||||||
mods.enable=启用
|
mods.enable=启用
|
||||||
mods.manage=模组管理
|
mods.manage=模组管理
|
||||||
|
mods.mcbbs=MCBBS
|
||||||
|
mods.mcmod=MC 百科
|
||||||
mods.mcmod.page=MC 百科页面
|
mods.mcmod.page=MC 百科页面
|
||||||
mods.mcmod.search=MC 百科搜索
|
mods.mcmod.search=MC 百科搜索
|
||||||
mods.name=名称
|
mods.name=名称
|
||||||
|
@ -192,5 +192,13 @@ public final class ModManager {
|
|||||||
return getModsDirectory().resolve(fileName);
|
return getModsDirectory().resolve(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getMcmodUrl(String mcmodId) {
|
||||||
|
return String.format("https://www.mcmod.cn/class/%s.html", mcmodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMcbbsUrl(String mcbbsId) {
|
||||||
|
return String.format("https://www.mcbbs.net/thread-%s-1-1.html", mcbbsId);
|
||||||
|
}
|
||||||
|
|
||||||
public static final String DISABLED_EXTENSION = ".disabled";
|
public static final String DISABLED_EXTENSION = ".disabled";
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user