TransitionHandler update

This commit is contained in:
huangyuhui
2018-01-25 22:41:15 +08:00
parent 056c0901f2
commit 12fa94627d
66 changed files with 646 additions and 522 deletions

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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()));
}

View File

@@ -77,4 +77,8 @@ public class AdvancedListBox extends ScrollPane {
public void setSpacing(double spacing) {
container.setSpacing(spacing);
}
public void clear() {
container.getChildren().clear();
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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();
};

View File

@@ -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() {

View File

@@ -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 -> {

View File

@@ -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))

View File

@@ -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);

View File

@@ -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(""));

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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);
});
}
}

View File

@@ -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);
}
}

View File

@@ -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();
});
}
}

View File

@@ -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());

View File

@@ -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";
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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";

View 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();

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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
*/

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -10,7 +10,7 @@
type="StackPane">
<JFXDialogLayout>
<heading>
<Label id="content" />
<Label fx:id="content" />
</heading>
<body>
<JFXTextField fx:id="textField" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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

View File

@@ -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=请耐心等待

View File

@@ -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()));
}

View File

@@ -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()) {

View File

@@ -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());
}

View File

@@ -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.

View File

@@ -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";
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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>() {

View File

@@ -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;
});

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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<>();

View File

@@ -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;
}

View File

@@ -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();
}