TransitionHandler update
This commit is contained in:
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -109,15 +110,13 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
@Override
|
||||
public void refreshVersions() {
|
||||
EventBus.EVENT_BUS.fireEvent(new RefreshingVersionsEvent(this));
|
||||
Schedulers.newThread().schedule(() -> {
|
||||
refreshVersionsImpl();
|
||||
EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this));
|
||||
});
|
||||
refreshVersionsImpl();
|
||||
EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this));
|
||||
}
|
||||
|
||||
public void changeDirectory(File newDirectory) {
|
||||
setBaseDirectory(newDirectory);
|
||||
refreshVersions();
|
||||
refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private void checkModpack() {
|
||||
|
||||
@@ -37,13 +37,12 @@ import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LaunchingStepsPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.Buffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -52,10 +51,11 @@ public final class LauncherHelper {
|
||||
public static final LauncherHelper INSTANCE = new LauncherHelper();
|
||||
private LauncherHelper(){}
|
||||
|
||||
private final LaunchingStepsPane launchingStepsPane = new LaunchingStepsPane();
|
||||
private TaskExecutor executor;
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(() -> {});
|
||||
|
||||
public void launch(String selectedVersion, String launcherName) {
|
||||
public void launch(String selectedVersion, File scriptFile) {
|
||||
Profile profile = Settings.INSTANCE.getSelectedProfile();
|
||||
GameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
@@ -67,12 +67,12 @@ public final class LauncherHelper {
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(dependencyManager.checkGameCompletionAsync(version))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(new CurseCompletionTask(dependencyManager, selectedVersion))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGIN)))
|
||||
.then(Task.of(variables -> {
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(Main.i18n("account.methods"), variables -> {
|
||||
try {
|
||||
variables.set("account", account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy()));
|
||||
} catch (AuthenticationException e) {
|
||||
@@ -87,22 +87,24 @@ public final class LauncherHelper {
|
||||
));
|
||||
}))
|
||||
.then(variables -> {
|
||||
if (launcherName == null) {
|
||||
return variables.<DefaultLauncher>get("launcher").launchAsync();
|
||||
if (scriptFile == null) {
|
||||
return variables.<DefaultLauncher>get("launcher").launchAsync().setName("version.launch");
|
||||
} else {
|
||||
variables.set("script", variables.<DefaultLauncher>get("launcher").makeLaunchScript(launcherName));
|
||||
return null;
|
||||
return variables.<DefaultLauncher>get("launcher").makeLaunchScriptAsync(scriptFile).setName("version.launch");
|
||||
}
|
||||
})
|
||||
.then(Task.of(variables -> {
|
||||
if (launcherName == null) {
|
||||
if (scriptFile == null) {
|
||||
PROCESSES.add(variables.get(DefaultLauncher.LAUNCH_ASYNC_ID));
|
||||
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
|
||||
Main.stopApplication();
|
||||
}
|
||||
} else
|
||||
Platform.runLater(() ->
|
||||
Controllers.dialog(Main.i18n("version.launch_script.success", scriptFile.getAbsolutePath())));
|
||||
}))
|
||||
.executor();
|
||||
|
||||
launchingStepsPane.setExecutor(executor);
|
||||
executor.addTaskListener(new TaskListener() {
|
||||
AtomicInteger finished = new AtomicInteger(0);
|
||||
|
||||
@@ -113,17 +115,6 @@ public final class LauncherHelper {
|
||||
launchingStepsPane.setProgress(1.0 * finished.get() / executor.getRunningTasks());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate(AutoTypingMap<String> variables) {
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog();
|
||||
|
||||
if (variables.containsKey("script")) {
|
||||
Controllers.dialog(Main.i18n("version.launch_script.success", variables.<File>get("script").getAbsolutePath()));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
executor.start();
|
||||
@@ -163,8 +154,9 @@ public final class LauncherHelper {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.closeDialog();
|
||||
|
||||
launchingStepsPane.setCurrentState(state.toString());
|
||||
launchingStepsPane.setCurrentState(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSteps((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
}
|
||||
|
||||
private void checkExit(LauncherVisibility v) {
|
||||
|
||||
@@ -17,10 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.Main;
|
||||
|
||||
public enum LoadingState {
|
||||
DEPENDENCIES,
|
||||
MODS,
|
||||
LOGIN,
|
||||
LAUNCHING,
|
||||
DONE
|
||||
DEPENDENCIES("launch.state.dependencies"),
|
||||
MODS("launch.state.modpack"),
|
||||
LOGGING_IN("launch.state.logging_in"),
|
||||
LAUNCHING("launch.state.waiting_launching"),
|
||||
DONE("");
|
||||
|
||||
private final String key;
|
||||
|
||||
LoadingState(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getLocalizedMessage() {
|
||||
return Main.i18n(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
private void onProfileChanged() {
|
||||
getSelectedProfile().getRepository().refreshVersions();
|
||||
getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile()));
|
||||
}
|
||||
|
||||
|
||||
@@ -77,4 +77,8 @@ public class AdvancedListBox extends ScrollPane {
|
||||
public void setSpacing(double spacing) {
|
||||
container.setSpacing(spacing);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
container.getChildren().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +99,24 @@ public final class Controllers {
|
||||
stage.setTitle(Main.TITLE);
|
||||
}
|
||||
|
||||
public static Region getDialogContent() {
|
||||
return decorator.getDialog().getContent();
|
||||
}
|
||||
|
||||
public static JFXDialog dialog(Region content) {
|
||||
return decorator.showDialog(content);
|
||||
}
|
||||
|
||||
public static void dialog(String text) {
|
||||
dialog(new MessageDialogPane(text, decorator.getDialog()));
|
||||
dialog(text, null);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title) {
|
||||
dialog(text, title, null);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, decorator.getDialog(), onAccept));
|
||||
}
|
||||
|
||||
public static void inputDialog(String text, Consumer<String> onResult) {
|
||||
|
||||
@@ -47,13 +47,14 @@ import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public final class Decorator extends StackPane implements AbstractWizardDisplayer {
|
||||
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
private static final SVGGlyph resizeMax = Lang.apply(new SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE),
|
||||
@@ -421,9 +422,11 @@ public final class Decorator extends StackPane implements AbstractWizardDisplaye
|
||||
}
|
||||
|
||||
public JFXDialog showDialog(Region content) {
|
||||
dialog.setContent(content);
|
||||
if (!dialogShown)
|
||||
dialog.show();
|
||||
if (dialog.getContent() != content) {
|
||||
dialog.setContent(content);
|
||||
if (!dialogShown)
|
||||
dialog.show();
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
|
||||
@@ -59,8 +60,8 @@ public class InstallerController {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new VersionJsonSaveTask(profile.getRepository(), version.setLibraries(newList))
|
||||
.with(Task.of(profile.getRepository()::refreshVersions))
|
||||
.with(Task.of(() -> loadVersion(this.profile, this.versionId)))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
.start();
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -43,7 +44,7 @@ public class InstallerItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblInstallerArtifact.setText(artifact);
|
||||
lblInstallerVersion.setText(version);
|
||||
lblInstallerVersion.setText(Main.i18n("archive.version") + ": " + version);
|
||||
}
|
||||
|
||||
public void onDelete() {
|
||||
|
||||
@@ -24,8 +24,10 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
@@ -39,6 +41,7 @@ import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
@@ -65,7 +68,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), Main.i18n("install")));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersions());
|
||||
FXUtils.installTooltip(btnAdd, 0, 5000, 0, new Tooltip(Main.i18n("install")));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, 0, 5000, 0, new Tooltip(Main.i18n("button.refresh")));
|
||||
}
|
||||
|
||||
private Node buildNode(Profile profile, String version, String game) {
|
||||
@@ -82,9 +87,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Main.i18n("login.no_Player007"));
|
||||
else {
|
||||
Controllers.inputDialog(Main.i18n("mainwindow.enter_script_name"), file -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setInitialDirectory(profile.getRepository().getRunDirectory(version));
|
||||
chooser.setTitle(Main.i18n("version.launch_script.save"));
|
||||
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
? new FileChooser.ExtensionFilter(Main.i18n("extension.bat"), "*.bat")
|
||||
: new FileChooser.ExtensionFilter(Main.i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(version, file);
|
||||
});
|
||||
}
|
||||
});
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
|
||||
@@ -138,7 +138,7 @@ public final class ModController {
|
||||
public void onAdd() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Main.i18n("mods.choose_mod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter("Mod", "*.jar", "*.zip", "*.litemod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(Main.i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
|
||||
File res = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (res == null) return;
|
||||
Task.of(() -> modManager.addMod(versionId, res))
|
||||
|
||||
@@ -24,6 +24,7 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -54,7 +55,7 @@ public final class ModItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblModFileName.setText(info.getFileName());
|
||||
lblModAuthor.setText(info.getName() + ", Version: " + info.getVersion() + ", Game: " + info.getGameVersion() + ", Authors: " + info.getAuthors());
|
||||
lblModAuthor.setText(info.getName() + ", " + Main.i18n("archive.version") + ": " + info.getVersion() + ", " + Main.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Main.i18n("archive.author") + ": " + info.getAuthors());
|
||||
chkEnabled.setSelected(info.isActive());
|
||||
chkEnabled.selectedProperty().addListener((a, b, newValue) -> {
|
||||
info.activeProperty().set(newValue);
|
||||
|
||||
@@ -52,7 +52,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
this.profile = profile;
|
||||
|
||||
title = new SimpleStringProperty(this, "title",
|
||||
profile == null ? Main.i18n("ui.newProfileWindow.title") : Main.i18n("profile") + " - " + profile.getName());
|
||||
profile == null ? Main.i18n("profile.new") : Main.i18n("profile") + " - " + profile.getName());
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -32,6 +33,7 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -68,6 +70,10 @@ public final class VersionItem extends StackPane {
|
||||
btnLaunch.setGraphic(SVG.launch("black", 15, 15));
|
||||
btnScript.setGraphic(SVG.script("black", 15, 15));
|
||||
|
||||
FXUtils.installTooltip(btnSettings, 0, 5000, 0, new Tooltip(Main.i18n("version.manage.settings")));
|
||||
FXUtils.installTooltip(btnLaunch, 0, 5000, 0, new Tooltip(Main.i18n("version.launch")));
|
||||
FXUtils.installTooltip(btnScript, 0, 5000, 0, new Tooltip(Main.i18n("version.launch_script")));
|
||||
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
FXUtils.limitSize(iconView, 32, 32);
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private JFXButton btnBrowseMenu;
|
||||
@FXML
|
||||
private JFXButton btnDelete;
|
||||
@FXML
|
||||
private JFXButton btnManagementMenu;
|
||||
@FXML
|
||||
private JFXButton btnExport;
|
||||
@@ -80,6 +82,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
browsePopup = new JFXPopup(browseList);
|
||||
managementPopup = new JFXPopup(managementList);
|
||||
|
||||
FXUtils.installTooltip(btnDelete, 0, 5000, 0, new Tooltip(Main.i18n("version.manage.remove")));
|
||||
FXUtils.installTooltip(btnBrowseMenu, 0, 5000, 0, new Tooltip(Main.i18n("game_settings.exploration")));
|
||||
FXUtils.installTooltip(btnManagementMenu, 0, 5000, 0, new Tooltip(Main.i18n("game_settings.management")));
|
||||
FXUtils.installTooltip(btnExport, 0, 5000, 0, new Tooltip(Main.i18n("modpack.export")));
|
||||
@@ -109,9 +112,12 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public void onDelete() {
|
||||
profile.getRepository().removeVersionFromDisk(version);
|
||||
profile.getRepository().refreshVersions();
|
||||
Controllers.navigate(null);
|
||||
if (FXUtils.alert(Alert.AlertType.CONFIRMATION, "Confirm", Main.i18n("version.manage.remove.confirm") + version)) {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onExport() {
|
||||
@@ -154,18 +160,13 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
Optional<String> res = FXUtils.inputDialog("Input", Main.i18n("version.manage.rename.message"), null, version);
|
||||
if (res.isPresent()) {
|
||||
if (profile.getRepository().renameVersion(version, res.get())) {
|
||||
profile.getRepository().refreshVersions();
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: // remove a version
|
||||
if (FXUtils.alert(Alert.AlertType.CONFIRMATION, "Confirm", Main.i18n("version.manage.remove.confirm") + version)) {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersions();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
onDelete();
|
||||
break;
|
||||
case 2: // redownload asset index
|
||||
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version).resolve(profile.getRepository())).start();
|
||||
|
||||
@@ -24,6 +24,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -73,6 +74,7 @@ public final class VersionSettingsController {
|
||||
@FXML private MultiFileItem javaItem;
|
||||
@FXML private MultiFileItem gameDirItem;
|
||||
@FXML private JFXToggleButton chkShowLogs;
|
||||
@FXML private JFXButton btnIconSelection;
|
||||
@FXML private ImageView iconView;
|
||||
|
||||
public void initialize() {
|
||||
@@ -114,6 +116,8 @@ public final class VersionSettingsController {
|
||||
gameDirItem.createChildren(Main.i18n("advancedsettings.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(Main.i18n("advancedsettings.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
|
||||
));
|
||||
|
||||
FXUtils.installTooltip(btnIconSelection, 0, 5000, 0, new Tooltip(Main.i18n("button.edit")));
|
||||
}
|
||||
|
||||
public void loadVersionSetting(Profile profile, String versionId, VersionSetting versionSetting) {
|
||||
@@ -245,7 +249,7 @@ public final class VersionSettingsController {
|
||||
|
||||
public void onExploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Image", "*.png"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("extension.png"), "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
|
||||
@@ -22,7 +22,8 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public interface AnimationHandler {
|
||||
Node getSnapshot();
|
||||
Duration getDuration();
|
||||
Pane getCurrentRoot();
|
||||
Node getPreviousNode();
|
||||
Node getCurrentNode();
|
||||
}
|
||||
|
||||
@@ -31,53 +31,57 @@ public enum ContainerAnimations {
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
FADE(c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getSnapshot().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(), new KeyValue(c.getSnapshot().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)))),
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)))),
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_IN(c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getSnapshot().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getSnapshot().scaleXProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().scaleYProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_OUT(c ->
|
||||
(Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getSnapshot().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getSnapshot().scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().scaleYProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_LEFT(c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentRoot().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentRoot().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
|
||||
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_RIGHT(c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentRoot().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentRoot().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getSnapshot().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
|
||||
|
||||
private AnimationProducer animationProducer;
|
||||
|
||||
|
||||
@@ -32,22 +32,24 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
private final StackPane view;
|
||||
private Timeline animation;
|
||||
private Duration duration;
|
||||
private final ImageView snapshot;
|
||||
private Node previousNode, currentNode;
|
||||
|
||||
/**
|
||||
* @param view A stack pane that contains another control that is [Parent]
|
||||
* @param view A stack pane that contains another control that is {@link Parent}
|
||||
*/
|
||||
public TransitionHandler(StackPane view) {
|
||||
this.view = view;
|
||||
|
||||
snapshot = new ImageView();
|
||||
snapshot.setPreserveRatio(true);
|
||||
snapshot.setSmooth(true);
|
||||
currentNode = view.getChildren().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getSnapshot() {
|
||||
return snapshot;
|
||||
public Node getPreviousNode() {
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getCurrentNode() {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,10 +78,7 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
snapshot.setImage(null);
|
||||
snapshot.setX(0);
|
||||
snapshot.setY(0);
|
||||
snapshot.setVisible(false);
|
||||
view.getChildren().remove(previousNode);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
@@ -87,23 +86,16 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
|
||||
private void updateContent(Node newView) {
|
||||
if (view.getWidth() > 0 && view.getHeight() > 0) {
|
||||
Node content = view.getChildren().stream().findFirst().orElse(null);
|
||||
WritableImage image;
|
||||
if (content != null && content instanceof Parent) {
|
||||
view.getChildren().setAll();
|
||||
image = FXUtils.takeSnapshot((Parent) content, view.getWidth(), view.getHeight());
|
||||
view.getChildren().setAll(content);
|
||||
} else
|
||||
image = view.snapshot(new SnapshotParameters(), new WritableImage((int) view.getWidth(), (int) view.getHeight()));
|
||||
snapshot.setImage(image);
|
||||
snapshot.setFitWidth(view.getWidth());
|
||||
snapshot.setFitHeight(view.getHeight());
|
||||
previousNode = currentNode;
|
||||
if (previousNode == null)
|
||||
previousNode = NULL;
|
||||
} else
|
||||
snapshot.setImage(null);
|
||||
previousNode = NULL;
|
||||
|
||||
snapshot.setVisible(true);
|
||||
snapshot.setOpacity(1.0);
|
||||
view.getChildren().setAll(snapshot, newView);
|
||||
snapshot.toFront();
|
||||
currentNode = newView;
|
||||
|
||||
view.getChildren().setAll(previousNode, currentNode);
|
||||
}
|
||||
|
||||
private static final StackPane NULL = new StackPane();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import java.io.File;
|
||||
@@ -51,6 +53,7 @@ public class FileItem extends BorderPane {
|
||||
right.setGraphic(SVG.pencil("black", 15, 15));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
right.setOnMouseClicked(e -> onExplore());
|
||||
FXUtils.installTooltip(right, 0, 5000, 0, new Tooltip(Main.i18n("button.edit")));
|
||||
setRight(right);
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
private final String text;
|
||||
private final JFXDialog dialog;
|
||||
@@ -32,13 +34,22 @@ public final class MessageDialogPane extends StackPane {
|
||||
private JFXButton acceptButton;
|
||||
@FXML
|
||||
private Label content;
|
||||
@FXML
|
||||
private Label title;
|
||||
|
||||
public MessageDialogPane(String text, JFXDialog dialog) {
|
||||
public MessageDialogPane(String text, String title, JFXDialog dialog, Runnable onAccept) {
|
||||
this.text = text;
|
||||
this.dialog = dialog;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
if (title != null)
|
||||
this.title.setText(title);
|
||||
|
||||
content.setText(text);
|
||||
acceptButton.setOnMouseClicked(e -> dialog.close());
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
dialog.close();
|
||||
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,37 +15,80 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskListPane;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class TaskExecutorDialogPane extends StackPane {
|
||||
private TaskExecutor executor;
|
||||
|
||||
public class LaunchingStepsPane extends StackPane {
|
||||
@FXML
|
||||
private JFXProgressBar pgsTasks;
|
||||
@FXML
|
||||
private Label lblCurrentState;
|
||||
@FXML
|
||||
private Label lblSteps;
|
||||
@FXML
|
||||
private JFXButton btnCancel;
|
||||
@FXML
|
||||
private TaskListPane taskListPane;
|
||||
|
||||
public LaunchingStepsPane() {
|
||||
public TaskExecutorDialogPane(Runnable cancel) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/launching-steps.fxml");
|
||||
|
||||
FXUtils.limitHeight(this, 200);
|
||||
FXUtils.limitWidth(this, 400);
|
||||
|
||||
btnCancel.setOnMouseClicked(e -> {
|
||||
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
|
||||
cancel.run();
|
||||
});
|
||||
}
|
||||
|
||||
public void setExecutor(TaskExecutor executor) {
|
||||
this.executor = executor;
|
||||
taskListPane.setExecutor(executor);
|
||||
}
|
||||
|
||||
public StringProperty currentStateProperty() {
|
||||
return lblCurrentState.textProperty();
|
||||
}
|
||||
|
||||
public String getCurrentState() {
|
||||
return lblCurrentState.getText();
|
||||
}
|
||||
|
||||
public void setCurrentState(String currentState) {
|
||||
lblCurrentState.setText(currentState);
|
||||
}
|
||||
|
||||
public StringProperty stepsProperty() {
|
||||
return lblSteps.textProperty();
|
||||
}
|
||||
|
||||
public String getSteps() {
|
||||
return lblSteps.getText();
|
||||
}
|
||||
|
||||
public void setSteps(String steps) {
|
||||
lblSteps.setText(steps);
|
||||
}
|
||||
|
||||
public void setProgress(double progress) {
|
||||
pgsTasks.setProgress(progress);
|
||||
if (progress == Double.MAX_VALUE)
|
||||
pgsTasks.setVisible(false);
|
||||
else
|
||||
pgsTasks.setProgress(progress);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.AbstractWizardDisplayer;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplayer {
|
||||
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(() -> {
|
||||
Controllers.closeDialog();
|
||||
Controllers.navigate(null);
|
||||
});
|
||||
|
||||
pane.setCurrentState(Main.i18n("message.doing"));
|
||||
pane.setProgress(Double.MAX_VALUE);
|
||||
if (settings.containsKey("title")) {
|
||||
Object title = settings.get("title");
|
||||
if (title instanceof StringProperty)
|
||||
pane.currentStateProperty().bind((StringProperty) title);
|
||||
else if (title instanceof String)
|
||||
pane.setCurrentState((String) title);
|
||||
}
|
||||
|
||||
if (settings.containsKey("subtitle")) {
|
||||
Object subtitle = settings.get("subtitle");
|
||||
if (subtitle instanceof StringProperty)
|
||||
pane.stepsProperty().bind((StringProperty) subtitle);
|
||||
else if (subtitle instanceof String)
|
||||
pane.setSteps((String) subtitle);
|
||||
}
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
TaskExecutor executor = task.executor(e -> new TaskListener() {
|
||||
@Override
|
||||
public void onSucceed() {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(Main.i18n("message.success"), null, () -> Controllers.navigate(null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
String appendix = StringUtils.getStackTrace(e.getLastException());
|
||||
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Main.i18n("wizard.failed"), () -> Controllers.navigate(null)));
|
||||
}
|
||||
});
|
||||
pane.setExecutor(executor);
|
||||
Controllers.dialog(pane);
|
||||
executor.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
@@ -45,22 +46,26 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class TaskListPane extends StackPane {
|
||||
private final TaskExecutor executor;
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final Map<Task, ProgressListNode> nodes = new HashMap<>();
|
||||
|
||||
public TaskListPane(TaskExecutor executor, Runnable onTerminate) {
|
||||
this.executor = executor;
|
||||
public TaskListPane() {
|
||||
listBox.setSpacing(0);
|
||||
|
||||
getChildren().setAll(listBox);
|
||||
}
|
||||
|
||||
public void setExecutor(TaskExecutor executor) {
|
||||
executor.addTaskListener(new TaskListener() {
|
||||
@Override
|
||||
public void onStart() {
|
||||
Platform.runLater(listBox::clear);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(Task task) {
|
||||
if (!task.getSignificance().shouldShow())
|
||||
return;
|
||||
ProgressListNode node = new ProgressListNode(task);
|
||||
nodes.put(task, node);
|
||||
Platform.runLater(() -> listBox.add(node));
|
||||
|
||||
if (task instanceof GameAssetRefreshTask) {
|
||||
task.setName(Main.i18n("assets.download"));
|
||||
@@ -83,6 +88,11 @@ public final class TaskListPane extends StackPane {
|
||||
} else if (task instanceof HMCLModpackExportTask) {
|
||||
task.setName(Main.i18n("modpack.export"));
|
||||
}
|
||||
|
||||
ProgressListNode node = new ProgressListNode(task);
|
||||
nodes.put(task, node);
|
||||
Platform.runLater(() -> listBox.add(node));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,17 +111,10 @@ public final class TaskListPane extends StackPane {
|
||||
return;
|
||||
Platform.runLater(() -> node.setThrowable(throwable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
Optional.ofNullable(onTerminate).ifPresent(Runnable::run);
|
||||
}
|
||||
});
|
||||
|
||||
getChildren().setAll(listBox);
|
||||
}
|
||||
|
||||
private static class ProgressListNode extends StackPane {
|
||||
private static class ProgressListNode extends VBox {
|
||||
private final JFXProgressBar bar = new JFXProgressBar();
|
||||
private final Label title = new Label();
|
||||
private final Label state = new Label();
|
||||
@@ -124,9 +127,8 @@ public final class TaskListPane extends StackPane {
|
||||
BorderPane borderPane = new BorderPane();
|
||||
borderPane.setLeft(title);
|
||||
borderPane.setRight(state);
|
||||
getChildren().addAll(bar, borderPane);
|
||||
getChildren().addAll(borderPane, bar);
|
||||
|
||||
bar.setMinHeight(20);
|
||||
bar.minWidthProperty().bind(widthProperty());
|
||||
bar.prefWidthProperty().bind(widthProperty());
|
||||
bar.maxWidthProperty().bind(widthProperty());
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -36,26 +37,31 @@ import static org.jackhuang.hmcl.Main.i18n;
|
||||
class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
private final InstallerWizardProvider provider;
|
||||
private final WizardController controller;
|
||||
private final GameRepository repository;
|
||||
private final DownloadProvider downloadProvider;
|
||||
|
||||
@FXML private VBox list;
|
||||
@FXML private JFXButton btnForge;
|
||||
@FXML private JFXButton btnLiteLoader;
|
||||
@FXML private JFXButton btnOptiFine;
|
||||
@FXML private Label lblGameVersion;
|
||||
@FXML private Label lblVersionName;
|
||||
@FXML private Label lblForge;
|
||||
@FXML private Label lblLiteLoader;
|
||||
@FXML
|
||||
private VBox list;
|
||||
@FXML
|
||||
private JFXButton btnForge;
|
||||
@FXML
|
||||
private JFXButton btnLiteLoader;
|
||||
@FXML
|
||||
private JFXButton btnOptiFine;
|
||||
@FXML
|
||||
private Label lblGameVersion;
|
||||
@FXML
|
||||
private Label lblVersionName;
|
||||
@FXML
|
||||
private Label lblForge;
|
||||
@FXML
|
||||
private Label lblLiteLoader;
|
||||
@FXML
|
||||
private Label lblOptiFine;
|
||||
@FXML private JFXButton btnInstall;
|
||||
@FXML
|
||||
private JFXButton btnInstall;
|
||||
|
||||
public AdditionalInstallersPage(InstallerWizardProvider provider, WizardController controller, GameRepository repository, DownloadProvider downloadProvider) {
|
||||
this.provider = provider;
|
||||
this.controller = controller;
|
||||
this.repository = repository;
|
||||
this.downloadProvider = downloadProvider;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/additional-installers.fxml");
|
||||
|
||||
@@ -64,55 +70,64 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
btnForge.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 0);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), provider.getGameVersion(), downloadProvider, "forge", () -> { controller.onPrev(false); }));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), provider.getGameVersion(), downloadProvider, "forge", () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
|
||||
btnLiteLoader.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 1);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), provider.getGameVersion(), downloadProvider, "liteloader", () -> { controller.onPrev(false); }));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), provider.getGameVersion(), downloadProvider, "liteloader", () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
|
||||
btnOptiFine.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 2);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), provider.getGameVersion(), downloadProvider, "optifine", () -> { controller.onPrev(false); }));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), provider.getGameVersion(), downloadProvider, "optifine", () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
|
||||
btnInstall.setOnMouseClicked(e -> onInstall());
|
||||
}
|
||||
|
||||
private void onInstall() {
|
||||
controller.onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Choose a game version";
|
||||
return Main.i18n("settings.tabs.installers");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText("Current Game Version: " + provider.getGameVersion());
|
||||
lblGameVersion.setText(Main.i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
|
||||
btnForge.setDisable(provider.getForge() != null);
|
||||
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText("Forge Versoin: " + Lang.nonNull(provider.getForge(), controller.getSettings().get("forge")));
|
||||
lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), controller.getSettings().get("forge")));
|
||||
else
|
||||
lblForge.setText("Forge not installed");
|
||||
lblForge.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.forge")));
|
||||
|
||||
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
|
||||
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText("LiteLoader Versoin: " + Lang.nonNull(provider.getLiteLoader(), controller.getSettings().get("liteloader")));
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), controller.getSettings().get("liteloader")));
|
||||
else
|
||||
lblLiteLoader.setText("LiteLoader not installed");
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.liteloader")));
|
||||
|
||||
btnOptiFine.setDisable(provider.getOptiFine() != null);
|
||||
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText("OptiFine Versoin: " + Lang.nonNull(provider.getOptiFine(), controller.getSettings().get("optifine")));
|
||||
lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), controller.getSettings().get("optifine")));
|
||||
else
|
||||
lblOptiFine.setText("OptiFine not installed");
|
||||
lblOptiFine.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.optifine")));
|
||||
|
||||
}
|
||||
|
||||
@Override public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALLER_TYPE);
|
||||
}
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALLER_TYPE);
|
||||
}
|
||||
|
||||
public void onInstall() {
|
||||
controller.onFinish();
|
||||
}
|
||||
|
||||
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
|
||||
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
@@ -77,7 +78,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
Task finalizeTask = Task.of(() -> {
|
||||
profile.getRepository().refreshVersions();
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
VersionSetting vs = profile.specializeVersionSetting(name);
|
||||
profile.getRepository().undoMark(name);
|
||||
if (vs != null)
|
||||
@@ -99,6 +100,9 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", Main.i18n("install.success"));
|
||||
settings.put("failure_message", Main.i18n("install.failed"));
|
||||
|
||||
switch (Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1)) {
|
||||
case 0: return finishVersionDownloadingAsync(settings);
|
||||
case 1: return finishModpackInstallingAsync(settings);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -78,18 +79,21 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", Main.i18n("install.success"));
|
||||
settings.put("failure_message", Main.i18n("install.failed"));
|
||||
|
||||
Task ret = Task.empty();
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "forge", (String) settings.get("forge")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(gameVersion, version, "forge", (String) settings.get("forge")));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "liteloader", (String) settings.get("liteloader")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(gameVersion, version, "liteloader", (String) settings.get("liteloader")));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "optifine", (String) settings.get("optifine")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(gameVersion, version, "optifine", (String) settings.get("optifine")));
|
||||
|
||||
return ret.with(Task.of(profile.getRepository()::refreshVersions));
|
||||
return ret.then(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -104,7 +104,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
|
||||
public static final String MODPACK_NAME = "modpack.name";
|
||||
public static final String MODPACK_VERSION = "modpack.version";
|
||||
public static final String MODPACK_AUTHOR = "modpack.author";
|
||||
public static final String MODPACK_AUTHOR = "archive.author";
|
||||
public static final String MODPACK_DESCRIPTION = "modpack.description";
|
||||
public static final String MODPACK_INCLUDE_LAUNCHER = "modpack.include_launcher";
|
||||
public static final String MODPACK_FILE = "modpack.file";
|
||||
|
||||
@@ -38,62 +38,11 @@ public interface AbstractWizardDisplayer extends WizardDisplayer {
|
||||
|
||||
Queue<Object> getCancelQueue();
|
||||
|
||||
@Override
|
||||
default void handleDeferredWizardResult(Map<String, Object> settings, DeferredWizardResult deferredWizardResult) {
|
||||
VBox vbox = new VBox();
|
||||
JFXProgressBar progressBar = new JFXProgressBar();
|
||||
Label label = new Label();
|
||||
progressBar.setMaxHeight(10);
|
||||
vbox.getChildren().addAll(progressBar, label);
|
||||
|
||||
StackPane root = new StackPane();
|
||||
root.getChildren().add(vbox);
|
||||
navigateTo(root, Navigation.NavigationDirection.FINISH);
|
||||
|
||||
getCancelQueue().add(Lang.thread(() -> {
|
||||
deferredWizardResult.start(settings, new ResultProgressHandle() {
|
||||
private boolean running = true;
|
||||
|
||||
@Override
|
||||
public void setProgress(int currentStep, int totalSteps) {
|
||||
progressBar.setProgress(1.0 * currentStep / totalSteps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(String description, int currentStep, int totalSteps) {
|
||||
label.setText(description);
|
||||
progressBar.setProgress(1.0 * currentStep / totalSteps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBusy(String description) {
|
||||
progressBar.setProgress(JFXProgressBar.INDETERMINATE_PROGRESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finished(Object result) {
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(String message, boolean canNavigateBack) {
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
});
|
||||
|
||||
Platform.runLater(this::navigateToSuccess);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutor executor = task.with(Task.of(Schedulers.javafx(), this::navigateToSuccess)).executor();
|
||||
TaskListPane pane = new TaskListPane(executor, () -> Platform.runLater(AbstractWizardDisplayer.this::navigateToSuccess));
|
||||
TaskListPane pane = new TaskListPane();
|
||||
pane.setExecutor(executor);
|
||||
navigateTo(pane, Navigation.NavigationDirection.FINISH);
|
||||
getCancelQueue().add(executor);
|
||||
executor.start();
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
/**
|
||||
* A controller for the progress bar shown in the user interface. Used in
|
||||
* conjunction then `DeferredWizardResult` for cases where at
|
||||
* the conclusion of the wizard, the work to create the final wizard result
|
||||
* will take a while and needs to happen on a background thread.
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
public interface ResultProgressHandle {
|
||||
|
||||
/**
|
||||
* Set the current position and total number of steps. Note it is
|
||||
* inadvisable to be holding any locks when calling this method, as it
|
||||
* may immediately update the GUI using
|
||||
* `EventQueue.invokeAndWait()`.
|
||||
*
|
||||
* @param currentStep the current step in the progress of computing the
|
||||
* * result.
|
||||
* *
|
||||
* @param totalSteps the total number of steps. Must be greater than
|
||||
* * or equal to currentStep.
|
||||
*/
|
||||
void setProgress(int currentStep, int totalSteps);
|
||||
|
||||
/**
|
||||
* Set the current position and total number of steps, and description
|
||||
* of what the computation is doing. Note it is
|
||||
* inadvisable to be holding any locks when calling this method, as it
|
||||
* may immediately update the GUI using
|
||||
* `EventQueue.invokeAndWait()`.
|
||||
* @param description Text to describe what is being done, which can
|
||||
* * be displayed in the UI.
|
||||
* *
|
||||
* @param currentStep the current step in the progress of computing the
|
||||
* * result.
|
||||
* *
|
||||
* @param totalSteps the total number of steps. Must be greater than
|
||||
* * or equal to currentStep.
|
||||
*/
|
||||
void setProgress(String description, int currentStep, int totalSteps);
|
||||
|
||||
/**
|
||||
* Set the status as "busy" - a rotating icon will be displayed instead
|
||||
* of a percent complete progress bar.
|
||||
|
||||
* Note it is inadvisable to be holding any locks when calling this method, as it
|
||||
* may immediately update the GUI using
|
||||
* `EventQueue.invokeAndWait()`.
|
||||
* @param description Text to describe what is being done, which can
|
||||
* * be displayed in the UI.
|
||||
*/
|
||||
void setBusy(String description);
|
||||
|
||||
/**
|
||||
* Call this method when the computation is complete, and pass in the
|
||||
* final result of the computation. The method doing the computation
|
||||
* (`DeferredWizardResult.start()` or something it
|
||||
* called) should exit immediately after calling this method. If the
|
||||
* `failed()` method is called after this method has been
|
||||
* called, a runtime exception may be thrown.
|
||||
* @param result the Object which was computed, if any.
|
||||
*/
|
||||
void finished(Object result);
|
||||
|
||||
/**
|
||||
* Call this method if computation fails. The message may be some text
|
||||
* describing what went wrong, or null if no description.
|
||||
* @param message The text to display to the user. The method
|
||||
* * doing the computation (`DeferredWizardResult.start()` or something it
|
||||
* * called). If the `finished()` method is called after this
|
||||
* * method has been called, a runtime exception may be thrown.
|
||||
* * should exit immediately after calling this method.
|
||||
* * It is A description of what went wrong, or null.
|
||||
* *
|
||||
* @param canNavigateBack whether or not the Prev button should be
|
||||
* * enabled.
|
||||
*/
|
||||
void failed(String message, boolean canNavigateBack);
|
||||
|
||||
/**
|
||||
* Returns true if the computation is still running, i.e., if neither finished or failed have been called.
|
||||
*
|
||||
* @return true if there is no result yet.
|
||||
*/
|
||||
boolean isRunning();
|
||||
}
|
||||
@@ -44,6 +44,11 @@ public final class Summary {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Summary(Node component, Object result) {
|
||||
this.component = component;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The component that will display the summary information
|
||||
*/
|
||||
|
||||
@@ -100,8 +100,7 @@ public class WizardController implements Navigation {
|
||||
@Override
|
||||
public void onFinish() {
|
||||
Object result = provider.finish(settings);
|
||||
if (result instanceof DeferredWizardResult) displayer.handleDeferredWizardResult(settings, ((DeferredWizardResult) result));
|
||||
else if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT);
|
||||
if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT);
|
||||
else if (result instanceof Task) displayer.handleTask(settings, ((Task) result));
|
||||
else if (result != null) throw new IllegalStateException("Unrecognized wizard result: " + result);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,5 @@ public interface WizardDisplayer {
|
||||
void onEnd();
|
||||
void onCancel();
|
||||
void navigateTo(Node page, Navigation.NavigationDirection nav);
|
||||
void handleDeferredWizardResult(Map<String, Object> settings, DeferredWizardResult deferredWizardResult);
|
||||
void handleTask(Map<String, Object> settings, Task task);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</RequiredFieldValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%ui.label.password" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="2">
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%login.password" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="2">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
</center>
|
||||
<bottom>
|
||||
<HBox alignment="CENTER">
|
||||
<JFXButton fx:id="btnInstall" onMouseClicked="#onInstall" prefWidth="100" prefHeight="40"
|
||||
buttonType="RAISED" text="%ui.button.install" styleClass="jfx-button-raised"/>
|
||||
<JFXButton fx:id="btnInstall" prefWidth="100" prefHeight="40"
|
||||
buttonType="RAISED" text="%button.install" styleClass="jfx-button-raised"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<bottom>
|
||||
<HBox alignment="CENTER">
|
||||
<JFXButton fx:id="btnInstall" onMouseClicked="#onInstall" prefWidth="100" prefHeight="40"
|
||||
buttonType="RAISED" text="%ui.button.install" styleClass="jfx-button-raised"/>
|
||||
buttonType="RAISED" text="%button.install" styleClass="jfx-button-raised"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
<BorderPane><left><Label text="%modpack.task.install.will" /></left><right><Label fx:id="lblModpackLocation" /></right></BorderPane>
|
||||
<JFXTextField fx:id="txtModpackName" labelFloat="true" promptText="%modpack.enter_name" StackPane.margin="$insets" />
|
||||
<BorderPane><left><Label text="%modpack.name"/></left><right><Label fx:id="lblName" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%ui.label.version"/></left><right><Label fx:id="lblVersion" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%archive.version"/></left><right><Label fx:id="lblVersion" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="Author"/></left><right><Label fx:id="lblAuthor" /></right></BorderPane>
|
||||
<BorderPane>
|
||||
<left><JFXButton fx:id="btnDescription" onMouseClicked="#onDescribe" text="%modpack.wizard.step.3" styleClass="jfx-button" /></left>
|
||||
<right><JFXButton buttonType="RAISED" fx:id="btnInstall" onMouseClicked="#onInstall" text="%ui.button.install" styleClass="jfx-button-raised" /></right>
|
||||
<right><JFXButton buttonType="RAISED" fx:id="btnInstall" onMouseClicked="#onInstall" text="%button.install" styleClass="jfx-button-raised" /></right>
|
||||
</BorderPane>
|
||||
</ComponentList>
|
||||
</VBox>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
type="StackPane">
|
||||
<JFXDialogLayout>
|
||||
<heading>
|
||||
<Label id="content" />
|
||||
<Label fx:id="content" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="textField" />
|
||||
|
||||
@@ -4,13 +4,19 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
|
||||
<?import org.jackhuang.hmcl.ui.construct.TaskListPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<JFXProgressBar fx:id="pgsTasks" StackPane.alignment="TOP_CENTER" />
|
||||
<VBox alignment="CENTER">
|
||||
<VBox alignment="TOP_CENTER" style="-fx-padding: 16px;">
|
||||
<Label fx:id="lblCurrentState" style="-fx-font-size: 20px;" />
|
||||
<Label fx:id="lblSteps" style="-fx-font-size: 14px;" />
|
||||
<TaskListPane fx:id="taskListPane" />
|
||||
</VBox>
|
||||
|
||||
<StackPane pickOnBounds="false" style="-fx-padding: 8px;">
|
||||
<JFXButton StackPane.alignment="BOTTOM_RIGHT" fx:id="btnCancel" text="%button.cancel" />
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</StackPane>
|
||||
<HBox alignment="CENTER_RIGHT" style="-fx-padding: 0 3 0 3;" spacing="3">
|
||||
<JFXButton onMouseClicked="#onTerminateGame" text="%logwindow.terminate_game" />
|
||||
<JFXButton onMouseClicked="#onClear" text="%ui.button.clear" />
|
||||
<JFXButton onMouseClicked="#onClear" text="%button.clear" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
</fx:root>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
type="StackPane">
|
||||
<JFXDialogLayout>
|
||||
<heading>
|
||||
<Label text="%message.info" />
|
||||
<Label fx:id="title" text="%message.info" />
|
||||
</heading>
|
||||
<body>
|
||||
<Label fx:id="content" textAlignment="JUSTIFY" wrapText="true" />
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
<RequiredFieldValidator message="%modpack.not_a_valid_name"/>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXTextField labelFloat="true" promptText="%modpack.author" fx:id="txtModpackAuthor"
|
||||
<JFXTextField labelFloat="true" promptText="%archive.author" fx:id="txtModpackAuthor"
|
||||
StackPane.margin="$insets">
|
||||
<validators>
|
||||
<RequiredFieldValidator/>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXTextField labelFloat="true" promptText="%ui.label.version" fx:id="txtModpackVersion" text="1.0"
|
||||
<JFXTextField labelFloat="true" promptText="%archive.version" fx:id="txtModpackVersion" text="1.0"
|
||||
StackPane.margin="$insets">
|
||||
<validators>
|
||||
<RequiredFieldValidator/>
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
<BorderPane pickOnBounds="false" style="-fx-padding: 20;">
|
||||
<left>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete" prefWidth="100" prefHeight="40"
|
||||
buttonType="RAISED" text="%ui.button.delete" styleClass="jfx-button-raised" />
|
||||
buttonType="RAISED" text="%button.delete" styleClass="jfx-button-raised" />
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnSave" onMouseClicked="#onSave" prefWidth="100" prefHeight="40"
|
||||
buttonType="RAISED" text="%ui.button.save" styleClass="jfx-button-raised"/>
|
||||
buttonType="RAISED" text="%button.save" styleClass="jfx-button-raised"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</fx:root>
|
||||
|
||||
@@ -29,25 +29,13 @@
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30">
|
||||
<tooltip>
|
||||
<Tooltip text="%version.manage.settings" />
|
||||
</tooltip>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnScript" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30">
|
||||
<tooltip>
|
||||
<Tooltip text="%version.launch_script" />
|
||||
</tooltip>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30">
|
||||
<tooltip>
|
||||
<Tooltip text="%version.launch" />
|
||||
</tooltip>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnScript" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
|
||||
<BorderPane> <!-- Icon -->
|
||||
<left>
|
||||
<Label text="settings.icon" />
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label text="%settings.icon" />
|
||||
</VBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox alignment="CENTER_RIGHT" spacing="8">
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
type="StackPane">
|
||||
<JFXDialogLayout>
|
||||
<heading>
|
||||
<Label text="%ui.message.enter_password" />
|
||||
<Label text="%login.enter_password" />
|
||||
</heading>
|
||||
<body>
|
||||
<VBox spacing="15" style="-fx-padding: 15 0 0 0;">
|
||||
<Label fx:id="lblUsername" />
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%ui.label.password" labelFloat="true">
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%login.password" labelFloat="true">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="Input Required!">
|
||||
</RequiredFieldValidator>
|
||||
|
||||
@@ -30,17 +30,16 @@ launch.wrong_javadir=Incorrect Java directory, will reset to default Java direct
|
||||
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
|
||||
|
||||
launch.state.logging_in=Logging In
|
||||
launch.state.generating_launching_codes=Generating Launching Codes
|
||||
launch.state.downloading_libraries=Downloading dependencies
|
||||
launch.state.decompressing_natives=Decompressing natives
|
||||
launch.state.modpack=Preparing for modpack
|
||||
launch.state.dependencies=Decompressing natives
|
||||
launch.state.waiting_launching=Waiting for game launching
|
||||
|
||||
install.no_version=The version is not found.
|
||||
install.no_version_if_intall=The required version is not found, do you wish to install the version automatically?
|
||||
install.failed=Failed to install
|
||||
install.success=Install successfully
|
||||
install.success=Installed successfully
|
||||
install.installer.not_installed=%s not Installed
|
||||
install.version=Version
|
||||
archive.version=Version
|
||||
install.installer.version=%s Version
|
||||
install.time=Time
|
||||
install.release_time=Release Time
|
||||
@@ -81,8 +80,7 @@ minecraft.wrong_path=Wrong Minecraft path, the launcher could not find the path.
|
||||
operation.stopped=The operation was aborted.
|
||||
operation.confirm_stop=Terminate the operations?
|
||||
|
||||
ui.login.password=Password
|
||||
ui.more=More
|
||||
button.more=More
|
||||
|
||||
crash.advice.UnsupportedClassVersionError=Maybe your java is too old, try to update the java.
|
||||
crash.advice.ConcurrentModificationException=Maybe your Java is newer than 1.8.0_11, you could downgrade to Java 7.
|
||||
@@ -106,50 +104,44 @@ crash.error=Minecraft has crashed.
|
||||
crash.main_class_not_found=Main Class is not found, may be your mc has been broken.
|
||||
crash.class_path_wrong=Maybe the launch script is malformed.
|
||||
|
||||
ui.label.newProfileWindow.new_profile_name=New Profile Name:
|
||||
ui.label.newProfileWindow.copy_from=Copy From:
|
||||
ui.newProfileWindow.title=New Config
|
||||
profile.new_name=New Profile Name:
|
||||
profile.copy_from=Copy From:
|
||||
profile.new=New Config
|
||||
|
||||
ui.button.ok=OK
|
||||
ui.button.refresh=Refresh
|
||||
button.refresh=Refresh
|
||||
version.launch=Play
|
||||
version.manage.settings=Settings
|
||||
ui.button.about=About
|
||||
ui.button.others=Others
|
||||
ui.button.logout=Log Out
|
||||
ui.button.download=Download
|
||||
ui.button.retry=Retry
|
||||
ui.button.delete=Delete
|
||||
ui.button.install=Install
|
||||
ui.button.info=Info
|
||||
ui.button.save=Save
|
||||
ui.button.copy=Copy
|
||||
ui.button.clear=Clear
|
||||
ui.button.close=Close
|
||||
ui.button.explore=Explore
|
||||
ui.button.test=Test
|
||||
ui.button.preview=Preview
|
||||
button.about=About
|
||||
button.others=Others
|
||||
button.download=Download
|
||||
button.retry=Retry
|
||||
button.delete=Delete
|
||||
button.install=Install
|
||||
button.info=Info
|
||||
button.save=Save
|
||||
button.copy=Copy
|
||||
button.clear=Clear
|
||||
button.close=Close
|
||||
button.explore=Explore
|
||||
button.test=Test
|
||||
button.preview=Preview
|
||||
button.cancel=Cancel
|
||||
button.ok=OK
|
||||
button.yes=Yes
|
||||
button.no=No
|
||||
|
||||
ui.label.version=Version
|
||||
ui.label.password=Password
|
||||
login.password=Password
|
||||
profile=Profile
|
||||
|
||||
ui.message.first_load=Please enter your name.
|
||||
ui.message.enter_password=Please enter your password.
|
||||
ui.message.launching=Launching...
|
||||
ui.message.making=Generating...
|
||||
login.enter_username=Please enter your name.
|
||||
login.enter_password=Please enter your password.
|
||||
ui.message.downloading=Downloading...
|
||||
ui.message.sure_remove=Sure to remove profile %s?
|
||||
ui.message.update_java=Please upgrade your Java.
|
||||
ui.message.open_jdk=We have found that you started this application using OpenJDK, which will cause so many troubles drawing the UI. We suggest you using Oracle JDK instead.
|
||||
profile.remove=Sure to remove profile %s?
|
||||
launcher.update_java=Please upgrade your Java.
|
||||
launcher.open_jdk=We have found that you started this application using OpenJDK, which will cause so many troubles drawing the UI. We suggest you using Oracle JDK instead.
|
||||
|
||||
ui.label.settings=Settings
|
||||
ui.label.crashing=Hello Minecraft! Launcher has crashed!
|
||||
ui.label.crashing_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
launcher.crash=Hello Minecraft! Launcher has crashed!
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
ui.label.failed_set=Failed to set:
|
||||
|
||||
download=Download
|
||||
@@ -208,7 +200,7 @@ modpack.export_finished=Exporting the modpack finished. See
|
||||
modpack.included_launcher=The modpack is included the launcher, you can publish it directly.
|
||||
modpack.not_included_launcher=You can use the modpack by clicking the "Import Modpack" button.
|
||||
modpack.enter_name=Enter your desired name for this game.
|
||||
modpack.author=Author
|
||||
archive.author=Authors
|
||||
modpack.export=Export Modpack
|
||||
modpack.task.install=Import Modpack
|
||||
modpack.task.install.error=Failed to install the modpack. Maybe the files is incorrect, or a management issue occurred.
|
||||
@@ -276,7 +268,7 @@ advancedsettings.dont_check_game_completeness=Don't check game completeness
|
||||
mainwindow.show_log=Show Logs
|
||||
version.launch_script=Make Launching Script.
|
||||
version.launch_script.failed=Failed to make script.
|
||||
mainwindow.enter_script_name=Enter the script name.
|
||||
version.launch_script.save=Save the launch script
|
||||
version.launch_script.success=Finished script creation, %s.
|
||||
mainwindow.no_version=No version found. Switch to Game Downloads Tab?
|
||||
|
||||
@@ -332,7 +324,6 @@ assets.failed_download=Failed to download assets, may cause no sounds and langua
|
||||
taskwindow.title=Tasks
|
||||
taskwindow.single_progress=Single progress
|
||||
taskwindow.total_progress=Total progress
|
||||
taskwindow.cancel=Cancel
|
||||
taskwindow.no_more_instance=Maybe you opened more than one task window, don't open it again!
|
||||
taskwindow.file_name=Task
|
||||
taskwindow.download_progress=Pgs.
|
||||
@@ -413,3 +404,11 @@ modpack.type.curse=Curse
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.curse.completion=Install relative files to Curse modpack
|
||||
archive.game_version=Game
|
||||
button.edit=Edit
|
||||
extension.sh=Bash shell
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
extension.png=Image file
|
||||
message.success=Tasks succeeded
|
||||
message.doing=Please wait
|
||||
|
||||
@@ -30,9 +30,8 @@ launch.wrong_javadir=错误的Java路径,将自动重置为默认Java路径。
|
||||
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
||||
|
||||
launch.state.logging_in=登录中
|
||||
launch.state.generating_launching_codes=正在生成启动代码
|
||||
launch.state.downloading_libraries=正在下载必要文件
|
||||
launch.state.decompressing_natives=正在释放本地文件
|
||||
launch.state.modpack=正在下载必要文件
|
||||
launch.state.dependencies=正在处理游戏依赖
|
||||
launch.state.waiting_launching=等待游戏启动
|
||||
|
||||
install.no_version=未找到要安装的对应MC版本
|
||||
@@ -40,7 +39,7 @@ install.no_version_if_intall=未找到要安装的对应MC版本,是否自动
|
||||
install.failed=安装失败
|
||||
install.success=安装成功
|
||||
install.installer.not_installed=不安装%s
|
||||
install.version=版本
|
||||
archive.version=版本
|
||||
install.installer.version=%s版本
|
||||
install.time=时间
|
||||
install.release_time=发布时间
|
||||
@@ -81,8 +80,7 @@ minecraft.wrong_path=错误的Minecraft路径,启动器未找到设定的Minec
|
||||
operation.stopped=操作被强行终止
|
||||
operation.confirm_stop=真的要终止操作吗?
|
||||
|
||||
ui.login.password=密码
|
||||
ui.more=更多
|
||||
button.more=更多
|
||||
|
||||
crash.advice.UnsupportedClassVersionError=这可能是因为您的Java版本过于老旧,可以尝试更换最新Java并在版本设置的Java路径中设置.
|
||||
crash.advice.ConcurrentModificationException=这可能是因为您的Java版本高于Java 1.8.0_11导致的,可以尝试卸载Java8安装Java7。
|
||||
@@ -106,49 +104,43 @@ crash.error=您的Minecraft崩溃了。
|
||||
crash.main_class_not_found=找不到主类,可能是您的JSON文件填写错误。无法启动游戏。可以通过下载整合包解决问题。
|
||||
crash.class_path_wrong=解析Class Path时出现错误,此错误本不应该发生。可能是启动脚本错误,请仔细检查启动脚本。
|
||||
|
||||
ui.label.newProfileWindow.new_profile_name=新配置名:
|
||||
ui.label.newProfileWindow.copy_from=复制配置:
|
||||
ui.newProfileWindow.title=新建配置
|
||||
profile.new_name=新配置名:
|
||||
profile.copy_from=复制配置:
|
||||
profile.new=新建配置
|
||||
|
||||
ui.button.ok=确认
|
||||
ui.button.refresh=刷新
|
||||
button.refresh=刷新
|
||||
version.launch=启动游戏
|
||||
version.manage.settings=游戏设置
|
||||
ui.button.about=关于
|
||||
ui.button.others=其他
|
||||
ui.button.logout=登出
|
||||
ui.button.download=下载
|
||||
ui.button.retry=重试
|
||||
ui.button.delete=删除
|
||||
ui.button.install=安装
|
||||
ui.button.info=信息
|
||||
ui.button.save=保存
|
||||
ui.button.copy=复制
|
||||
ui.button.clear=清除
|
||||
ui.button.close=关闭
|
||||
ui.button.explore=浏览
|
||||
ui.button.test=测试
|
||||
ui.button.preview=预览
|
||||
button.about=关于
|
||||
button.others=其他
|
||||
button.download=下载
|
||||
button.retry=重试
|
||||
button.delete=删除
|
||||
button.install=安装
|
||||
button.info=信息
|
||||
button.save=保存
|
||||
button.copy=复制
|
||||
button.clear=清除
|
||||
button.close=关闭
|
||||
button.explore=浏览
|
||||
button.test=测试
|
||||
button.preview=预览
|
||||
button.cancel=取消
|
||||
button.ok=确定
|
||||
button.yes=是
|
||||
button.no=否
|
||||
|
||||
ui.label.version=版本
|
||||
ui.label.password=密码
|
||||
login.password=密码
|
||||
profile=配置
|
||||
|
||||
ui.message.first_load=请输入您的账号
|
||||
ui.message.enter_password=请输入您的密码
|
||||
ui.message.launching=启动中
|
||||
ui.message.making=生成中
|
||||
ui.message.sure_remove=真的要删除配置%s吗?
|
||||
ui.message.update_java=请更新您的Java
|
||||
ui.message.open_jdk=我们发现您正在使用OpenJDK,这会导致很多界面问题,我们建议您更换Oracle JDK。
|
||||
login.enter_username=请输入您的账号
|
||||
login.enter_password=请输入您的密码
|
||||
profile.remove=真的要删除配置%s吗?
|
||||
launcher.update_java=请更新您的Java
|
||||
launcher.open_jdk=我们发现您正在使用OpenJDK,这会导致很多界面问题,我们建议您更换Oracle JDK。
|
||||
|
||||
ui.label.settings=选项
|
||||
ui.label.crashing=Hello Minecraft!遇到了无法处理的错误,请复制下列内容并通过mcbbs、贴吧、Github或Minecraft Forum反馈bug。
|
||||
ui.label.crashing_out_dated=Hello Minecraft! Launcher遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.crash=Hello Minecraft!遇到了无法处理的错误,请复制下列内容并通过mcbbs、贴吧、Github或Minecraft Forum反馈bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
ui.label.failed_set=设置失败:
|
||||
|
||||
download=下载
|
||||
@@ -207,7 +199,7 @@ modpack.export_finished=整合包导出完成,参见
|
||||
modpack.included_launcher=整合包已包含启动器,可直接发布
|
||||
modpack.not_included_launcher=整合包未包含启动器,可使用本软件的导入整合包功能导入整合包
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.author=作者
|
||||
archive.author=作者
|
||||
|
||||
modpack.export=导出整合包
|
||||
modpack.task.install=导入整合包
|
||||
@@ -276,7 +268,7 @@ advancedsettings.dont_check_game_completeness=不检查游戏完整性
|
||||
mainwindow.show_log=查看日志
|
||||
version.launch_script=生成启动脚本
|
||||
version.launch_script.failed=生成启动脚本失败
|
||||
mainwindow.enter_script_name=输入要生成脚本的文件名
|
||||
version.launch_script.save=保存启动脚本
|
||||
version.launch_script.success=启动脚本已生成完毕: %s.
|
||||
mainwindow.no_version=未找到任何版本,是否进入游戏下载?
|
||||
|
||||
@@ -332,7 +324,6 @@ assets.failed_download=下载资源文件失败,可能导致没有中文和声
|
||||
taskwindow.title=任务
|
||||
taskwindow.single_progress=单项进度
|
||||
taskwindow.total_progress=总进度
|
||||
taskwindow.cancel=取消
|
||||
taskwindow.no_more_instance=可能同时打开了多个任务窗口,请不要多次打开!
|
||||
taskwindow.file_name=任务
|
||||
taskwindow.download_progress=进度
|
||||
@@ -413,3 +404,11 @@ modpack.type.curse=Curse
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.curse.completion=下载Curse整合包相关文件
|
||||
archive.game_version=游戏版本
|
||||
button.edit=修改
|
||||
extension.sh=Bash 脚本
|
||||
extension.bat=Windows 脚本
|
||||
extension.mod=模组文件
|
||||
extension.png=图片文件
|
||||
message.success=已完成
|
||||
message.doing=请耐心等待
|
||||
|
||||
@@ -125,8 +125,8 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
|
||||
setResult(installProfile.getVersionInfo()
|
||||
.setInheritsFrom(version.getId())
|
||||
.resolve(provider)
|
||||
.setId(version.getId()).setLogging(Collections.EMPTY_MAP));
|
||||
.resolve(provider).setJar(null)
|
||||
.setId(version.getId()).setLogging(Collections.emptyMap()));
|
||||
|
||||
dependencies.add(new GameLibrariesTask(dependencyManager, installProfile.getVersionInfo()));
|
||||
}
|
||||
|
||||
@@ -71,9 +71,13 @@ public final class GameAssetDownloadTask extends Task {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
public void execute() throws Exception {
|
||||
int size = refreshTask.getResult().size();
|
||||
int downloaded = 0;
|
||||
for (Map.Entry<File, AssetObject> entry : refreshTask.getResult()) {
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
|
||||
File file = entry.getKey();
|
||||
AssetObject assetObject = entry.getValue();
|
||||
String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation();
|
||||
@@ -84,7 +88,6 @@ public final class GameAssetDownloadTask extends Task {
|
||||
if (file.isDirectory())
|
||||
continue;
|
||||
boolean flag = true;
|
||||
int downloaded = 0;
|
||||
try {
|
||||
// check the checksum of file to ensure that the file is not need to re-download.
|
||||
if (file.exists()) {
|
||||
|
||||
@@ -79,6 +79,9 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
|
||||
int progress = 0;
|
||||
if (index != null)
|
||||
for (AssetObject assetObject : index.getObjects().values()) {
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
|
||||
res.add(new Pair<>(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject));
|
||||
updateProgress(++progress, index.getObjects().size());
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
@@ -69,6 +71,10 @@ public interface GameRepository extends VersionProvider {
|
||||
*/
|
||||
void refreshVersions();
|
||||
|
||||
default Task refreshVersionsAsync() {
|
||||
return Task.of(this::refreshVersions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root folder of specific version.
|
||||
* The root folders the versions must be unique.
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.launch;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.SimpleTaskResult;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
@@ -274,23 +276,18 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
public final TaskResult<ManagedProcess> launchAsync() {
|
||||
return new TaskResult<ManagedProcess>() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return LAUNCH_ASYNC_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setResult(launch());
|
||||
}
|
||||
};
|
||||
return new SimpleTaskResult<>(LAUNCH_ASYNC_ID, this::launch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File makeLaunchScript(String file) throws IOException {
|
||||
public void makeLaunchScript(File scriptFile) throws IOException {
|
||||
boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS;
|
||||
File scriptFile = new File(file + (isWindows ? ".bat" : ".sh"));
|
||||
|
||||
if (isWindows && !FileUtils.getExtension(scriptFile).equals("bat"))
|
||||
throw new IOException("The extension of " + scriptFile + " is not 'bat' in Windows");
|
||||
else if (!isWindows && !FileUtils.getExtension(scriptFile).equals("sh"))
|
||||
throw new IOException("The extension of " + scriptFile + " is not 'sh' in macOS/Linux");
|
||||
|
||||
if (!FileUtils.makeFile(scriptFile))
|
||||
throw new IOException("Script file: " + scriptFile + " cannot be created.");
|
||||
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(scriptFile)))) {
|
||||
@@ -310,7 +307,10 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
if (!scriptFile.setExecutable(true))
|
||||
throw new IOException("Cannot make script file '" + scriptFile + "' executable.");
|
||||
return scriptFile;
|
||||
}
|
||||
|
||||
public final Task makeLaunchScriptAsync(File file) {
|
||||
return Task.of(() -> makeLaunchScript(file));
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess) {
|
||||
@@ -367,4 +367,5 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
public static final String LAUNCH_ASYNC_ID = "process";
|
||||
public static final String LAUNCH_SCRIPT_ASYNC_ID = "script";
|
||||
}
|
||||
|
||||
@@ -61,10 +61,9 @@ public abstract class Launcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file The file path without extension
|
||||
* @return the actual file with extension sh or bat.
|
||||
* @param file the file path.
|
||||
*/
|
||||
public abstract File makeLaunchScript(String file) throws IOException;
|
||||
public abstract void makeLaunchScript(File file) throws IOException;
|
||||
|
||||
public abstract ManagedProcess launch() throws IOException, InterruptedException;
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ public class FileDownloadTask extends Task {
|
||||
if (temp != null)
|
||||
temp.delete();
|
||||
Logging.LOG.log(Level.WARNING, "Unable to download file " + currentURL, e);
|
||||
exception = e;
|
||||
} finally {
|
||||
closeFiles();
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ class SchedulerImpl extends Scheduler {
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
Thread.interrupted(); // clear the `interrupted` flag to prevent from interrupting EventDispatch thread.
|
||||
});
|
||||
|
||||
return new Future<Void>() {
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Schedulers {
|
||||
|
||||
@@ -33,7 +33,10 @@ public final class Schedulers {
|
||||
private static synchronized ExecutorService getCachedExecutorService() {
|
||||
if (CACHED_EXECUTOR == null)
|
||||
CACHED_EXECUTOR = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
||||
60, TimeUnit.SECONDS, new SynchronousQueue<>());
|
||||
60, TimeUnit.SECONDS, new SynchronousQueue<>(), runnable -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
|
||||
return thread;
|
||||
});
|
||||
|
||||
return CACHED_EXECUTOR;
|
||||
}
|
||||
@@ -42,8 +45,8 @@ public final class Schedulers {
|
||||
|
||||
private static synchronized ExecutorService getIOExecutorService() {
|
||||
if (IO_EXECUTOR == null)
|
||||
IO_EXECUTOR = Executors.newFixedThreadPool(6, r -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||
IO_EXECUTOR = Executors.newFixedThreadPool(6, runnable -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
@@ -55,8 +58,8 @@ public final class Schedulers {
|
||||
|
||||
private static synchronized ExecutorService getSingleExecutorService() {
|
||||
if (SINGLE_EXECUTOR == null)
|
||||
SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(r -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||
SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(runnable -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
|
||||
@@ -29,15 +29,18 @@ class SimpleTask extends Task {
|
||||
private final ExceptionalConsumer<AutoTypingMap<String>, ?> consumer;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public SimpleTask(ExceptionalConsumer<AutoTypingMap<String>, ?> consumer) {
|
||||
this(consumer, Schedulers.defaultScheduler());
|
||||
public SimpleTask(String name, ExceptionalConsumer<AutoTypingMap<String>, ?> consumer) {
|
||||
this(name, consumer, Schedulers.defaultScheduler());
|
||||
}
|
||||
|
||||
public SimpleTask(ExceptionalConsumer<AutoTypingMap<String>, ?> consumer, Scheduler scheduler) {
|
||||
public SimpleTask(String name, ExceptionalConsumer<AutoTypingMap<String>, ?> consumer, Scheduler scheduler) {
|
||||
this.consumer = consumer;
|
||||
this.scheduler = scheduler;
|
||||
|
||||
setName(consumer.toString());
|
||||
if (name == null)
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
else
|
||||
setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
*
|
||||
* 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
|
||||
@@ -15,26 +15,26 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class DeferredWizardResult {
|
||||
private final boolean canAbort;
|
||||
public final class SimpleTaskResult<V> extends TaskResult<V> {
|
||||
private final String id;
|
||||
private final ExceptionalSupplier<V, ?> supplier;
|
||||
|
||||
public DeferredWizardResult() {
|
||||
this(false);
|
||||
public SimpleTaskResult(String id, ExceptionalSupplier<V, ?> supplier) {
|
||||
this.id = id;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public DeferredWizardResult(boolean canAbort) {
|
||||
this.canAbort = canAbort;
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setResult(supplier.get());
|
||||
}
|
||||
|
||||
public abstract void start(Map<String, Object> settings, ResultProgressHandle progressHandle);
|
||||
|
||||
public void abort() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -87,8 +87,9 @@ public abstract class Task {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
public Task setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
private AutoTypingMap<String> variables = null;
|
||||
@@ -131,7 +132,7 @@ public abstract class Task {
|
||||
|
||||
private long lastTime = Long.MIN_VALUE;
|
||||
private final AtomicReference<Double> progressUpdate = new AtomicReference<>();
|
||||
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", 0);
|
||||
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1);
|
||||
|
||||
public ReadOnlyDoubleProperty progressProperty() {
|
||||
return progress.getReadOnlyProperty();
|
||||
@@ -244,20 +245,36 @@ public abstract class Task {
|
||||
});
|
||||
}
|
||||
|
||||
public static Task of(String name, ExceptionalRunnable<?> runnable) {
|
||||
return of(name, s -> runnable.run());
|
||||
}
|
||||
|
||||
public static Task of(ExceptionalRunnable<?> runnable) {
|
||||
return of(s -> runnable.run());
|
||||
}
|
||||
|
||||
public static Task of(String name, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return of(name, Schedulers.defaultScheduler(), closure);
|
||||
}
|
||||
|
||||
public static Task of(ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return of(Schedulers.defaultScheduler(), closure);
|
||||
}
|
||||
|
||||
public static Task of(String name, Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return new SimpleTask(name, closure, scheduler);
|
||||
}
|
||||
|
||||
public static Task of(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return new SimpleTask(closure, scheduler);
|
||||
return of(null, scheduler, closure);
|
||||
}
|
||||
|
||||
public static Task of(String name, Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return new SimpleTask(name, i -> closure.run(), scheduler);
|
||||
}
|
||||
|
||||
public static Task of(Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return new SimpleTask(i -> closure.run(), scheduler);
|
||||
return of(null, scheduler, closure);
|
||||
}
|
||||
|
||||
public static <V> TaskResult<V> ofResult(String id, Callable<V> callable) {
|
||||
|
||||
@@ -23,10 +23,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
@@ -66,17 +63,22 @@ public final class TaskExecutor {
|
||||
this.scheduler = Objects.requireNonNull(scheduler);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
public TaskExecutor start() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
workerQueue.add(scheduler.schedule(() -> {
|
||||
if (!executeTasks(Collections.singleton(firstTask)))
|
||||
if (executeTasks(Collections.singleton(firstTask)))
|
||||
taskListeners.forEach(TaskListener::onSucceed);
|
||||
else
|
||||
taskListeners.forEach(it -> {
|
||||
it.onTerminate();
|
||||
it.onTerminate(variables);
|
||||
});
|
||||
}));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean test() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
Future<?> future = scheduler.schedule(() -> {
|
||||
if (!executeTasks(Collections.singleton(firstTask))) {
|
||||
@@ -85,7 +87,8 @@ public final class TaskExecutor {
|
||||
it.onTerminate(variables);
|
||||
});
|
||||
flag.set(false);
|
||||
}
|
||||
} else
|
||||
taskListeners.forEach(TaskListener::onSucceed);
|
||||
});
|
||||
workerQueue.add(future);
|
||||
Lang.invoke(() -> future.get());
|
||||
@@ -128,7 +131,11 @@ public final class TaskExecutor {
|
||||
if (canceled)
|
||||
return false;
|
||||
|
||||
latch.await();
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
return success.get() && !canceled;
|
||||
}
|
||||
|
||||
@@ -156,8 +163,10 @@ public final class TaskExecutor {
|
||||
variables.set(taskResult.getId(), taskResult.getResult());
|
||||
}
|
||||
|
||||
if (!executeTasks(task.getDependencies()) && task.isRelyingOnDependencies())
|
||||
throw new IllegalStateException("Subtasks failed for " + task.getName());
|
||||
if (!executeTasks(task.getDependencies()) && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
flag = true;
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
@@ -169,7 +178,7 @@ public final class TaskExecutor {
|
||||
} catch (InterruptedException e) {
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
lastException = e;
|
||||
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName(), e);
|
||||
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
}
|
||||
@@ -178,12 +187,10 @@ public final class TaskExecutor {
|
||||
} catch (RejectedExecutionException e) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
lastException = e;
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
}
|
||||
lastException = e;
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
} finally {
|
||||
task.setVariables(null);
|
||||
}
|
||||
@@ -209,7 +216,8 @@ public final class TaskExecutor {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.currentThread().setName(task.getName());
|
||||
if (Thread.currentThread().getName().contains("pool"))
|
||||
Thread.currentThread().setName(task.getName());
|
||||
if (!executeTask(task))
|
||||
success.set(false);
|
||||
} finally {
|
||||
|
||||
@@ -27,6 +27,9 @@ import java.util.EventListener;
|
||||
*/
|
||||
public abstract class TaskListener implements EventListener {
|
||||
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
public void onReady(Task task) {
|
||||
}
|
||||
|
||||
@@ -42,6 +45,9 @@ public abstract class TaskListener implements EventListener {
|
||||
public void onTerminate(AutoTypingMap<String> variables) {
|
||||
}
|
||||
|
||||
public void onSucceed() {
|
||||
}
|
||||
|
||||
public static class DefaultTaskListener extends TaskListener {
|
||||
|
||||
public static final DefaultTaskListener INSTANCE = new DefaultTaskListener();
|
||||
|
||||
@@ -165,7 +165,7 @@ public final class JavaVersion implements Serializable {
|
||||
return JAVAS;
|
||||
}
|
||||
|
||||
public static synchronized void initialize() throws IOException, InterruptedException {
|
||||
public static synchronized void initialize() throws IOException {
|
||||
if (JAVAS != null)
|
||||
throw new IllegalStateException("JavaVersions have already been initialized.");
|
||||
HashMap<String, JavaVersion> temp = new HashMap<>();
|
||||
|
||||
@@ -219,16 +219,21 @@ public final class Lang {
|
||||
|
||||
public static <T> List<T> merge(Collection<T> a, Collection<T> b) {
|
||||
LinkedList<T> result = new LinkedList<>();
|
||||
result.addAll(a);
|
||||
result.addAll(b);
|
||||
if (a != null)
|
||||
result.addAll(a);
|
||||
if (b != null)
|
||||
result.addAll(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> merge(Collection<T> a, Collection<T> b, Collection<T> c) {
|
||||
LinkedList<T> result = new LinkedList<>();
|
||||
result.addAll(a);
|
||||
result.addAll(b);
|
||||
result.addAll(c);
|
||||
if (a != null)
|
||||
result.addAll(a);
|
||||
if (b != null)
|
||||
result.addAll(b);
|
||||
if (c != null)
|
||||
result.addAll(c);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -62,6 +64,19 @@ public final class StringUtils {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable throwable) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
throwable.printStackTrace(new PrintStream(stream));
|
||||
return stream.toString();
|
||||
}
|
||||
|
||||
public static String getStackTrace(StackTraceElement[] elements) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (StackTraceElement element : elements)
|
||||
builder.append("\tat ").append(element).append(OperatingSystem.LINE_SEPARATOR);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static boolean isBlank(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user