Compare commits

...
28 Commits
Author SHA1 Message Date
yushijinhun 93f943d40c Fix crash in version settings
introduced in d75c3bbf96
2019-02-04 17:06:20 +08:00
Feng Chang f20c11a4e2 Ensure sequential execution of configuration writer threads (#516)
* Ensure sequential execution of configuration writer threads

Signed-off-by: Chang Feng <chang_196700@hotmail.com>

* Change method to add synchronized to action

Signed-off-by: Chang Feng <chang_196700@hotmail.com>
2019-02-04 02:18:24 +08:00
yushijinhun 0c25f31493 fix npe 2019-01-26 22:50:51 +08:00
yushijinhun 8d774e267f Show register link in login dialog
https://github.com/yushijinhun/authlib-injector/issues/25
2019-01-26 15:19:17 +08:00
yushijinhun 6c1de5d485 Improve tooltip UX 2019-01-26 13:33:59 +08:00
yushijinhun 77c86ed3bc Add tooltip for authlib-injector accounts 2019-01-26 13:17:45 +08:00
yushijinhun d75c3bbf96 Refactor TwoLineListItem 2019-01-26 13:06:56 +08:00
huanghongxun 8bb6cb714c use result of prev task instead of AutoTypingMap 2019-01-22 17:15:03 +08:00
huanghongxun 28d002f6d3 Do not schedule task if preExecute and postExecute are not overrided. 2019-01-22 16:46:11 +08:00
huanghongxun 78fcc69d29 Fixed not displaying "Home" icon. Mentioned in #513 2019-01-22 16:28:41 +08:00
huanghongxun cdfae54ab9 Fixed costly time consumption reading level.dat. Mentioned in #512 2019-01-22 16:11:31 +08:00
huanghongxun 0813211c20 Use JFXListView in place of custom components to accelerate rendering 2019-01-22 16:01:00 +08:00
huanghongxun 056a4eead2 Add comments to LiteLoaderInstallTask and OptiFineInstallTask 2019-01-22 15:54:59 +08:00
huanghongxun 2635d31425 Fix not correctly adding "--tweakClass" for MultiMC modpack 2019-01-22 15:52:57 +08:00
huanghongxun 76d35af5b9 MultiMC modpack patch json file may have "+libraries" or "libraries" 2019-01-22 15:51:52 +08:00
huanghongxun bcff1695d3 Refactor MaintainTask 2019-01-22 15:50:49 +08:00
huanghongxun 3d04447c2f Load world asynchronously 2019-01-22 14:59:01 +08:00
huanghongxun 2736b88a10 Add some promise-like method to TaskResult 2019-01-22 14:58:00 +08:00
huanghongxun 0804a2fbf8 Merge TaskCallable and TaskCallable2 2019-01-22 14:56:48 +08:00
huanghongxun 7224ed6a7d Fix missorting optifine versions 2019-01-16 12:34:13 +08:00
huanghongxun c41737364c Prevent from potential conflict that a MultiMC modpack has ".minecraft" and "minecraft" at the same time 2019-01-13 17:10:23 +08:00
huanghongxun 320e579844 Remove game deletion and renaming action in game settings page 2019-01-13 16:55:07 +08:00
huanghongxun fc3f2e045b Retry fetching remote text file when downloading had been terminated 2019-01-13 16:50:54 +08:00
huanghongxun 481b47bf10 Fix #508. MultiMC stores files in ".minecraft" instead 2019-01-13 16:50:21 +08:00
huanghongxun 0fb87f0b1d Accept modpack drag 2019-01-13 11:55:55 +08:00
huanghongxun 1252d7700f Update alert prompt of Forge 2760 2019-01-11 21:23:26 +08:00
huanghongxun 30c2d88f62 Use spinnerPane in ModListPage 2019-01-11 21:18:26 +08:00
huanghongxun 718a98d7ec Fix alerting Forge conflicts with LiteLoader but actually not 2019-01-11 21:17:02 +08:00
52 changed files with 587 additions and 473 deletions

No files matched your search

@@ -375,7 +375,7 @@ public final class LauncherHelper {
if (!flag) {
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
.anyMatch(it ->
0 <= VersionNumber.VERSION_COMPARATOR.compare("1.12.2-14.23.5.2760", it.getVersion()) &&
VersionNumber.VERSION_COMPARATOR.compare("1.12.2-14.23.5.2760", it.getVersion()) <= 0 &&
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 0);
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
@@ -90,8 +90,13 @@ public final class Controllers {
// FXThread
public static GameList getGameListPage() {
if (gameListPage == null)
if (gameListPage == null) {
gameListPage = new GameList();
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
File modpack = modpacks.get(0);
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
});
}
return gameListPage;
}
@@ -234,8 +234,20 @@ public final class FXUtils {
}
}
public static void installTooltip(Node node, String tooltip) {
installTooltip(node, 0, 5000, 0, new Tooltip(tooltip));
public static void installFastTooltip(Node node, Tooltip tooltip) {
installTooltip(node, 50, 5000, 0, tooltip);
}
public static void installFastTooltip(Node node, String tooltip) {
installFastTooltip(node, new Tooltip(tooltip));
}
public static void installSlowTooltip(Node node, Tooltip tooltip) {
installTooltip(node, 500, 5000, 0, tooltip);
}
public static void installSlowTooltip(Node node, String tooltip) {
installSlowTooltip(node, new Tooltip(tooltip));
}
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
@@ -119,16 +119,16 @@ public final class LeftPaneController extends AdvancedListBox {
if (repository.getVersionCount() == 0) {
File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) {
Task.ofResult("encoding", () -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.then(Task.ofResult("modpack", var -> ModpackHelper.readModpackManifest(modpackFile.toPath(), var.get("encoding"))))
.then(Task.of(var -> {
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.thenResult(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.thenResult(modpack -> {
AtomicReference<Region> region = new AtomicReference<>();
Modpack modpack = var.get("modpack");
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.installing")));
executor.start();
})).start();
return null;
}).start();
}
}
}
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXScrollPane;
import com.jfoenix.controls.JFXSpinner;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -28,17 +28,14 @@ import javafx.scene.control.SkinBase;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
public class ListPageSkin extends SkinBase<ListPage> {
public class ListPageSkin extends SkinBase<ListPage<?>> {
public ListPageSkin(ListPage<?> skinnable) {
super(skinnable);
StackPane rootPane = new StackPane();
JFXSpinner spinner = new JFXSpinner();
spinner.setRadius(16);
spinner.getStyleClass().setAll("materialDesign-purple", "first-spinner");
SpinnerPane spinnerPane = new SpinnerPane();
StackPane contentPane = new StackPane();
{
@@ -56,7 +53,7 @@ public class ListPageSkin extends SkinBase<ListPage> {
scrollPane.setContent(list);
JFXScrollPane.smoothScrolling(scrollPane);
}
VBox vBox = new VBox();
{
vBox.setAlignment(Pos.BOTTOM_RIGHT);
@@ -92,13 +89,9 @@ public class ListPageSkin extends SkinBase<ListPage> {
contentPane.getChildren().setAll(scrollPane, vBox);
}
rootPane.getChildren().setAll(contentPane);
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
spinnerPane.setContent(contentPane);
skinnable.loadingProperty().addListener((a, b, newValue) -> {
if (newValue) rootPane.getChildren().setAll(spinner);
else rootPane.getChildren().setAll(contentPane);
});
getChildren().setAll(rootPane);
getChildren().setAll(spinnerPane);
}
}
@@ -87,11 +87,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
lblIcon.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20));
TwoLineListItem prompt = new TwoLineListItem();
prompt.setTitleFill(Color.WHITE);
prompt.setSubtitleFill(Color.WHITE);
prompt.setSubtitle(i18n("update.bubble.subtitle"));
prompt.setPickOnBounds(false);
prompt.setStyle("-jfx-title-font-weight: BOLD;");
prompt.titleProperty().bind(latestVersionProperty());
hBox.getChildren().setAll(lblIcon, prompt);
@@ -129,7 +129,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
// ==== Update ====
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
updateListener = any -> {
btnUpdate.setVisible(UpdateChecker.isOutdated());
@@ -359,7 +359,7 @@ public abstract class SettingsView extends StackPane {
HBox hBox = new HBox();
hBox.setSpacing(3);
cboFont = new FontComboBox(12, false);
cboFont = new FontComboBox(12);
txtFontSize = new JFXTextField();
FXUtils.setLimitWidth(txtFontSize, 50);
hBox.getChildren().setAll(cboFont, txtFontSize);
@@ -21,18 +21,26 @@ import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
public class AccountListItemSkin extends SkinBase<AccountListItem> {
public AccountListItemSkin(AccountListItem skinnable) {
@@ -58,8 +66,22 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
TwoLineListItem item = new TwoLineListItem();
Label title = new Label();
title.getStyleClass().add("title");
title.textProperty().bind(skinnable.titleProperty());
Label subtitle = new Label();
subtitle.getStyleClass().add("subtitle");
subtitle.textProperty().bind(skinnable.subtitleProperty());
if (skinnable.getAccount() instanceof AuthlibInjectorAccount) {
Tooltip tooltip = new Tooltip();
AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer();
tooltip.textProperty().bind(Bindings.createStringBinding(server::toString, server));
FXUtils.installSlowTooltip(subtitle, tooltip);
}
VBox item = new VBox(title, subtitle);
item.getStyleClass().add("two-line-list-item");
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
root.setCenter(center);
@@ -69,7 +91,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
btnRefresh.getStyleClass().add("toggle-icon4");
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1));
JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRefresh, i18n("button.refresh")));
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
right.getChildren().add(btnRefresh);
JFXButton btnRemove = new JFXButton();
@@ -77,14 +99,12 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1));
JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRemove, i18n("button.delete")));
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
right.getChildren().add(btnRemove);
root.setRight(right);
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
JFXDepthManager.setDepth(root, 1);
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
getChildren().setAll(root);
}
@@ -59,25 +59,19 @@ public class AccountLoginPane extends StackPane {
String password = txtPassword.getText();
progressBar.setVisible(true);
lblCreationWarning.setText("");
Task.ofResult("login", () -> {
try {
return oldAccount.logInWithPassword(password);
} catch (Exception e) {
return e;
}
}).subscribe(Schedulers.javafx(), variable -> {
Object account = variable.get("login");
if (account instanceof AuthInfo) {
success.accept(((AuthInfo) account));
fireEvent(new DialogCloseEvent());
} else if (account instanceof NoSelectedCharacterException) {
fireEvent(new DialogCloseEvent());
} else if (account instanceof Exception) {
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
}
progressBar.setVisible(false);
});
Task.ofResult(() -> oldAccount.logInWithPassword(password))
.finalizedResult(Schedulers.javafx(), authInfo -> {
success.accept(authInfo);
fireEvent(new DialogCloseEvent());
progressBar.setVisible(false);
}, e -> {
if (e instanceof NoSelectedCharacterException) {
fireEvent(new DialogCloseEvent());
} else {
lblCreationWarning.setText(AddAccountPane.accountException(e));
}
progressBar.setVisible(false);
}).start();
}
@FXML
@@ -19,8 +19,12 @@ package org.jackhuang.hmcl.ui.account;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
@@ -44,11 +48,16 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.*;
@@ -63,12 +72,13 @@ public class AddAccountPane extends StackPane {
@FXML private JFXComboBox<AccountFactory<?>> cboType;
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
@FXML private Label lblInjectorServer;
@FXML private Hyperlink linkManageInjectorServers;
@FXML private JFXDialogLayout layout;
@FXML private JFXButton btnAccept;
@FXML private JFXButton btnAddServer;
@FXML private JFXButton btnManageServer;
@FXML private SpinnerPane acceptPane;
@FXML private HBox linksContainer;
private ListProperty<Hyperlink> links = new SimpleListProperty<>();;
public AddAccountPane() {
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
@@ -117,6 +127,34 @@ public class AddAccountPane extends StackPane {
txtUsername.textProperty(),
txtPassword.textProperty(), txtPassword.visibleProperty(),
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
// authlib-injector links
links.bind(MultiStepBinding.of(cboServers.getSelectionModel().selectedItemProperty())
.map(AddAccountPane::createHyperlinks)
.map(FXCollections::observableList));
Bindings.bindContent(linksContainer.getChildren(), links);
linksContainer.visibleProperty().bind(cboServers.visibleProperty());
}
private static final String[] ALLOWED_LINKS = { "register" };
public static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) {
if (server == null) {
return emptyList();
}
Map<String, String> links = server.getLinks();
List<Hyperlink> result = new ArrayList<>();
for (String key : ALLOWED_LINKS) {
String value = links.get(key);
if (value != null) {
Hyperlink link = new Hyperlink(i18n("account.injector.link." + key));
FXUtils.installSlowTooltip(link, value);
link.setOnAction(e -> FXUtils.openLink(value));
result.add(link);
}
}
return unmodifiableList(result);
}
/**
@@ -160,10 +198,8 @@ public class AddAccountPane extends StackPane {
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
Object additionalData = getAuthAdditionalData();
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, additionalData))
.finalized(Schedulers.javafx(), variables -> {
Account account = variables.get("create_account");
Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData))
.finalizedResult(Schedulers.javafx(), account -> {
int oldIndex = Accounts.getAccounts().indexOf(account);
if (oldIndex == -1) {
Accounts.getAccounts().add(account);
@@ -61,7 +61,7 @@ public class FileItem extends BorderPane {
right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
right.getStyleClass().add("toggle-icon4");
right.setOnMouseClicked(e -> onExplore());
FXUtils.installTooltip(right, i18n("button.edit"));
FXUtils.installFastTooltip(right, i18n("button.edit"));
setRight(right);
Tooltip tip = new Tooltip();
@@ -24,27 +24,27 @@ import static javafx.collections.FXCollections.singletonObservableList;
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXListCell;
import javafx.beans.NamedArg;
import javafx.beans.binding.Bindings;
import javafx.scene.control.ListCell;
import javafx.scene.text.Font;
public class FontComboBox extends JFXComboBox<String> {
private boolean loaded = false;
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize,
@NamedArg(value = "enableStyle", defaultValue = "false") boolean enableStyle) {
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize) {
styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\""));
setCellFactory(listView -> new ListCell<String>() {
setCellFactory(listView -> new JFXListCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
if (!empty) {
setText(item);
setFont(new Font(item, fontSize));
setGraphic(null);
setStyle("-fx-font-family: \"" + item + "\"");
}
}
});
@@ -30,7 +30,7 @@ public class IconedMenuItem extends IconedItem {
}
public IconedMenuItem addTooltip(String tooltip) {
FXUtils.installTooltip(this, tooltip);
FXUtils.installFastTooltip(this, tooltip);
return this;
}
}
@@ -63,7 +63,7 @@ public final class ImagePickerItem extends BorderPane {
deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked);
deleteButton.getStyleClass().add("toggle-icon4");
FXUtils.installTooltip(selectButton, i18n("button.edit"));
FXUtils.installFastTooltip(selectButton, i18n("button.edit"));
HBox hBox = new HBox();
hBox.getChildren().setAll(imageView, selectButton, deleteButton);
@@ -19,7 +19,9 @@ package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXSpinner;
import javafx.beans.DefaultProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
@@ -32,19 +34,29 @@ public class SpinnerPane extends StackPane {
private final JFXSpinner spinner = new JFXSpinner();
private final StackPane contentPane = new StackPane();
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading") {
protected void invalidated() {
if (get())
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
else
transitionHandler.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
}
};
public SpinnerPane() {
getStyleClass().add("spinner-pane");
getChildren().setAll(contentPane);
content.addListener((a, b, newValue) -> contentPane.getChildren().setAll(newValue));
}
public void showSpinner() {
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
setLoading(true);
}
public void hideSpinner() {
transitionHandler.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
setLoading(false);
}
public Node getContent() {
@@ -58,4 +70,16 @@ public class SpinnerPane extends StackPane {
public void setContent(Node content) {
this.content.set(content);
}
public boolean isLoading() {
return loading.get();
}
public BooleanProperty loadingProperty() {
return loading;
}
public void setLoading(boolean loading) {
this.loading.set(loading);
}
}
@@ -17,14 +17,17 @@
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXProgressBar;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
@@ -42,15 +45,16 @@ import java.util.Map;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class TaskListPane extends StackPane {
private final AdvancedListBox listBox = new AdvancedListBox();
private final JFXListView<Task> listBox = new JFXListView<>();
private final Map<Task, ProgressListNode> nodes = new HashMap<>();
private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper();
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
public TaskListPane() {
listBox.setSpacing(0);
getChildren().setAll(listBox);
listBox.setPadding(Insets.EMPTY);
listBox.setCellFactory(listView -> new ProgressListNode());
}
public ReadOnlyIntegerProperty finishedTasksProperty() {
@@ -66,7 +70,7 @@ public final class TaskListPane extends StackPane {
@Override
public void onStart() {
Platform.runLater(() -> {
listBox.clear();
listBox.getItems().clear();
finishedTasks.set(0);
totTasks.set(0);
});
@@ -108,66 +112,66 @@ public final class TaskListPane extends StackPane {
task.setName(i18n("modpack.scan"));
}
ProgressListNode node = new ProgressListNode(task);
nodes.put(task, node);
Platform.runLater(() -> listBox.add(node));
Platform.runLater(() -> listBox.getItems().add(task));
}
@Override
public void onFinished(Task task) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
node.unbind();
Platform.runLater(() -> {
listBox.remove(node);
finishedTasks.set(finishedTasks.getValue() + 1);
});
}
@Override
public void onFailed(Task task, Throwable throwable) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
Platform.runLater(() -> {
node.setThrowable(throwable);
finishedTasks.set(finishedTasks.getValue() + 1);
if (listBox.getItems().remove(task))
finishedTasks.set(finishedTasks.getValue() + 1);
});
}
});
}
private static class ProgressListNode extends VBox {
private static class ProgressListNode extends ListCell<Task> {
private final BorderPane borderPane = new BorderPane();
private final JFXProgressBar bar = new JFXProgressBar();
private final Label title = new Label();
private final Label state = new Label();
public ProgressListNode(Task task) {
bar.progressProperty().bind(task.progressProperty());
title.setText(task.getName());
state.textProperty().bind(task.messageProperty());
BorderPane borderPane = new BorderPane();
{
borderPane.setLeft(title);
borderPane.setRight(state);
getChildren().addAll(borderPane, bar);
borderPane.setBottom(bar);
borderPane.setMinWidth(0);
borderPane.setPrefWidth(1);
setPadding(Insets.EMPTY);
bar.minWidthProperty().bind(widthProperty());
bar.prefWidthProperty().bind(widthProperty());
bar.maxWidthProperty().bind(widthProperty());
}
public void unbind() {
bar.progressProperty().unbind();
state.textProperty().unbind();
}
@Override
protected void updateItem(Task item, boolean empty) {
boolean wasEmpty = isEmpty();
Task oldTask = getItem();
public void setThrowable(Throwable throwable) {
unbind();
state.setText(throwable.getLocalizedMessage());
bar.setProgress(0);
if (!wasEmpty && oldTask != null) {
bar.progressProperty().unbind();
state.textProperty().unbind();
}
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
setGraphic(borderPane);
bar.visibleProperty().bind(Bindings.createBooleanBinding(() -> item.progressProperty().get() != -1, item.progressProperty()));
bar.progressProperty().bind(item.progressProperty());
state.textProperty().bind(Bindings.createObjectBinding(() -> {
if (item.getState() == Task.TaskState.FAILED) {
return item.getLastException().getLocalizedMessage();
} else {
return item.messageProperty().get();
}
}, item.messageProperty(), item.stateProperty()));
title.setText(item.getName());
}
}
}
}
@@ -19,32 +19,15 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import java.util.List;
public class TwoLineListItem extends StackPane {
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 subtitle = new SimpleStringProperty(this, "subtitle");
private final StyleableObjectProperty<Font> titleFont = new SimpleStyleableObjectProperty<>(StyleableProperties.TITLE_FONT, this, "title-font", Font.font(15));
private final StyleableObjectProperty<Font> subtitleFont = new SimpleStyleableObjectProperty<>(StyleableProperties.SUBTITLE_FONT, this, "subtitle-font", Font.getDefault());
private final StyleableObjectProperty<Paint> titleFill = new SimpleStyleableObjectProperty<>(StyleableProperties.TITLE_FILL, this, "title-fill", Color.BLACK);
private final StyleableObjectProperty<Paint> subtitleFill = new SimpleStyleableObjectProperty<>(StyleableProperties.SUBTITLE_FILL, this, "subtitle-fill", Color.GRAY);
public TwoLineListItem(String titleString, String subtitleString) {
this();
@@ -55,19 +38,14 @@ public class TwoLineListItem extends StackPane {
public TwoLineListItem() {
setMouseTransparent(true);
Label lblTitle = new Label();
lblTitle.textFillProperty().bind(titleFill);
lblTitle.fontProperty().bind(titleFont);
lblTitle.getStyleClass().add("title");
lblTitle.textProperty().bind(title);
Label lblSubtitle = new Label();
lblSubtitle.textFillProperty().bind(subtitleFill);
lblSubtitle.fontProperty().bind(subtitleFont);
lblSubtitle.getStyleClass().add("subtitle");
lblSubtitle.textProperty().bind(subtitle);
VBox vbox = new VBox();
vbox.getChildren().setAll(lblTitle, lblSubtitle);
getChildren().setAll(vbox);
getChildren().setAll(lblTitle, lblSubtitle);
getStyleClass().add(DEFAULT_STYLE_CLASS);
}
@@ -95,74 +73,8 @@ public class TwoLineListItem extends StackPane {
this.subtitle.set(subtitle);
}
public Font getTitleFont() {
return titleFont.get();
}
public StyleableObjectProperty<Font> titleFontProperty() {
return titleFont;
}
public void setTitleFont(Font titleFont) {
this.titleFont.set(titleFont);
}
public Font getSubtitleFont() {
return subtitleFont.get();
}
public StyleableObjectProperty<Font> subtitleFontProperty() {
return subtitleFont;
}
public void setSubtitleFont(Font subtitleFont) {
this.subtitleFont.set(subtitleFont);
}
public Paint getTitleFill() {
return titleFill.get();
}
public StyleableObjectProperty<Paint> titleFillProperty() {
return titleFill;
}
public void setTitleFill(Paint titleFill) {
this.titleFill.set(titleFill);
}
public Paint getSubtitleFill() {
return subtitleFill.get();
}
public StyleableObjectProperty<Paint> subtitleFillProperty() {
return subtitleFill;
}
public void setSubtitleFill(Paint subtitleFill) {
this.subtitleFill.set(subtitleFill);
}
@Override
public String toString() {
return getTitle();
}
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.FACTORY.getCssMetaData();
}
private static class StyleableProperties {
private static final StyleablePropertyFactory<TwoLineListItem> FACTORY = new StyleablePropertyFactory<>(StackPane.getClassCssMetaData());
private static final CssMetaData<TwoLineListItem, Font> TITLE_FONT = FACTORY.createFontCssMetaData("-jfx-title-font", s -> s.titleFont, Font.font(15));
private static final CssMetaData<TwoLineListItem, Font> SUBTITLE_FONT = FACTORY.createFontCssMetaData("-jfx-subtitle-font", s -> s.subtitleFont);
private static final CssMetaData<TwoLineListItem, Paint> TITLE_FILL = FACTORY.createPaintCssMetaData("-jfx-title-fill", s -> s.titleFill);
private static final CssMetaData<TwoLineListItem, Paint> SUBTITLE_FILL = FACTORY.createPaintCssMetaData("-jfx-subtitle-fill", s -> s.subtitleFill, Color.GREY);
}
}
@@ -207,7 +207,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
else navLeft.getChildren().setAll(backNavButton);
});
skinnable.showCloseAsHomeProperty().addListener((a, b, newValue) -> {
FXUtils.onChangeAndOperate(skinnable.showCloseAsHomeProperty(), (newValue) -> {
if (newValue)
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
else
@@ -109,10 +109,9 @@ public final class ModpackPage extends StackPane implements WizardPage {
}
spinnerPane.showSpinner();
Task.ofResult("encoding", () -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
.then(Task.ofResult("manifest", var ->
manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), var.get("encoding"))))
.finalized(Schedulers.javafx(), var -> {
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
.thenResult(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
.finalizedResult(Schedulers.javafx(), manifest -> {
spinnerPane.hideSpinner();
controller.getSettings().put(MODPACK_MANIFEST, manifest);
lblName.setText(manifest.getName());
@@ -44,7 +44,7 @@ public class DatapackListItem extends BorderPane {
setCenter(modItem);
JFXButton btnRemove = new JFXButton();
FXUtils.installTooltip(btnRemove, i18n("datapack.remove"));
FXUtils.installFastTooltip(btnRemove, i18n("datapack.remove"));
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
@@ -73,7 +73,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
btnUpgrade.setOnMouseClicked(e -> skinnable.update());
btnUpgrade.getStyleClass().add("toggle-icon4");
btnUpgrade.setGraphic(SVG.update(Theme.blackFillBinding(), -1, -1));
JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnUpgrade, i18n("version.update")));
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")));
right.getChildren().add(btnUpgrade);
}
@@ -47,7 +47,7 @@ public final class ModItem extends BorderPane {
JFXButton btnRemove = new JFXButton();
JFXUtilities.runInFX(() -> {
FXUtils.installTooltip(btnRemove, i18n("mods.remove"));
FXUtils.installFastTooltip(btnRemove, i18n("mods.remove"));
});
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
btnRemove.getStyleClass().add("toggle-icon4");
@@ -94,20 +94,18 @@ public final class VersionPage extends StackPane implements DecoratorPage {
PopupMenu managementList = new PopupMenu();
managementPopup = new JFXPopup(managementList);
managementList.getContent().setAll(
new IconedMenuItem(null, i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> Versions.renameVersion(profile, version), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> Versions.deleteVersion(profile, version), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(() -> Versions.updateGameAssets(profile, version), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(() -> FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries")), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(() -> Versions.cleanVersion(profile, version), managementPopup)).addTooltip(i18n("version.manage.clean.tooltip"))
);
FXUtils.installTooltip(btnDelete, i18n("version.manage.remove"));
FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management"));
FXUtils.installTooltip(btnExport, i18n("modpack.export"));
FXUtils.installFastTooltip(btnDelete, i18n("version.manage.remove"));
FXUtils.installFastTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
FXUtils.installFastTooltip(btnManagementMenu, i18n("settings.game.management"));
FXUtils.installFastTooltip(btnExport, i18n("modpack.export"));
btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
FXUtils.installTooltip(btnTestGame, i18n("version.launch.test"));
FXUtils.installFastTooltip(btnTestGame, i18n("version.launch.test"));
setEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
}
@@ -35,6 +35,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.VersionSetting;
@@ -60,6 +61,7 @@ import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class VersionSettingsPage extends StackPane implements DecoratorPage {
@@ -85,7 +87,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
@FXML private ComponentList advancedSettingsPane;
@FXML private ComponentList componentList;
@FXML private ComponentList iconPickerItemWrapper;
@FXML private JFXComboBox<?> cboLauncherVisibility;
@FXML private JFXComboBox<LauncherVisibility> cboLauncherVisibility;
@FXML private JFXCheckBox chkFullscreen;
@FXML private Label lblPhysicalMemory;
@FXML private JFXToggleButton chkNoJVMArgs;
@@ -100,6 +102,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
public VersionSettingsPage() {
FXUtils.loadFXML(this, "/assets/fxml/version/version-settings.fxml");
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase())));
}
@FXML
@@ -50,8 +50,13 @@ public class WorldListPage extends ListPage<WorldListItem> {
public void loadVersion(Profile profile, String id) {
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
itemsProperty().setAll(World.getWorlds(savesDir).stream()
.map(WorldListItem::new).collect(Collectors.toList()));
setLoading(true);
Task.ofResult(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
.finalizedResult(Schedulers.javafx(), (result, isDependentsSucceeded) -> {
setLoading(false);
if (isDependentsSucceeded)
itemsProperty().setAll(result.stream().map(WorldListItem::new).collect(Collectors.toList()));
}).start();
}
@Override
@@ -65,28 +70,26 @@ public class WorldListPage extends ListPage<WorldListItem> {
installWorld(res.get(0));
}
public void installWorld(File zipFile) {
try {
// Only accept one world file because user is required to confirm the new world name
// Or too many input dialogs are popped.
World world = new World(zipFile.toPath());
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.of(() -> world.install(savesDir, name))
.finalized(Schedulers.javafx(), var -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
else
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
}).start();
}).setInitialText(world.getWorldName());
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse world file " + zipFile, e);
Controllers.dialog(i18n("world.import.invalid"));
}
private void installWorld(File zipFile) {
// Only accept one world file because user is required to confirm the new world name
// Or too many input dialogs are popped.
Task.ofResult(() -> new World(zipFile.toPath()))
.finalizedResult(Schedulers.javafx(), world -> {
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.of(() -> world.install(savesDir, name))
.finalized(Schedulers.javafx(), var -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
else
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
}).start();
}).setInitialText(world.getWorldName());
}, e -> {
Logging.LOG.log(Level.WARNING, "Unable to parse world file " + zipFile, e);
Controllers.dialog(i18n("world.import.invalid"));
}).start();
}
}
+16 -41
View File
@@ -104,20 +104,24 @@
-fx-padding: 4 0 4 0;
}
.two-line-list-item > .title {
-fx-text-fill: black;
-fx-font-size: 15px;
}
.two-line-list-item > .subtitle {
-fx-text-fill: gray;
}
.bubble {
-fx-background-color: gray;
-fx-background-radius: 2px;
-fx-text-fill: white;
}
.bubble .two-line-list-item {
-jfx-title-fill: white;
-jfx-subtitle-fill: white;
}
.two-line-list-item {
-jfx-title-font-size: 15px;
-jfx-title-fill: black;
-jfx-subtitle-fill: gray;
.bubble .two-line-list-item > .title,
.bubble .two-line-list-item > .subtitle {
-fx-text-fill: white;
}
.window-title-bar .separator {
@@ -678,20 +682,12 @@
* *
*******************************************************************************/
.jfx-list-cell:odd,
.jfx-list-cell:even,
.list-cell:odd,
.list-cell:even {
.jfx-list-cell, .list-cell {
-fx-background-color: WHITE;
}
.list-cell:selected, .jfx-list-cell:selected {
-fx-background-insets: 0.0;
-fx-text-fill: BLACK;
}
.jfx-list-cell:filled:hover,
.jfx-list-cell:selected .label {
.list-cell:selected, .jfx-list-cell:selected,
.list-cell:hover, .jfx-list-cell:hover {
-fx-text-fill: black;
}
@@ -1109,31 +1105,10 @@
-fx-fill: #D34336;
}
.combo-box-popup .list-view .jfx-list-cell .label,
.combo-box-popup .list-view .jfx-list-cell:filled:hover .label {
-fx-text-fill: BLACK;
}
.combo-box-popup .list-view .jfx-list-cell .custom-jfx-list-view-icon,
.combo-box-popup .list-view .jfx-list-cell:filled:hover .custom-jfx-list-view-icon,
.combo-box-popup .list-view .jfx-list-cell:selected .custom-jfx-list-view-icon {
-fx-fill: -fx-base-color;
}
.combo-box-popup .list-view .jfx-list-cell:odd:selected > .jfx-rippler > StackPane,
.combo-box-popup .list-view .jfx-list-cell:even:selected > .jfx-rippler > StackPane {
-fx-background-color: rgba(0.0, 0.0, 255.0, 0.2);
}
.combo-box-popup .list-view .jfx-list-cell {
-fx-background-insets: 0.0;
}
.combo-box-popup .list-view .jfx-list-cell:odd,
.combo-box-popup .list-view .jfx-list-cell:even {
-fx-background-color: WHITE;
}
.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {
-jfx-rippler-fill: -fx-base-color;
}
@@ -32,6 +32,7 @@
<JFXComboBox fx:id="cboServers" promptText="%account.injector.empty" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<HBox GridPane.columnIndex="2" GridPane.rowIndex="1" spacing="8">
<HBox fx:id="linksContainer" alignment="CENTER_LEFT"/>
<JFXButton fx:id="btnAddServer" styleClass="toggle-icon4" onMouseClicked="#onAddInjecterServer">
<graphic>
<javafx.scene.shape.SVGPath content="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
@@ -60,16 +60,7 @@
<Label text="%settings.advanced.launcher_visible" BorderPane.alignment="CENTER_LEFT"/>
</left>
<right>
<JFXComboBox fx:id="cboLauncherVisibility" BorderPane.alignment="CENTER_RIGHT" FXUtils.limitWidth="300">
<items>
<FXCollections fx:factory="observableArrayList">
<Label text="%settings.advanced.launcher_visibility.close"/>
<Label text="%settings.advanced.launcher_visibility.hide"/>
<Label text="%settings.advanced.launcher_visibility.keep"/>
<Label text="%settings.advanced.launcher_visibility.hide_reopen"/>
</FXCollections>
</items>
</JFXComboBox>
<JFXComboBox fx:id="cboLauncherVisibility" BorderPane.alignment="CENTER_RIGHT" FXUtils.limitWidth="300" />
</right>
</BorderPane>
@@ -49,6 +49,7 @@ account.injector.empty=Empty (Click the plus button right to add)
account.injector.manage=Manage authentication servers
account.injector.manage.title=Authentication servers
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
account.injector.link.register=Register
account.injector.server=Auth Server
account.injector.server_url=Server URL
account.injector.server_name=Server Name
@@ -144,7 +145,7 @@ lang.default=Use system language
launch.advice.corrected=We have already corrected JVM selection and game can be launched now. If you do want to keep your JVM choice, you can turn off the Java VM checker in game settings.
launch.advice.uncorrected=If you are sure that game can be launched normally, you can turn off the Java VM checker in game settings.
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
launch.advice.forge2760_liteloader=Forge 2760 and above is not compatible with LiteLoader, please consider downgrade Forge to 2759 or earlier.
launch.advice.forge2760_liteloader=Forge 2760 and above is not compatible with LiteLoader, please consider upgrade Forge to 2773 or later.
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. You are supposed to install the latest version of Java 8.
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
@@ -307,7 +308,7 @@ settings.advanced.java_permanent_generation_space=PermGen Space/MB
settings.advanced.jvm_args=Java VM Arguments
settings.advanced.launcher_visibility.close=Close the launcher when the game launched.
settings.advanced.launcher_visibility.hide=Hide the launcher when the game launched.
settings.advanced.launcher_visibility.hide_reopen=Hide the launcher and re-open when game closes.
settings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and re-open when game closes.
settings.advanced.launcher_visibility.keep=Keep the launcher visible.
settings.advanced.launcher_visible=Launcher Visibility
settings.advanced.minecraft_arguments=Minecraft Arguments
@@ -48,6 +48,7 @@ account.injector.empty=無(點擊右側加號添加)
account.injector.manage=管理認證伺服器
account.injector.manage.title=認證伺服器
account.injector.http=警告:此伺服器使用不安全的 HTTP 協議,您的密碼在登入時會被明文傳輸。
account.injector.link.register=註冊
account.injector.server=認證伺服器
account.injector.server_url=伺服器位址
account.injector.server_name=伺服器名稱
@@ -143,7 +144,7 @@ lang.default=跟隨系統語言
launch.advice.corrected=我们已经修正了问题。如果您確實希望使用您自訂的 Java 虛擬機,您可以在遊戲設定中關閉 Java 虛擬機相容性檢查。
launch.advice.uncorrected=如果您確實希望使用您自訂的 Java 虛擬機,您可以在遊戲設定中關閉 Java 虛擬機相容性檢查。
launch.advice.different_platform=您的系統為 64 位元,但是 Java 是 32 位元,建議您安裝 64 位 Java。
launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容。請刪除 LiteLoader 或者更換 Forge 至 2759 或更舊的版本。
launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容。請刪除 LiteLoader 或者更換 Forge 至 2773 或更新的版本。
launch.advice.java8_1_13=Minecraft 1.13 只支援 Java 8 或更高版本,請使用 Java 8 或最新版本。
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的 Java 8。
launch.advice.java9=低於 (包含) 1.13 的有安裝 Mod Minecraft 版本 不支援 Java 9 或更高版本,請使用 Java 8。
@@ -306,7 +307,7 @@ settings.advanced.java_permanent_generation_space=記憶體永久儲存區域(
settings.advanced.jvm_args=Java 虛擬機參數(不必填寫)
settings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器
settings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器
settings.advanced.launcher_visibility.hide_reopen=隱藏啟動器並在遊戲結束後重新開啟
settings.advanced.launcher_visibility.hide_and_reopen=隱藏啟動器並在遊戲結束後重新開啟
settings.advanced.launcher_visibility.keep=不隱藏啟動器
settings.advanced.launcher_visible=啟動器可見性
settings.advanced.minecraft_arguments=Minecraft 額外參數(不必填寫)
@@ -48,6 +48,7 @@ account.injector.empty=无(点击右侧加号添加)
account.injector.manage=管理认证服务器
account.injector.manage.title=认证服务器
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
account.injector.link.register=注册
account.injector.server=认证服务器
account.injector.server_url=服务器地址
account.injector.server_name=服务器名称
@@ -143,7 +144,7 @@ lang.default=跟随系统语言
launch.advice.corrected=我们已经修复了问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java。
launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容。请删除 LiteLoader 或者更换 Forge 至 2759 或更旧的版本。
launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容。请删除 LiteLoader 或者更换 Forge 至 2773 或更新的版本。
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议您到 https://java.com 安装最新版的 Java 8。
launch.advice.java9=低于 1.13 的有安装 Mod 的 Minecraft 版本不支持 Java 9 或更高版本,请使用 Java 8。
@@ -306,7 +307,7 @@ settings.advanced.java_permanent_generation_space=内存永久保存区域(不
settings.advanced.jvm_args=Java 虚拟机参数(不必填写)
settings.advanced.launcher_visibility.close=游戏启动后结束启动器
settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器
settings.advanced.launcher_visibility.hide_reopen=隐藏启动器并在游戏结束后重新打开
settings.advanced.launcher_visibility.hide_and_reopen=隐藏启动器并在游戏结束后重新打开
settings.advanced.launcher_visibility.keep=保持启动器可见
settings.advanced.launcher_visible=启动器可见性
settings.advanced.minecraft_arguments=Minecraft 额外参数(不必填写)
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.auth.authlibinjector;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyAsByteArray;
@@ -29,6 +30,8 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
@@ -137,6 +140,7 @@ public class AuthlibInjectorServer implements Observable {
@Nullable
private transient String name;
private transient Map<String, String> links = emptyMap();
private transient boolean metadataRefreshed;
private transient ObservableHelper helper = new ObservableHelper(this);
@@ -162,6 +166,10 @@ public class AuthlibInjectorServer implements Observable {
.orElse(url);
}
public Map<String, String> getLinks() {
return links;
}
public String fetchMetadataResponse() throws IOException {
if (metadataResponse == null || !metadataRefreshed) {
refreshMetadata();
@@ -201,6 +209,16 @@ public class AuthlibInjectorServer implements Observable {
this.name = metaObject.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString))
.orElse(null);
this.links = metaObject.flatMap(meta -> tryCast(meta.get("links"), JsonObject.class))
.map(linksObject -> {
Map<String, String> converted = new LinkedHashMap<>();
linksObject.entrySet().forEach(
entry -> tryCast(entry.getValue(), JsonPrimitive.class).ifPresent(element -> {
converted.put(entry.getKey(), element.getAsString());
}));
return converted;
})
.orElse(emptyMap());
}
}
@@ -17,18 +17,8 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.Argument;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.StringArgument;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MaintainTask extends TaskResult<Version> {
@@ -51,40 +41,30 @@ public class MaintainTask extends TaskResult<Version> {
public static Version maintain(Version version) {
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
List<String> mcArgs = version.getMinecraftArguments().map(StringUtils::tokenize).map(ArrayList::new).orElse(null);
List<Argument> game = version.getArguments().map(Arguments::getGame).map(ArrayList::new).orElseGet(ArrayList::new);
boolean useMcArgs = mcArgs != null;
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
if (!libraryAnalyzer.hasForge()) {
removeTweakClass(useMcArgs, mcArgs, game, "forge");
builder.removeTweakClass("forge");
}
// Installing Forge will override the Minecraft arguments in json, so LiteLoader and OptiFine Tweaker are being re-added.
removeTweakClass(useMcArgs, mcArgs, game, "liteloader");
builder.removeTweakClass("liteloader");
if (libraryAnalyzer.hasLiteLoader()) {
addArgument(useMcArgs, mcArgs, game, "--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker");
builder.addArgument("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker");
}
removeTweakClass(useMcArgs, mcArgs, game, "optifine");
builder.removeTweakClass("optifine");
if (libraryAnalyzer.hasOptiFine()) {
if (!libraryAnalyzer.hasLiteLoader() && !libraryAnalyzer.hasForge()) {
addArgument(useMcArgs, mcArgs, game, "--tweakClass", "optifine.OptiFineTweaker");
builder.addArgument("--tweakClass", "optifine.OptiFineTweaker");
} else {
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
addArgument(useMcArgs, mcArgs, game, "--tweakClass", "optifine.OptiFineForgeTweaker");
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
}
}
Version result;
if (useMcArgs) {
// Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping,
// so we regenerate the minecraftArgument without escaping.
result = version.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString());
} else {
result = version.setArguments(version.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, Collections.emptyList())));
}
return result;
return builder.build();
}
@Override
@@ -92,43 +72,5 @@ public class MaintainTask extends TaskResult<Version> {
return id;
}
private static void removeTweakClass(boolean useMcArgs, List<String> mcArgs, List<Argument> game, String target) {
if (useMcArgs) {
for (int i = 0; i + 1 < mcArgs.size(); ++i) {
String arg0Str = mcArgs.get(i);
String arg1Str = mcArgs.get(i + 1);
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
mcArgs.remove(i);
mcArgs.remove(i);
--i;
}
}
} else {
for (int i = 0; i + 1 < game.size(); ++i) {
Argument arg0 = game.get(i);
Argument arg1 = game.get(i + 1);
if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) {
// We need to preserve the tokens
String arg0Str = arg0.toString();
String arg1Str = arg1.toString();
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
game.remove(i);
game.remove(i);
--i;
}
}
}
}
}
private static void addArgument(boolean useMcArgs, List<String> mcArgs, List<Argument> game, String... args) {
if (useMcArgs) {
mcArgs.addAll(Arrays.asList(args));
} else {
for (String arg : args)
game.add(new StringArgument(arg));
}
}
public static final String ID = "version";
}
@@ -104,6 +104,11 @@ public class LibraryDownloadTask extends Task {
}
}
@Override
public boolean doPreExecute() {
return true;
}
@Override
public void preExecute() throws Exception {
Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary);
@@ -135,6 +140,11 @@ public class LibraryDownloadTask extends Task {
}
}
@Override
public boolean doPostExecute() {
return true;
}
@Override
public void postExecute() throws Exception {
if (!cached)
@@ -76,10 +76,11 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
Version tempVersion = version.setLibraries(Lang.merge(remote.getLibraries(), Collections.singleton(library)));
// --tweakClass will be added in MaintainTask
setResult(version
.setMainClass("net.minecraft.launchwrapper.Launch")
.setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries()))
.setLogging(Collections.emptyMap())
.setLogging(Collections.emptyMap()) // Mods may log in malformed format, causing XML parser to crash. So we suppress using official log4j configuration
);
dependencies.add(dependencyManager.checkLibraryCompletionAsync(tempVersion));
@@ -66,7 +66,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
if (!duplicates.add(mirror))
continue;
boolean isPre = element.getPatch() != null && (element.getPatch().equals("pre") || element.getPatch().startsWith("alpha"));
boolean isPre = element.getPatch() != null && (element.getPatch().startsWith("pre") || element.getPatch().startsWith("alpha"));
if (StringUtils.isBlank(element.getGameVersion()))
continue;
@@ -86,6 +86,7 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper."))
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
// --tweakClass will be added in MaintainTask
setResult(version
.setLibraries(Lang.merge(version.getLibraries(), libraries))
.setMainClass("net.minecraft.launchwrapper.Launch")
@@ -0,0 +1,92 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.game;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
*
* @author huangyuhui
*/
public final class VersionLibraryBuilder {
private final Version version;
private final List<String> mcArgs;
private final List<Argument> game;
private final boolean useMcArgs;
public VersionLibraryBuilder(Version version) {
this.version = version;
this.mcArgs = version.getMinecraftArguments().map(StringUtils::tokenize).map(ArrayList::new).orElse(null);
this.game = version.getArguments().map(Arguments::getGame).map(ArrayList::new).orElseGet(ArrayList::new);
this.useMcArgs = mcArgs != null;
}
public Version build() {
if (useMcArgs) {
// Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping,
// so we regenerate the minecraftArgument without escaping.
return version.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString());
} else {
return version.setArguments(version.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, Collections.emptyList())));
}
}
public void removeTweakClass(String target) {
if (useMcArgs) {
for (int i = 0; i + 1 < mcArgs.size(); ++i) {
String arg0Str = mcArgs.get(i);
String arg1Str = mcArgs.get(i + 1);
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
mcArgs.remove(i);
mcArgs.remove(i);
--i;
}
}
} else {
for (int i = 0; i + 1 < game.size(); ++i) {
Argument arg0 = game.get(i);
Argument arg1 = game.get(i + 1);
if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) {
// We need to preserve the tokens
String arg0Str = arg0.toString();
String arg1Str = arg1.toString();
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
game.remove(i);
game.remove(i);
--i;
}
}
}
}
}
public void addArgument(String... args) {
if (useMcArgs) {
mcArgs.addAll(Arrays.asList(args));
} else {
for (String arg : args)
game.add(new StringArgument(arg));
}
}
}
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.Unzipper;
import org.jackhuang.hmcl.util.io.Zipper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -39,6 +40,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -193,7 +195,7 @@ public class World {
}
private static CompoundTag parseLevelDat(Path path) throws IOException {
try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
try (InputStream is = new BufferedInputStream(new GZIPInputStream(Files.newInputStream(path)))) {
Tag nbt = NBTIO.readTag(is);
if (nbt instanceof CompoundTag)
return (CompoundTag) nbt;
@@ -202,20 +204,20 @@ public class World {
}
}
public static List<World> getWorlds(Path savesDir) {
List<World> worlds = new ArrayList<>();
public static Stream<World> getWorlds(Path savesDir) {
try {
if (Files.exists(savesDir))
for (Path world : Files.newDirectoryStream(savesDir)) {
return Files.list(savesDir).flatMap(world -> {
try {
worlds.add(new World(world));
return Stream.of(new World(world));
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Failed to read world " + world, e);
return Stream.empty();
}
}
});
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Failed to read saves", e);
}
return worlds;
return Stream.empty();
}
}
@@ -64,6 +64,11 @@ public class ModpackUpdateTask extends Task {
FileUtils.copyDirectory(repository.getVersionRoot(id).toPath(), backupFolder);
}
@Override
public boolean doPostExecute() {
return true;
}
@Override
public void postExecute() throws Exception {
if (isDependenciesSucceeded()) {
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.mod;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Lang;
import java.util.ArrayList;
import java.util.Collections;
@@ -44,19 +45,23 @@ public final class MultiMCInstancePatch {
private final List<String> tweakers;
@SerializedName("+libraries")
private final List<Library> _libraries;
@SerializedName("libraries")
private final List<Library> libraries;
public MultiMCInstancePatch() {
this("", "", "", "", "", Collections.emptyList(), Collections.emptyList());
this("", "", "", "", "", Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
}
public MultiMCInstancePatch(String name, String version, String gameVersion, String mainClass, String fileId, List<String> tweakers, List<Library> libraries) {
public MultiMCInstancePatch(String name, String version, String gameVersion, String mainClass, String fileId, List<String> tweakers, List<Library> _libraries, List<Library> libraries) {
this.name = name;
this.version = version;
this.gameVersion = gameVersion;
this.mainClass = mainClass;
this.fileId = fileId;
this.tweakers = new ArrayList<>(tweakers);
this._libraries = new ArrayList<>(_libraries);
this.libraries = new ArrayList<>(libraries);
}
@@ -85,7 +90,7 @@ public final class MultiMCInstancePatch {
}
public List<Library> getLibraries() {
return Collections.unmodifiableList(libraries);
return Lang.merge(_libraries, libraries);
}
}
@@ -21,10 +21,11 @@ import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -63,7 +64,6 @@ public final class MultiMCModpackInstallTask extends Task {
this.name = name;
this.repository = dependencyManager.getGameRepository();
File run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
throw new IllegalArgumentException("Version " + name + " already exists.");
@@ -89,6 +89,22 @@ public final class MultiMCModpackInstallTask extends Task {
if (event.isFailed())
repository.removeVersionFromDisk(name);
});
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public boolean doPreExecute() {
return true;
}
@Override
public void preExecute() throws Exception {
File run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
ModpackConfiguration<MultiMCInstanceConfiguration> config = null;
try {
@@ -102,14 +118,14 @@ public final class MultiMCModpackInstallTask extends Task {
} catch (JsonParseException | IOException ignore) {
}
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", any -> true, config));
try (FileSystem fs = CompressingUtils.readonly(zipFile.toPath()).setEncoding(modpack.getEncoding()).build()) {
if (Files.exists(fs.getPath("/" + manifest.getName() + "/.minecraft")))
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/" + manifest.getName() + "/.minecraft", any -> true, config));
else if (Files.exists(fs.getPath("/" + manifest.getName() + "/minecraft")))
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", any -> true, config));
}
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public List<Task> getDependents() {
return dependents;
@@ -129,20 +145,19 @@ public final class MultiMCModpackInstallTask extends Task {
if (patchJson.toString().endsWith(".json")) {
// If json is malformed, we should stop installing this modpack instead of skipping it.
MultiMCInstancePatch patch = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
List<String> newArguments = new LinkedList<>();
for (String arg : patch.getTweakers()) {
newArguments.add("--tweakClass");
newArguments.add(arg);
}
version = version
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
for (String arg : patch.getTweakers())
builder.addArgument("--tweakClass", arg);
version = builder.build()
.setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries()))
.setMainClass(patch.getMainClass())
.setArguments(version.getArguments().orElseGet(Arguments::new).addGameArguments(newArguments));
.setMainClass(patch.getMainClass());
}
}
}
dependencies.add(new VersionJsonSaveTask(repository, version));
dependencies.add(new MaintainTask(version).then(var -> new VersionJsonSaveTask(repository, var.get(MaintainTask.ID))));
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
@@ -126,7 +126,7 @@ public final class GetTask extends TaskResult<String> {
}
if (size > 0 && size != read)
throw new IllegalStateException("Not completed! Readed: " + read + ", total size: " + size);
throw new IOException("Not completed! Readed: " + read + ", total size: " + size);
String result = baos.toString(charset.name());
setResult(result);
@@ -18,10 +18,7 @@
package org.jackhuang.hmcl.task;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.*;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.InvocationDispatcher;
@@ -58,14 +55,18 @@ public abstract class Task {
this.significance = significance;
}
private TaskState state = TaskState.READY;
private ReadOnlyObjectWrapper<TaskState> state = new ReadOnlyObjectWrapper<>(this, "state", TaskState.READY);
public TaskState getState() {
return state;
return state.get();
}
void setState(TaskState state) {
this.state = state;
this.state.setValue(state);
}
public ReadOnlyObjectProperty<TaskState> stateProperty() {
return state.getReadOnlyProperty();
}
private Throwable lastException = null;
@@ -144,6 +145,10 @@ public abstract class Task {
this.variables = variables;
}
public boolean doPreExecute() {
return false;
}
/**
* @throws InterruptedException if current thread is interrupted
* @see Thread#isInterrupted
@@ -156,6 +161,10 @@ public abstract class Task {
*/
public abstract void execute() throws Exception;
public boolean doPostExecute() {
return false;
}
/**
* @throws InterruptedException if current thread is interrupted
* @see Thread#isInterrupted
@@ -371,12 +380,16 @@ public abstract class Task {
return new CoupleTask<>(null, b, true);
}
public static <V> TaskResult<V> ofResult(Callable<V> callable) {
return ofResult("", callable);
}
public static <V> TaskResult<V> ofResult(String id, Callable<V> callable) {
return new TaskCallable<>(id, callable);
}
public static <V> TaskResult<V> ofResult(String id, ExceptionalFunction<AutoTypingMap<String>, V, ?> closure) {
return new TaskCallable2<>(id, closure);
return new TaskCallable<>(id, closure);
}
private static ExceptionalFunction<AutoTypingMap<String>, Task, ?> convert(Task t) {
@@ -17,6 +17,9 @@
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.util.concurrent.Callable;
/**
@@ -26,9 +29,13 @@ import java.util.concurrent.Callable;
class TaskCallable<V> extends TaskResult<V> {
private final String id;
private final Callable<V> callable;
private final ExceptionalFunction<AutoTypingMap<String>, V, ?> callable;
public TaskCallable(String id, Callable<V> callable) {
this(id, variables -> callable.call());
}
public TaskCallable(String id, ExceptionalFunction<AutoTypingMap<String>, V, ?> callable) {
this.id = id;
this.callable = callable;
}
@@ -40,6 +47,6 @@ class TaskCallable<V> extends TaskResult<V> {
@Override
public void execute() throws Exception {
setResult(callable.call());
setResult(callable.apply(getVariables()));
}
}
@@ -1,46 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
/**
*
* @author huangyuhui
*/
class TaskCallable2<V> extends TaskResult<V> {
private final String id;
private final ExceptionalFunction<AutoTypingMap<String>, V, ?> callable;
public TaskCallable2(String id, ExceptionalFunction<AutoTypingMap<String>, V, ?> callable) {
this.id = id;
this.callable = callable;
}
@Override
public String getId() {
return id;
}
@Override
public void execute() throws Exception {
setResult(callable.apply(getVariables()));
}
}
@@ -149,13 +149,15 @@ public final class TaskExecutor {
try {
task.setVariables(variables);
try {
task.getScheduler().schedule(task::preExecute).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else
throw e;
if (task.doPreExecute()) {
try {
task.getScheduler().schedule(task::preExecute).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else
throw e;
}
}
boolean doDependentsSucceeded = executeTasks(task.getDependents());
@@ -194,13 +196,15 @@ public final class TaskExecutor {
if (doDependenciesSucceeded)
task.setDependenciesSucceeded();
try {
task.getScheduler().schedule(task::postExecute).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else
throw e;
if (task.doPostExecute()) {
try {
task.getScheduler().schedule(task::postExecute).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else
throw e;
}
}
flag = true;
@@ -211,6 +215,7 @@ public final class TaskExecutor {
task.onDone().fireEvent(new TaskEvent(this, task, false));
taskListeners.forEach(it -> it.onFinished(task));
} catch (InterruptedException e) {
task.setLastException(e);
if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
}
@@ -38,6 +38,7 @@ public abstract class TaskListener implements EventListener {
}
public void onFailed(Task task, Throwable throwable) {
onFinished(task);
}
public void onStop(boolean success, TaskExecutor executor) {
@@ -17,6 +17,13 @@
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.ReflectionHelper;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.util.Collection;
import java.util.Collections;
/**
* A task that has a result.
*
@@ -35,4 +42,62 @@ public abstract class TaskResult<V> extends Task {
}
public abstract String getId();
public <R, E extends Exception> TaskResult<R> thenResult(ExceptionalFunction<V, R, E> task) {
return thenResult(Schedulers.defaultScheduler(), task);
}
public <R, E extends Exception> TaskResult<R> thenResult(Scheduler scheduler, ExceptionalFunction<V, R, E> task) {
return thenResult(ReflectionHelper.getCaller().toString(), scheduler, task);
}
public <R, E extends Exception> TaskResult<R> thenResult(String id, Scheduler scheduler, ExceptionalFunction<V, R, E> task) {
return new Subtask<>(id, scheduler, task);
}
public <T extends Exception, K extends Exception> Task finalizedResult(Scheduler scheduler, ExceptionalConsumer<V, T> success, ExceptionalConsumer<Exception, K> failure) {
return finalized(scheduler, variables -> success.accept(getResult()), failure);
}
public Task finalizedResult(Scheduler scheduler, FinalizedCallback<V> callback) {
return new FinalizedTask(this, scheduler,
(variables, isDependentsSucceeded) -> callback.execute(getResult(), isDependentsSucceeded),
ReflectionHelper.getCaller().toString());
}
private class Subtask<R> extends TaskResult<R> {
private final String id;
private final Scheduler scheduler;
private final ExceptionalFunction<V, R, ?> callable;
public Subtask(String id, Scheduler scheduler, ExceptionalFunction<V, R, ?> callable) {
this.id = id;
this.scheduler = scheduler;
this.callable = callable;
}
@Override
public Collection<? extends Task> getDependents() {
return Collections.singleton(TaskResult.this);
}
@Override
public String getId() {
return id;
}
@Override
public Scheduler getScheduler() {
return scheduler;
}
@Override
public void execute() throws Exception {
setResult(callable.apply(TaskResult.this.getResult()));
}
}
public interface FinalizedCallback<V> {
void execute(V result, boolean isDependentsSucceeded) throws Exception;
}
}
@@ -33,7 +33,9 @@ public class InvocationDispatcher<ARG> implements Consumer<ARG> {
public static <ARG> InvocationDispatcher<ARG> runOn(Consumer<Runnable> executor, Consumer<ARG> action) {
return new InvocationDispatcher<>(arg -> executor.accept(() -> {
action.accept(arg.get());
synchronized (action) {
action.accept(arg.get());
}
}));
}
@@ -66,6 +66,10 @@ public class VersionNumberTest {
u = VersionNumber.asVersion("1.6.0_22");
v = VersionNumber.asVersion("1.8.0_11");
Assert.assertTrue(u.compareTo(v) < 0);
u = VersionNumber.asVersion("1.12.2-14.23.5.2760");
v = VersionNumber.asVersion("1.12.2-14.23.4.2739");
Assert.assertTrue(u.compareTo(v) > 0);
}
@Test