mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-09 03:46:18 -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);
|
||||
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"));
|
||||
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"));
|
||||
box.getChildren().setAll(profileLink, birthLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
@ -415,7 +415,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
|
||||
if (factory instanceof YggdrasilAccountFactory) {
|
||||
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"));
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
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.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.AggregatedObservableList;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
public class TwoLineListItem extends VBox {
|
||||
@ -35,7 +39,8 @@ public class TwoLineListItem extends VBox {
|
||||
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
||||
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) {
|
||||
this();
|
||||
@ -47,23 +52,24 @@ public class TwoLineListItem extends VBox {
|
||||
public TwoLineListItem() {
|
||||
setMouseTransparent(true);
|
||||
|
||||
HBox firstLine = new HBox();
|
||||
FlowPane firstLine = new FlowPane();
|
||||
firstLine.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
Label lblTitle = new Label();
|
||||
lblTitle.getStyleClass().add("title");
|
||||
lblTitle.textProperty().bind(title);
|
||||
|
||||
HBox tagContainer = new HBox();
|
||||
|
||||
tagLabels = MappedObservableList.create(tags, tag -> {
|
||||
Label tagLabel = new Label();
|
||||
tagLabel.getStyleClass().add("tag");
|
||||
tagLabel.setText(tag);
|
||||
FlowPane.setMargin(tagLabel, new Insets(0, 8, 0, 0));
|
||||
return tagLabel;
|
||||
});
|
||||
Bindings.bindContent(tagContainer.getChildren(), tagLabels);
|
||||
|
||||
firstLine.getChildren().addAll(lblTitle, tagContainer);
|
||||
firstLineChildren = new AggregatedObservableList<>();
|
||||
firstLineChildren.appendList(FXCollections.singletonObservableList(lblTitle));
|
||||
firstLineChildren.appendList(tagLabels);
|
||||
Bindings.bindContent(firstLine.getChildren(), firstLineChildren.getAggregatedList());
|
||||
|
||||
Label lblSubtitle = new Label();
|
||||
lblSubtitle.getStyleClass().add("subtitle");
|
||||
|
@ -17,17 +17,22 @@
|
||||
*/
|
||||
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.ObjectBinding;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
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.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
@ -47,7 +52,9 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
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 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 DownloadPage.DownloadCallback callback;
|
||||
private boolean searchInitialized = false;
|
||||
protected final BooleanProperty supportChinese = new SimpleBooleanProperty();
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
||||
: null;
|
||||
@ -194,11 +203,10 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
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();
|
||||
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);
|
||||
|
||||
ObjectBinding<Boolean> hasVersion = BindingMapping.of(getSkinnable().version)
|
||||
@ -239,7 +247,6 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
sortStackPane.getChildren().setAll(sortComboBox);
|
||||
sortComboBox.prefWidthProperty().bind(sortStackPane.widthProperty());
|
||||
sortComboBox.getStyleClass().add("fit-width");
|
||||
sortComboBox.setPromptText(i18n("search.sort"));
|
||||
sortComboBox.getItems().setAll(
|
||||
i18n("curse.sort.date_created"),
|
||||
i18n("curse.sort.popularity"),
|
||||
@ -252,8 +259,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
JFXButton searchButton = new JFXButton();
|
||||
searchButton.setText(i18n("search"));
|
||||
GridPane.setHalignment(searchButton, HPos.LEFT);
|
||||
searchPane.addRow(rowIndex++, searchButton);
|
||||
searchButton.getStyleClass().add("jfx-button-raised");
|
||||
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()
|
||||
.search(gameVersionField.getText(),
|
||||
|
@ -35,6 +35,7 @@ import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
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.CurseModManager;
|
||||
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.SVG;
|
||||
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.TwoLineListItem;
|
||||
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 failed = new SimpleBooleanProperty(false);
|
||||
private final CurseAddon addon;
|
||||
private final ModTranslations.Mod mod;
|
||||
private final Profile.ProfileVersion version;
|
||||
private final DownloadCallback callback;
|
||||
|
||||
public DownloadPage(CurseAddon addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
||||
this.addon = addon;
|
||||
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||
this.version = version;
|
||||
this.callback = callback;
|
||||
|
||||
@ -183,6 +187,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
descriptionPane.getStyleClass().add("card");
|
||||
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();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
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()
|
||||
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
||||
.collect(Collectors.toList()));
|
||||
descriptionPane.getChildren().add(content);
|
||||
|
||||
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));
|
||||
if (getSkinnable().mod != null) {
|
||||
JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod"));
|
||||
openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod())));
|
||||
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();
|
||||
openUrlButton.getStyleClass().add("toggle-icon4");
|
||||
openUrlButton.setGraphic(SVG.launchOutline(Theme.blackFillBinding(), -1, -1));
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(i18n("mods.curseforge"));
|
||||
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getWebsiteUrl()));
|
||||
|
||||
descriptionPane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content, openUrlButton);
|
||||
descriptionPane.getChildren().add(openUrlButton);
|
||||
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
|
@ -26,6 +26,8 @@ import java.util.List;
|
||||
public class ModDownloadListPage extends DownloadListPage {
|
||||
public ModDownloadListPage(int section, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
super(section, callback, versionSelection);
|
||||
|
||||
supportChinese.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -190,7 +190,7 @@ public final class ModTranslations {
|
||||
public String getDisplayName() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (StringUtils.isNotBlank(abbr)) {
|
||||
builder.append("[").append(abbr).append("]");
|
||||
builder.append("[").append(abbr).append("] ");
|
||||
}
|
||||
builder.append(name);
|
||||
if (StringUtils.isNotBlank(subname)) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
.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-text-alignment: justify;
|
||||
}
|
||||
@ -108,7 +108,7 @@
|
||||
-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-font-weight: bold;
|
||||
}
|
||||
@ -121,7 +121,7 @@
|
||||
-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;
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
-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-font-weight: bold;
|
||||
}
|
||||
@ -205,9 +205,10 @@
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.two-line-list-item > HBox > .title {
|
||||
.two-line-list-item > FlowPane > .title {
|
||||
-fx-text-fill: #292929;
|
||||
-fx-font-size: 15px;
|
||||
-fx-padding: 0 8 0 0;
|
||||
}
|
||||
|
||||
.two-line-list-item > HBox > .subtitle {
|
||||
@ -216,11 +217,7 @@
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.two-line-list-item > HBox > HBox {
|
||||
-fx-spacing: 8;
|
||||
}
|
||||
|
||||
.two-line-list-item > HBox > HBox > .tag {
|
||||
.two-line-list-item > FlowPane > .tag {
|
||||
-fx-text-fill: -fx-base-color;
|
||||
-fx-background-color: -fx-base-rippler-color;
|
||||
-fx-padding: 2;
|
||||
@ -234,7 +231,7 @@
|
||||
-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 {
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
@ -491,13 +491,16 @@ mods.add.failed=Failed to install mods %s.
|
||||
mods.add.success=Successfully installed mods %s.
|
||||
mods.category=Category
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.curseforge=CurseForge
|
||||
mods.disable=Disable
|
||||
mods.download=Mod Downloads
|
||||
mods.download.title=Mod Downloads - %1s
|
||||
mods.enable=Enable
|
||||
mods.manage=Mods
|
||||
mods.mcmod.page=MCWiki
|
||||
mods.mcmod.search=Search in MCWiki
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MCMOD
|
||||
mods.mcmod.page=MCMOD
|
||||
mods.mcmod.search=Search in MCMOD
|
||||
mods.name=Name
|
||||
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
||||
mods.url=Official Page
|
||||
|
@ -498,11 +498,14 @@ mods.add.failed=新增模組 %s 失敗。
|
||||
mods.add.success=成功新增模組 %s。
|
||||
mods.category=類別
|
||||
mods.choose_mod=選擇模組
|
||||
mods.curseforge=CurseForge
|
||||
mods.disable=停用
|
||||
mods.download=模組下載
|
||||
mods.download.title=模組下載 - %1s
|
||||
mods.enable=啟用
|
||||
mods.manage=模組管理
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MC 百科
|
||||
mods.mcmod.page=MC 百科頁面
|
||||
mods.mcmod.search=MC 百科蒐索
|
||||
mods.name=名稱
|
||||
|
@ -494,11 +494,14 @@ mods.add.failed=添加模组 %s 失败。
|
||||
mods.add.success=成功添加模组 %s。
|
||||
mods.category=类别
|
||||
mods.choose_mod=选择模组
|
||||
mods.curseforge=CurseForge
|
||||
mods.disable=禁用
|
||||
mods.download=模组下载
|
||||
mods.download.title=模组下载 - %1s
|
||||
mods.enable=启用
|
||||
mods.manage=模组管理
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MC 百科
|
||||
mods.mcmod.page=MC 百科页面
|
||||
mods.mcmod.search=MC 百科搜索
|
||||
mods.name=名称
|
||||
|
@ -192,5 +192,13 @@ public final class ModManager {
|
||||
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";
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user