Update Readme.md for HMCLCore introduction
This commit is contained in:
98
HMCL/src/main/java/org/jackhuang/hmcl/Main.java
Normal file
98
HMCL/src/main/java/org/jackhuang/hmcl/Main.java
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class Main extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
|
||||
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
|
||||
Platform.setImplicitExit(false);
|
||||
Controllers.initialize(primaryStage);
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.setScene(Controllers.getScene());
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
|
||||
launch(args);
|
||||
}
|
||||
|
||||
public static void stopApplication() {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
stopWithoutPlatform();
|
||||
Platform.exit();
|
||||
});
|
||||
}
|
||||
|
||||
public static void stopWithoutPlatform() {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
Controllers.getStage().close();
|
||||
Schedulers.shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
public static String i18n(String key) {
|
||||
try {
|
||||
return RESOURCE_BUNDLE.getString(key);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public static File getWorkingDirectory(String folder) {
|
||||
String home = System.getProperty("user.home", ".");
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX:
|
||||
return new File(home, "." + folder + "/");
|
||||
case WINDOWS:
|
||||
String appdata = System.getenv("APPDATA");
|
||||
return new File(Lang.nonNull(appdata, home), "." + folder + "/");
|
||||
case OSX:
|
||||
return new File(home, "Library/Application Support/" + folder);
|
||||
default:
|
||||
return new File(home, folder + "/");
|
||||
}
|
||||
}
|
||||
|
||||
public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft");
|
||||
|
||||
public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final File APPDATA = getWorkingDirectory("hmcl");
|
||||
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.task.Scheduler;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -80,9 +80,9 @@ public final class AccountHelper {
|
||||
}
|
||||
|
||||
public static Image getSkin(YggdrasilAccount account, double scaleRatio) {
|
||||
if (account.getSelectedProfile() == null) return FXUtilsKt.DEFAULT_ICON;
|
||||
if (account.getSelectedProfile() == null) return FXUtils.DEFAULT_ICON;
|
||||
String name = account.getSelectedProfile().getName();
|
||||
if (name == null) return FXUtilsKt.DEFAULT_ICON;
|
||||
if (name == null) return FXUtils.DEFAULT_ICON;
|
||||
File file = getSkinFile(name);
|
||||
if (file.exists()) {
|
||||
Image original = new Image("file:" + file.getAbsolutePath());
|
||||
@@ -91,7 +91,7 @@ public final class AccountHelper {
|
||||
original.getHeight() * scaleRatio,
|
||||
false, false);
|
||||
}
|
||||
return FXUtilsKt.DEFAULT_ICON;
|
||||
return FXUtils.DEFAULT_ICON;
|
||||
}
|
||||
|
||||
public static Rectangle2D getViewport(double scaleRatio) {
|
||||
@@ -128,7 +128,7 @@ public final class AccountHelper {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (account.canLogIn() && (account.getSelectedProfile() == null || refresh))
|
||||
DialogController.INSTANCE.logIn(account);
|
||||
DialogController.logIn(account);
|
||||
|
||||
GameProfile profile = account.getSelectedProfile();
|
||||
if (profile == null) return;
|
||||
|
||||
@@ -26,32 +26,28 @@ import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task {
|
||||
private final File zipFile;
|
||||
private final String version;
|
||||
private final String id;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String id) throws IOException {
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String id) {
|
||||
dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
this.zipFile = zipFile;
|
||||
this.version = id;
|
||||
this.id = id;
|
||||
|
||||
if (repository.hasVersion(id))
|
||||
throw new IllegalArgumentException("Version " + id + " already exists");
|
||||
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = Constants.GSON.fromJson(json, Version.class).setJar(null);
|
||||
dependents.add(dependency.gameBuilder().name(id).gameVersion(modpack.getGameVersion()).buildAsync());
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) repository.removeVersionFromDisk(id);
|
||||
@@ -70,7 +66,11 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
CompressingUtils.unzip(zipFile, repository.getRunDirectory(version),
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = Constants.GSON.fromJson(json, Version.class).setJar(null);
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
|
||||
CompressingUtils.unzip(zipFile, repository.getRunDirectory(id),
|
||||
"minecraft/", it -> !Objects.equals(it, "minecraft/pack.json"), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,31 +66,31 @@ public final class LauncherHelper {
|
||||
Version version = repository.getVersion(selectedVersion);
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
|
||||
Controllers.INSTANCE.dialog(launchingStepsPane);
|
||||
TaskExecutor executor = Task.of(v -> emitStatus(LoadingState.DEPENDENCIES), Schedulers.javafx())
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(dependencyManager.checkGameCompletionAsync(version))
|
||||
.then(Task.of(v -> emitStatus(LoadingState.MODS), Schedulers.javafx()))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(new CurseCompletionTask(dependencyManager, selectedVersion))
|
||||
.then(Task.of(v -> emitStatus(LoadingState.LOGIN), Schedulers.javafx()))
|
||||
.then(Task.of(v -> {
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGIN)))
|
||||
.then(Task.of(variables -> {
|
||||
try {
|
||||
v.set("account", account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy()));
|
||||
variables.set("account", account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy()));
|
||||
} catch (AuthenticationException e) {
|
||||
v.set("account", DialogController.INSTANCE.logIn(account));
|
||||
JFXUtilities.runInFX(() -> Controllers.INSTANCE.dialog(launchingStepsPane));
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane));
|
||||
}
|
||||
}))
|
||||
.then(Task.of(v -> emitStatus(LoadingState.LAUNCHING), Schedulers.javafx()))
|
||||
.then(Task.of(v -> {
|
||||
v.set("launcher", new HMCLGameLauncher(
|
||||
repository, selectedVersion, v.get("account"), setting.toLaunchOptions(profile.getGameDir()), new HMCLProcessListener(v.get("account"), setting)
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
|
||||
.then(Task.of(variables -> {
|
||||
variables.set("launcher", new HMCLGameLauncher(
|
||||
repository, selectedVersion, variables.get("account"), setting.toLaunchOptions(profile.getGameDir()), new HMCLProcessListener(variables.get("account"), setting)
|
||||
));
|
||||
}))
|
||||
.then(v -> v.<DefaultLauncher>get("launcher").launchAsync())
|
||||
.then(Task.of(v -> {
|
||||
PROCESSES.add(v.get(DefaultLauncher.LAUNCH_ASYNC_ID));
|
||||
.then(variables -> variables.<DefaultLauncher>get("launcher").launchAsync())
|
||||
.then(Task.of(variables -> {
|
||||
PROCESSES.add(variables.get(DefaultLauncher.LAUNCH_ASYNC_ID));
|
||||
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
|
||||
Main.Companion.stop();
|
||||
Main.stopApplication();
|
||||
}))
|
||||
.executor();
|
||||
|
||||
@@ -106,7 +106,7 @@ public final class LauncherHelper {
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
Platform.runLater(Controllers.INSTANCE::closeDialog);
|
||||
Platform.runLater(Controllers::closeDialog);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ public final class LauncherHelper {
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.INSTANCE.closeDialog();
|
||||
Controllers.closeDialog();
|
||||
|
||||
launchingStepsPane.setCurrentState(state.toString());
|
||||
launchingStepsPane.setSteps((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
@@ -131,7 +131,7 @@ public final class LauncherHelper {
|
||||
private void checkExit(LauncherVisibility v) {
|
||||
switch (v) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(Controllers.INSTANCE.getStage()::show);
|
||||
Platform.runLater(Controllers.getStage()::show);
|
||||
break;
|
||||
case KEEP:
|
||||
// No operations here
|
||||
@@ -143,7 +143,7 @@ public final class LauncherHelper {
|
||||
// Shut down the platform when user closed log window.
|
||||
Platform.setImplicitExit(true);
|
||||
// If we use Main.stop(), log window will be halt immediately.
|
||||
Main.Companion.stopWithoutJavaFXPlatform();
|
||||
Main.stopWithoutPlatform();
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -215,7 +215,7 @@ public final class LauncherHelper {
|
||||
switch (visibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.INSTANCE.getStage().hide();
|
||||
Controllers.getStage().hide();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
@@ -226,7 +226,7 @@ public final class LauncherHelper {
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.INSTANCE.getStage().close();
|
||||
Controllers.getStage().close();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -34,7 +34,7 @@ public final class Config {
|
||||
private String backgroundImage = null;
|
||||
|
||||
@SerializedName("commonpath")
|
||||
private String commonDirectory = Main.getMinecraftDirectory().getAbsolutePath();
|
||||
private String commonDirectory = Main.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
private int proxyType = 0;
|
||||
|
||||
@@ -130,7 +130,7 @@ public class Settings {
|
||||
else {
|
||||
Logging.LOG.config("No settings file here, may be first loading.");
|
||||
if (!c.getConfigurations().containsKey(HOME_PROFILE))
|
||||
c.getConfigurations().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.getMinecraftDirectory()));
|
||||
c.getConfigurations().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.MINECRAFT_DIRECTORY));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
143
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java
Normal file
143
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
|
||||
public final class AccountItem extends StackPane {
|
||||
|
||||
private final Account account;
|
||||
|
||||
@FXML
|
||||
private Pane icon;
|
||||
@FXML private VBox content;
|
||||
@FXML private StackPane header;
|
||||
@FXML private StackPane body;
|
||||
@FXML private JFXButton btnDelete;
|
||||
@FXML private JFXButton btnRefresh;
|
||||
@FXML private Label lblUser;
|
||||
@FXML private JFXRadioButton chkSelected;
|
||||
@FXML private Label lblType;
|
||||
@FXML private JFXProgressBar pgsSkin;
|
||||
@FXML private ImageView portraitView;
|
||||
@FXML private HBox buttonPane;
|
||||
|
||||
public AccountItem(int i, Account account, ToggleGroup toggleGroup) {
|
||||
this.account = account;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-item.fxml");
|
||||
|
||||
FXUtils.limitWidth(this, 160);
|
||||
FXUtils.limitHeight(this, 156);
|
||||
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0));
|
||||
|
||||
chkSelected.setToggleGroup(toggleGroup);
|
||||
btnDelete.setGraphic(SVG.delete("black", 15, 15));
|
||||
btnRefresh.setGraphic(SVG.refresh("black", 15, 15));
|
||||
|
||||
// create content
|
||||
String headerColor = getDefaultColor(i % 12);
|
||||
header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor);
|
||||
|
||||
// create image view
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 32.0, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
|
||||
chkSelected.getProperties().put("account", account);
|
||||
chkSelected.setSelected(Settings.INSTANCE.getSelectedAccount() == account);
|
||||
lblUser.setText(account.getUsername());
|
||||
lblType.setText(AccountsPage.accountType(account));
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
btnRefresh.setOnMouseClicked(e -> {
|
||||
pgsSkin.setVisible(true);
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), this::loadSkin);
|
||||
});
|
||||
AccountHelper.loadSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), this::loadSkin);
|
||||
}
|
||||
|
||||
if (account instanceof OfflineAccount) { // Offline Account cannot be refreshed,
|
||||
buttonPane.getChildren().remove(btnRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSkin() {
|
||||
if (!(account instanceof YggdrasilAccount))
|
||||
return;
|
||||
|
||||
pgsSkin.setVisible(false);
|
||||
portraitView.setViewport(AccountHelper.getViewport(4));
|
||||
portraitView.setImage(AccountHelper.getSkin((YggdrasilAccount) account, 4));
|
||||
FXUtils.limitSize(portraitView, 32, 32);
|
||||
}
|
||||
|
||||
private String getDefaultColor(int i) {
|
||||
switch (i) {
|
||||
case 0: return "#8F3F7E";
|
||||
case 1: return "#B5305F";
|
||||
case 2: return "#CE584A";
|
||||
case 3: return "#DB8D5C";
|
||||
case 4: return "#DA854E";
|
||||
case 5: return "#E9AB44";
|
||||
case 6: return "#FEE435";
|
||||
case 7: return "#99C286";
|
||||
case 8: return "#01A05E";
|
||||
case 9: return "#4A8895";
|
||||
case 10: return "#16669B";
|
||||
case 11: return "#2F65A5";
|
||||
case 12: return "#4E6A9C";
|
||||
default: return "#FFFFFF";
|
||||
}
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
chkSelected.setSelected(selected);
|
||||
}
|
||||
|
||||
public void setOnDeleteButtonMouseClicked(EventHandler<? super MouseEvent> eventHandler) {
|
||||
btnDelete.setOnMouseClicked(eventHandler);
|
||||
}
|
||||
}
|
||||
180
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java
Normal file
180
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.game.HMCLMultiCharacterSelector;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", "Accounts");
|
||||
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
@FXML private JFXMasonryPane masonryPane;
|
||||
@FXML private JFXDialog dialog;
|
||||
@FXML private JFXTextField txtUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
|
||||
|
||||
getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
FXUtils.setValidateWhileTextChanged(txtUsername);
|
||||
FXUtils.setValidateWhileTextChanged(txtPassword);
|
||||
|
||||
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
|
||||
txtPassword.setVisible(newValue.intValue() != 0);
|
||||
});
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
txtPassword.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.setOnAction(e -> onCreationAccept());
|
||||
|
||||
FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), account -> {
|
||||
for (Node node : masonryPane.getChildren())
|
||||
if (node instanceof AccountItem)
|
||||
((AccountItem) node).setSelected(account == ((AccountItem) node).getAccount());
|
||||
});
|
||||
|
||||
loadAccounts();
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
}
|
||||
|
||||
public void loadAccounts() {
|
||||
List<Node> children = new LinkedList<>();
|
||||
int i = 0;
|
||||
ToggleGroup group = new ToggleGroup();
|
||||
for (Map.Entry<String, Account> entry : Settings.INSTANCE.getAccounts().entrySet()) {
|
||||
children.add(buildNode(++i, entry.getValue(), group));
|
||||
}
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue != null)
|
||||
Settings.INSTANCE.setSelectedAccount((Account) newValue.getProperties().get("account"));
|
||||
});
|
||||
FXUtils.resetChildren(masonryPane, children);
|
||||
Platform.runLater(() -> {
|
||||
masonryPane.requestLayout();
|
||||
scrollPane.requestLayout();
|
||||
});
|
||||
}
|
||||
|
||||
private Node buildNode(int i, Account account, ToggleGroup group) {
|
||||
AccountItem item = new AccountItem(i, account, group);
|
||||
item.setOnDeleteButtonMouseClicked(e -> {
|
||||
Settings.INSTANCE.deleteAccount(account.getUsername());
|
||||
Platform.runLater(this::loadAccounts);
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
public void addNewAccount() {
|
||||
txtUsername.setText("");
|
||||
txtPassword.setText("");
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void onCreationAccept() {
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult("create_account", () -> {
|
||||
try {
|
||||
Account account;
|
||||
switch (type) {
|
||||
case 0: account = OfflineAccountFactory.INSTANCE.fromUsername(username); break;
|
||||
case 1: account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password); break;
|
||||
default: throw new Error();
|
||||
}
|
||||
|
||||
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy());
|
||||
return account;
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
}).subscribe(Schedulers.javafx(), variables -> {
|
||||
Object account = variables.get("create_account");
|
||||
if (account instanceof Account) {
|
||||
Settings.INSTANCE.addAccount((Account) account);
|
||||
dialog.close();
|
||||
loadAccounts();
|
||||
} else if (account instanceof InvalidCredentialsException) {
|
||||
lblCreationWarning.setText(Main.i18n("login.wrong_password"));
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(((Exception) account).getLocalizedMessage());
|
||||
}
|
||||
progressBar.setVisible(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void onCreationCancel() {
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return Main.i18n("login.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount) return Main.i18n("login.methods.yggdrasil");
|
||||
else throw new Error(Main.i18n("login.methods.no_method") + ": " + account);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class AdvancedListBox extends ScrollPane {
|
||||
private final VBox container = new VBox();
|
||||
|
||||
{
|
||||
setContent(container);
|
||||
|
||||
FXUtils.smoothScrolling(this);
|
||||
|
||||
setFitToHeight(true);
|
||||
setFitToWidth(true);
|
||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
|
||||
container.setSpacing(5);
|
||||
container.getStyleClass().add("advanced-list-box-content");
|
||||
}
|
||||
|
||||
public AdvancedListBox add(Node child) {
|
||||
if (child instanceof Pane)
|
||||
container.getChildren().add(child);
|
||||
else {
|
||||
StackPane pane = new StackPane();
|
||||
pane.getStyleClass().add("advanced-list-box-item");
|
||||
pane.getChildren().setAll(child);
|
||||
container.getChildren().add(pane);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedListBox startCategory(String category) {
|
||||
return add(new ClassTitle(category));
|
||||
}
|
||||
}
|
||||
113
HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
Normal file
113
HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
|
||||
public final class Controllers {
|
||||
|
||||
private static Scene scene;
|
||||
private static Stage stage;
|
||||
private static MainPage mainPage = new MainPage();
|
||||
private static SettingsPage settingsPage = null;
|
||||
private static VersionPage versionPage = null;
|
||||
private static LeftPaneController leftPaneController;
|
||||
private static Decorator decorator;
|
||||
|
||||
public static Scene getScene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
public static Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static SettingsPage getSettingsPage() {
|
||||
if (settingsPage == null)
|
||||
settingsPage = new SettingsPage();
|
||||
return settingsPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static VersionPage getVersionPage() {
|
||||
if (versionPage == null)
|
||||
versionPage = new VersionPage();
|
||||
return versionPage;
|
||||
}
|
||||
|
||||
public static Decorator getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
public static MainPage getMainPage() {
|
||||
return mainPage;
|
||||
}
|
||||
|
||||
public static LeftPaneController getLeftPaneController() {
|
||||
return leftPaneController;
|
||||
}
|
||||
|
||||
public static void initialize(Stage stage) {
|
||||
Controllers.stage = stage;
|
||||
|
||||
decorator = new Decorator(stage, mainPage, Main.TITLE, false, true);
|
||||
decorator.showPage(null);
|
||||
leftPaneController = new LeftPaneController(decorator.getLeftPane());
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
Task.of(JavaVersion::initialize).start();
|
||||
|
||||
decorator.setCustomMaximize(false);
|
||||
|
||||
scene = new Scene(decorator, 804, 521);
|
||||
scene.getStylesheets().addAll(FXUtils.STYLESHEETS);
|
||||
stage.setMinWidth(800);
|
||||
stage.setMaxWidth(800);
|
||||
stage.setMinHeight(480);
|
||||
stage.setMaxHeight(480);
|
||||
|
||||
stage.getIcons().add(new Image("/assets/img/icon.png"));
|
||||
stage.setTitle(Main.TITLE);
|
||||
}
|
||||
|
||||
public static JFXDialog dialog(Region content) {
|
||||
return decorator.showDialog(content);
|
||||
}
|
||||
|
||||
public static void dialog(String text) {
|
||||
dialog(new MessageDialogPane(text, decorator.getDialog()));
|
||||
}
|
||||
|
||||
public static void closeDialog() {
|
||||
decorator.getDialog().close();
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
decorator.showPage(node);
|
||||
}
|
||||
}
|
||||
519
HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java
Normal file
519
HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXDrawer;
|
||||
import com.jfoenix.controls.JFXHamburger;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.BoundingBox;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
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.wizard.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public final class Decorator extends StackPane implements AbstractWizardDisplayer {
|
||||
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),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
private static final SVGGlyph resizeMin = Lang.apply(new SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>(Main::stopApplication);
|
||||
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
|
||||
|
||||
private final Stage primaryStage;
|
||||
private final Node mainPage;
|
||||
private final boolean max, min;
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging, dialogShown, maximized;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
private TransitionHandler animationHandler;
|
||||
|
||||
@FXML
|
||||
private StackPane contentPlaceHolder;
|
||||
@FXML
|
||||
private StackPane drawerWrapper;
|
||||
@FXML
|
||||
private BorderPane titleContainer;
|
||||
@FXML
|
||||
private BorderPane leftRootPane;
|
||||
@FXML
|
||||
private HBox buttonsContainer;
|
||||
@FXML
|
||||
private JFXButton backNavButton;
|
||||
@FXML
|
||||
private JFXButton refreshNavButton;
|
||||
@FXML
|
||||
private JFXButton closeNavButton;
|
||||
@FXML
|
||||
private JFXButton refreshMenuButton;
|
||||
@FXML
|
||||
private JFXButton addMenuButton;
|
||||
@FXML
|
||||
private Label titleLabel;
|
||||
@FXML
|
||||
private Label lblTitle;
|
||||
@FXML
|
||||
private AdvancedListBox leftPane;
|
||||
@FXML
|
||||
private JFXDrawer drawer;
|
||||
@FXML
|
||||
private StackPane titleBurgerContainer;
|
||||
@FXML
|
||||
private JFXHamburger titleBurger;
|
||||
@FXML
|
||||
private JFXDialog dialog;
|
||||
@FXML
|
||||
private JFXButton btnMin;
|
||||
@FXML
|
||||
private JFXButton btnMax;
|
||||
@FXML
|
||||
private JFXButton btnClose;
|
||||
|
||||
public Decorator(Stage primaryStage, Node mainPage, String title) {
|
||||
this(primaryStage, mainPage, title, true, true);
|
||||
}
|
||||
|
||||
public Decorator(Stage primaryStage, Node mainPage, String title, boolean max, boolean min) {
|
||||
this.primaryStage = primaryStage;
|
||||
this.mainPage = mainPage;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/decorator.fxml");
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
btnClose.setGraphic(close);
|
||||
btnMin.setGraphic(minus);
|
||||
btnMax.setGraphic(resizeMax);
|
||||
|
||||
lblTitle.setText(title);
|
||||
|
||||
buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (event.getClickCount() == 2)
|
||||
btnMax.fire();
|
||||
});
|
||||
|
||||
drawerWrapper.getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setOnDialogClosed(e -> dialogShown = false);
|
||||
dialog.setOnDialogOpened(e -> dialogShown = true);
|
||||
|
||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
||||
if (!max) buttonsContainer.getChildren().remove(btnMax);
|
||||
|
||||
JFXDepthManager.setDepth(titleContainer, 1);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
||||
if (!isDragging) allowMove = false;
|
||||
});
|
||||
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
FXUtils.setOverflowHidden((Pane) lookup("#contentPlaceHolderRoot"));
|
||||
FXUtils.setOverflowHidden(drawerWrapper);
|
||||
}
|
||||
|
||||
public void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isMaximized() && !primaryStage.isFullScreen() && !maximized) {
|
||||
if (!primaryStage.isResizable())
|
||||
updateInitMouseValues(mouseEvent);
|
||||
else {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = getBoundsInParent();
|
||||
if (getBorder() != null && getBorder().getStrokes().size() > 0) {
|
||||
double borderWidth = this.contentPlaceHolder.snappedLeftInset();
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > this.getHeight() - borderWidth) {
|
||||
setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > this.getHeight() - borderWidth) {
|
||||
setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
setCursor(Cursor.N_RESIZE);
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
setCursor(Cursor.S_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
this.updateInitMouseValues(mouseEvent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMouseReleased() {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
public void onMouseDragged(MouseEvent mouseEvent) {
|
||||
this.isDragging = true;
|
||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress() && !this.primaryStage.isMaximized() && !this.maximized) {
|
||||
this.newX = mouseEvent.getScreenX();
|
||||
this.newY = mouseEvent.getScreenY();
|
||||
double deltax = this.newX - this.initX;
|
||||
double deltay = this.newY - this.initY;
|
||||
Cursor cursor = this.getCursor();
|
||||
if (Cursor.E_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltax);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NE_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltay)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltay);
|
||||
}
|
||||
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltax);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SE_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltax);
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltay);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.S_RESIZE == cursor) {
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltay);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.W_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltax)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltax);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltax)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltax);
|
||||
}
|
||||
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltay);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltax)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltax);
|
||||
}
|
||||
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltay)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltay);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.N_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltay)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltay);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (this.allowMove) {
|
||||
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
||||
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onMin() {
|
||||
primaryStage.setIconified(true);
|
||||
}
|
||||
|
||||
public void onMax() {
|
||||
if (!max) return;
|
||||
if (!this.isCustomMaximize()) {
|
||||
this.primaryStage.setMaximized(!this.primaryStage.isMaximized());
|
||||
this.maximized = this.primaryStage.isMaximized();
|
||||
if (this.primaryStage.isMaximized()) {
|
||||
this.btnMax.setGraphic(resizeMin);
|
||||
this.btnMax.setTooltip(new Tooltip("Restore Down"));
|
||||
} else {
|
||||
this.btnMax.setGraphic(resizeMax);
|
||||
this.btnMax.setTooltip(new Tooltip("Maximize"));
|
||||
}
|
||||
} else {
|
||||
if (!this.maximized) {
|
||||
this.originalBox = new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
|
||||
Screen screen = Screen.getScreensForRectangle(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()).get(0);
|
||||
Rectangle2D bounds = screen.getVisualBounds();
|
||||
this.maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
|
||||
primaryStage.setX(this.maximizedBox.getMinX());
|
||||
primaryStage.setY(this.maximizedBox.getMinY());
|
||||
primaryStage.setWidth(this.maximizedBox.getWidth());
|
||||
primaryStage.setHeight(this.maximizedBox.getHeight());
|
||||
this.btnMax.setGraphic(resizeMin);
|
||||
this.btnMax.setTooltip(new Tooltip("Restore Down"));
|
||||
} else {
|
||||
primaryStage.setX(this.originalBox.getMinX());
|
||||
primaryStage.setY(this.originalBox.getMinY());
|
||||
primaryStage.setWidth(this.originalBox.getWidth());
|
||||
primaryStage.setHeight(this.originalBox.getHeight());
|
||||
this.originalBox = null;
|
||||
this.btnMax.setGraphic(resizeMax);
|
||||
this.btnMax.setTooltip(new Tooltip("Maximize"));
|
||||
}
|
||||
|
||||
this.maximized = !this.maximized;
|
||||
}
|
||||
}
|
||||
|
||||
public void onClose() {
|
||||
onCloseButtonAction.get().run();
|
||||
}
|
||||
|
||||
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
||||
initX = mouseEvent.getScreenX();
|
||||
initY = mouseEvent.getScreenY();
|
||||
xOffset = mouseEvent.getSceneX();
|
||||
yOffset = mouseEvent.getSceneY();
|
||||
}
|
||||
|
||||
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x < getWidth() && x > getWidth() - contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
|
||||
return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
|
||||
return y < getHeight() && y > getHeight() - contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean setStageWidth(double width) {
|
||||
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
||||
primaryStage.setWidth(width);
|
||||
initX = newX;
|
||||
return true;
|
||||
} else {
|
||||
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
||||
primaryStage.setWidth(titleContainer.getMinWidth());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setStageHeight(double height) {
|
||||
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
||||
primaryStage.setHeight(height);
|
||||
initY = newY;
|
||||
return true;
|
||||
} else {
|
||||
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
||||
primaryStage.setHeight(titleContainer.getHeight());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMaximized(boolean maximized) {
|
||||
if (this.maximized != maximized) {
|
||||
Platform.runLater(btnMax::fire);
|
||||
}
|
||||
}
|
||||
|
||||
private void setContent(Node content, AnimationProducer animation) {
|
||||
animationHandler.setContent(content, animation);
|
||||
|
||||
if (content instanceof Region) {
|
||||
((Region) content).setMinSize(0, 0);
|
||||
FXUtils.setOverflowHidden((Region) content);
|
||||
}
|
||||
|
||||
backNavButton.setDisable(!wizardController.canPrev());
|
||||
|
||||
if (content instanceof Refreshable)
|
||||
refreshNavButton.setVisible(true);
|
||||
|
||||
if (content != mainPage)
|
||||
closeNavButton.setVisible(true);
|
||||
|
||||
String prefix = category == null ? "" : category + " - ";
|
||||
|
||||
titleLabel.textProperty().unbind();
|
||||
|
||||
if (content instanceof WizardPage)
|
||||
titleLabel.setText(prefix + ((WizardPage) content).getTitle());
|
||||
|
||||
if (content instanceof DecoratorPage)
|
||||
titleLabel.textProperty().bind(((DecoratorPage) content).titleProperty());
|
||||
}
|
||||
|
||||
private String category;
|
||||
private Node nowPage;
|
||||
|
||||
public void showPage(Node content) {
|
||||
Node c = content == null ? mainPage : content;
|
||||
onEnd();
|
||||
if (nowPage instanceof DecoratorPage)
|
||||
((DecoratorPage) nowPage).onClose();
|
||||
nowPage = content;
|
||||
|
||||
setContent(c, ContainerAnimations.FADE.getAnimationProducer());
|
||||
|
||||
if (c instanceof Region) {
|
||||
// Let root pane fix window size.
|
||||
StackPane parent = (StackPane) c.getParent();
|
||||
((Region) c).prefWidthProperty().bind(parent.widthProperty());
|
||||
((Region) c).prefHeightProperty().bind(parent.heightProperty());
|
||||
}
|
||||
}
|
||||
|
||||
public JFXDialog showDialog(Region content) {
|
||||
dialog.setContent(content);
|
||||
if (!dialogShown)
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
startWizard(wizardProvider, null);
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
this.category = category;
|
||||
wizardController.setProvider(wizardProvider);
|
||||
wizardController.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
backNavButton.setVisible(true);
|
||||
backNavButton.setDisable(false);
|
||||
closeNavButton.setVisible(true);
|
||||
refreshNavButton.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd() {
|
||||
backNavButton.setVisible(false);
|
||||
closeNavButton.setVisible(false);
|
||||
refreshNavButton.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
}
|
||||
|
||||
public void onRefresh() {
|
||||
((Refreshable) contentPlaceHolder.getChildren().get(0)).refresh();
|
||||
}
|
||||
|
||||
public void onCloseNav() {
|
||||
wizardController.onCancel();
|
||||
showPage(null);
|
||||
}
|
||||
|
||||
public void onBack() {
|
||||
wizardController.onPrev(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queue<Object> getCancelQueue() {
|
||||
return cancelQueue;
|
||||
}
|
||||
|
||||
public Runnable getOnCloseButtonAction() {
|
||||
return onCloseButtonAction.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
||||
return onCloseButtonAction;
|
||||
}
|
||||
|
||||
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
||||
this.onCloseButtonAction.set(onCloseButtonAction);
|
||||
}
|
||||
|
||||
public boolean isCustomMaximize() {
|
||||
return customMaximize.get();
|
||||
}
|
||||
|
||||
public BooleanProperty customMaximizeProperty() {
|
||||
return customMaximize;
|
||||
}
|
||||
|
||||
public void setCustomMaximize(boolean customMaximize) {
|
||||
this.customMaximize.set(customMaximize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WizardController getWizardController() {
|
||||
return wizardController;
|
||||
}
|
||||
|
||||
public JFXDialog getDialog() {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public JFXButton getAddMenuButton() {
|
||||
return addMenuButton;
|
||||
}
|
||||
|
||||
public AdvancedListBox getLeftPane() {
|
||||
return leftPane;
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,6 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class DialogController {
|
||||
public static final DialogController INSTANCE = new DialogController();
|
||||
|
||||
private DialogController() {}
|
||||
|
||||
public static AuthInfo logIn(Account account) throws Exception {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
@@ -41,14 +38,12 @@ public final class DialogController {
|
||||
YggdrasilAccountLoginPane pane = new YggdrasilAccountLoginPane((YggdrasilAccount) account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
Controllers.INSTANCE.closeDialog();
|
||||
return Unit.INSTANCE;
|
||||
Controllers.closeDialog();
|
||||
}, () -> {
|
||||
latch.countDown();
|
||||
Controllers.INSTANCE.closeDialog();
|
||||
return Unit.INSTANCE;
|
||||
Controllers.closeDialog();
|
||||
});
|
||||
pane.dialog = Controllers.INSTANCE.dialog(pane);
|
||||
pane.setDialog(Controllers.dialog(pane));
|
||||
});
|
||||
latch.await();
|
||||
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
||||
|
||||
303
HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
Normal file
303
HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.adapters.ReflectionHelper;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.ReflectionHelper.call;
|
||||
import static org.jackhuang.hmcl.util.ReflectionHelper.construct;
|
||||
|
||||
public final class FXUtils {
|
||||
private FXUtils() {
|
||||
}
|
||||
|
||||
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener((a, b, c) -> consumer.accept(c));
|
||||
}
|
||||
|
||||
public static <T> void onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener(new WeakChangeListener<>((a, b, c) -> consumer.accept(c)));
|
||||
}
|
||||
|
||||
public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
onChange(value, consumer);
|
||||
consumer.accept(value.getValue());
|
||||
}
|
||||
|
||||
public static <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
onWeakChange(value, consumer);
|
||||
consumer.accept(value.getValue());
|
||||
}
|
||||
|
||||
public static void limitSize(ImageView imageView, double maxWidth, double maxHeight) {
|
||||
imageView.setPreserveRatio(true);
|
||||
onChangeAndOperate(imageView.imageProperty(), image -> {
|
||||
if (image != null && (image.getWidth() > maxWidth || image.getHeight() > maxHeight)) {
|
||||
imageView.setFitHeight(maxHeight);
|
||||
imageView.setFitWidth(maxWidth);
|
||||
} else {
|
||||
imageView.setFitHeight(-1);
|
||||
imageView.setFitWidth(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(JFXTextField field) {
|
||||
field.textProperty().addListener(o -> field.validate());
|
||||
field.validate();
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(JFXPasswordField field) {
|
||||
field.textProperty().addListener(o -> field.validate());
|
||||
field.validate();
|
||||
}
|
||||
|
||||
public static void setOverflowHidden(Region region) {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(region.widthProperty());
|
||||
rectangle.heightProperty().bind(region.heightProperty());
|
||||
region.setClip(rectangle);
|
||||
}
|
||||
|
||||
public static void limitWidth(Region region, double width) {
|
||||
region.setMaxWidth(width);
|
||||
region.setMinWidth(width);
|
||||
region.setPrefWidth(width);
|
||||
}
|
||||
|
||||
public static void limitHeight(Region region, double height) {
|
||||
region.setMaxHeight(height);
|
||||
region.setMinHeight(height);
|
||||
region.setPrefHeight(height);
|
||||
}
|
||||
|
||||
public static void smoothScrolling(ScrollPane scrollPane) {
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
public static void loadFXML(Node node, String absolutePath) {
|
||||
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), Main.RESOURCE_BUNDLE);
|
||||
loader.setRoot(node);
|
||||
loader.setController(node);
|
||||
Lang.invoke(() -> loader.load());
|
||||
}
|
||||
|
||||
public static WritableImage takeSnapshot(Parent node, double width, double height) {
|
||||
Scene scene = new Scene(node, width, height);
|
||||
scene.getStylesheets().addAll(STYLESHEETS);
|
||||
return scene.snapshot(null);
|
||||
}
|
||||
|
||||
public static void resetChildren(JFXMasonryPane pane, List<Node> children) {
|
||||
// Fixes mis-repositioning.
|
||||
ReflectionHelper.setFieldContent(JFXMasonryPane.class, pane, "oldBoxes", null);
|
||||
pane.getChildren().setAll(children);
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
try {
|
||||
call(construct(Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"), new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false),
|
||||
"install", node, tooltip);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip by reflection", e);
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean alert(Alert.AlertType type, String title, String contentText) {
|
||||
return alert(type, title, contentText, null);
|
||||
}
|
||||
|
||||
public static boolean alert(Alert.AlertType type, String title, String contentText, String headerText) {
|
||||
Alert alert = new Alert(type);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(headerText);
|
||||
alert.setContentText(contentText);
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
return result.isPresent() && result.get() == ButtonType.OK;
|
||||
}
|
||||
|
||||
public static Optional<String> inputDialog(String title, String contentText) {
|
||||
return inputDialog(title, contentText, null);
|
||||
}
|
||||
|
||||
public static Optional<String> inputDialog(String title, String contentText, String headerText) {
|
||||
return inputDialog(title, contentText, headerText, "");
|
||||
}
|
||||
|
||||
public static Optional<String> inputDialog(String title, String contentText, String headerText, String defaultValue) {
|
||||
TextInputDialog dialog = new TextInputDialog(defaultValue);
|
||||
dialog.setTitle(title);
|
||||
dialog.setHeaderText(headerText);
|
||||
dialog.setContentText(contentText);
|
||||
return dialog.showAndWait();
|
||||
}
|
||||
|
||||
public static void openFolder(File file) {
|
||||
file.mkdirs();
|
||||
String path = file.getAbsolutePath();
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case OSX:
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path});
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by executing /usr/bin/open", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<?> property) {
|
||||
textField.textProperty().unbind();
|
||||
textField.textProperty().bindBidirectional((Property<Integer>) property, SafeIntStringConverter.INSTANCE);
|
||||
}
|
||||
|
||||
public static void bindString(JFXTextField textField, Property<String> property) {
|
||||
textField.textProperty().unbind();
|
||||
textField.textProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().unbind();
|
||||
toggleButton.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().unbind();
|
||||
checkBox.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
|
||||
unbindEnum(comboBox);
|
||||
ChangeListener<Number> listener = (a, b, newValue) -> {
|
||||
((Property) property).setValue(property.getValue().getClass().getEnumConstants()[newValue.intValue()]);
|
||||
};
|
||||
comboBox.getSelectionModel().select(property.getValue().ordinal());
|
||||
comboBox.getProperties().put("listener", listener);
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
||||
}
|
||||
|
||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||
ChangeListener listener = Lang.get(comboBox.getProperties(), "listener", ChangeListener.class, null);
|
||||
if (listener == null) return;
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
}
|
||||
|
||||
public static void smoothScrolling(ListView<?> listView) {
|
||||
listView.skinProperty().addListener(o -> {
|
||||
ScrollBar bar = (ScrollBar) listView.lookup(".scroll-bar");
|
||||
Node virtualFlow = listView.lookup(".virtual-flow");
|
||||
double[] frictions = new double[]{0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
|
||||
double[] pushes = new double[]{1};
|
||||
double[] derivatives = new double[frictions.length];
|
||||
|
||||
Timeline timeline = new Timeline();
|
||||
bar.addEventHandler(MouseEvent.DRAG_DETECTED, e -> timeline.stop());
|
||||
|
||||
EventHandler<ScrollEvent> scrollEventHandler = event -> {
|
||||
if (event.getEventType() == ScrollEvent.SCROLL) {
|
||||
int direction = event.getDeltaY() > 0 ? -1 : 1;
|
||||
for (int i = 0; i < pushes.length; ++i)
|
||||
derivatives[i] += direction * pushes[i];
|
||||
if (timeline.getStatus() == Animation.Status.STOPPED)
|
||||
timeline.play();
|
||||
event.consume();
|
||||
}
|
||||
};
|
||||
|
||||
bar.addEventHandler(ScrollEvent.ANY, scrollEventHandler);
|
||||
virtualFlow.setOnScroll(scrollEventHandler);
|
||||
|
||||
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
|
||||
for (int i = 0; i < derivatives.length; ++i)
|
||||
derivatives[i] *= frictions[i];
|
||||
for (int i = 1; i < derivatives.length; ++i)
|
||||
derivatives[i] += derivatives[i - 1];
|
||||
double dy = derivatives[derivatives.length - 1];
|
||||
double height = listView.getLayoutBounds().getHeight();
|
||||
bar.setValue(Math.min(Math.max(bar.getValue() + dy / height, 0), 1));
|
||||
if (Math.abs(dy) < 0.001)
|
||||
timeline.stop();
|
||||
listView.requestLayout();
|
||||
}));
|
||||
timeline.setCycleCount(Animation.INDEFINITE);
|
||||
});
|
||||
}
|
||||
|
||||
public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
|
||||
|
||||
public static final String[] STYLESHEETS = new String[]{
|
||||
FXUtils.class.getResource("/css/jfoenix-fonts.css").toExternalForm(),
|
||||
FXUtils.class.getResource("/css/jfoenix-design.css").toExternalForm(),
|
||||
FXUtils.class.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm()
|
||||
};
|
||||
|
||||
public static final Interpolator SINE = new Interpolator() {
|
||||
@Override
|
||||
protected double curve(double t) {
|
||||
return Math.sin(t * Math.PI / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Interpolator.SINE";
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class InstallerController {
|
||||
private String optiFine;
|
||||
|
||||
public void initialize() {
|
||||
FXUtilsKt.smoothScrolling(scrollPane);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
@@ -59,8 +59,8 @@ public class InstallerController {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new VersionJsonSaveTask(profile.getRepository(), version.setLibraries(newList))
|
||||
.with(Task.of(e -> profile.getRepository().refreshVersions()))
|
||||
.with(Task.of(e -> loadVersion(this.profile, this.versionId)))
|
||||
.with(Task.of(profile.getRepository()::refreshVersions))
|
||||
.with(Task.of(() -> loadVersion(this.profile, this.versionId)))
|
||||
.start();
|
||||
};
|
||||
|
||||
@@ -85,6 +85,6 @@ public class InstallerController {
|
||||
// TODO: if minecraftVersion returns null.
|
||||
if (gameVersion == null) return;
|
||||
|
||||
Controllers.INSTANCE.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion, version, forge, liteLoader, optiFine));
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion, version, forge, liteLoader, optiFine));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class InstallerItem extends BorderPane {
|
||||
|
||||
public InstallerItem(String artifact, String version, Consumer<InstallerItem> deleteCallback) {
|
||||
this.deleteCallback = deleteCallback;
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/version/installer-item.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/installer-item.fxml");
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
|
||||
@@ -31,10 +31,10 @@ public class LaunchingStepsPane extends StackPane {
|
||||
private Label lblSteps;
|
||||
|
||||
public LaunchingStepsPane() {
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/launching-steps.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/launching-steps.fxml");
|
||||
|
||||
FXUtilsKt.limitHeight(this, 200);
|
||||
FXUtilsKt.limitWidth(this, 400);
|
||||
FXUtils.limitHeight(this, 200);
|
||||
FXUtils.limitWidth(this, 400);
|
||||
}
|
||||
|
||||
public void setCurrentState(String currentState) {
|
||||
|
||||
118
HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java
Normal file
118
HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Paint;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class LeftPaneController {
|
||||
private final AdvancedListBox leftPane;
|
||||
private final VBox profilePane = new VBox();
|
||||
private final VersionListItem accountItem = new VersionListItem("No Account", "unknown");
|
||||
|
||||
public LeftPaneController(AdvancedListBox leftPane) {
|
||||
this.leftPane = leftPane;
|
||||
|
||||
leftPane.startCategory("ACCOUNTS")
|
||||
.add(Lang.apply(new RipplerContainer(accountItem), rippler -> {
|
||||
rippler.setOnMouseClicked(e -> Controllers.navigate(new AccountsPage()));
|
||||
accountItem.setOnSettingsButtonClicked(() -> Controllers.navigate(new AccountsPage()));
|
||||
}))
|
||||
.startCategory("LAUNCHER")
|
||||
.add(Lang.apply(new IconedItem(SVG.gear("black", 20, 20), Main.i18n("launcher.title.launcher")), iconedItem -> {
|
||||
iconedItem.prefWidthProperty().bind(leftPane.widthProperty());
|
||||
iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
}))
|
||||
.startCategory(Main.i18n("ui.label.profile"))
|
||||
.add(profilePane);
|
||||
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
|
||||
Controllers.getDecorator().getAddMenuButton().setOnMouseClicked(e ->
|
||||
Controllers.getDecorator().showPage(new ProfilePage(null))
|
||||
);
|
||||
|
||||
FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), it -> {
|
||||
if (it == null) {
|
||||
accountItem.setVersionName("mojang@mojang.com");
|
||||
accountItem.setGameVersion("Yggdrasil");
|
||||
} else {
|
||||
accountItem.setVersionName(it.getUsername());
|
||||
accountItem.setGameVersion(AccountsPage.accountType(it));
|
||||
}
|
||||
|
||||
if (it instanceof YggdrasilAccount)
|
||||
accountItem.setImage(AccountHelper.getSkin((YggdrasilAccount) it, 4), AccountHelper.getViewport(4));
|
||||
else
|
||||
accountItem.setImage(FXUtils.DEFAULT_ICON, null);
|
||||
});
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
Controllers.navigate(new AccountsPage());
|
||||
}
|
||||
|
||||
public void onProfileChanged(ProfileChangedEvent event) {
|
||||
Profile profile = event.getProfile();
|
||||
|
||||
for (Node node : profilePane.getChildren()) {
|
||||
if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof Pair<?, ?>) {
|
||||
((RipplerContainer) node).setSelected(Objects.equals(((Pair) node.getProperties().get("profile")).getKey(), profile.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onProfilesLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
for (Profile profile : Settings.INSTANCE.getProfiles()) {
|
||||
VersionListItem item = new VersionListItem(profile.getName());
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(() -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
|
||||
ripplerContainer.setRipplerFill(Paint.valueOf("#89E1F9"));
|
||||
ripplerContainer.setOnMouseClicked(e -> {
|
||||
// clean selected property
|
||||
for (Node node : profilePane.getChildren())
|
||||
if (node instanceof RipplerContainer)
|
||||
((RipplerContainer) node).setSelected(false);
|
||||
ripplerContainer.setSelected(true);
|
||||
Settings.INSTANCE.setSelectedProfile(profile);
|
||||
});
|
||||
ripplerContainer.getProperties().put("profile", new Pair<>(profile.getName(), item));
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
list.add(ripplerContainer);
|
||||
}
|
||||
Platform.runLater(() -> profilePane.getChildren().setAll(list));
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
@@ -61,8 +61,8 @@ public final class LogWindow extends Stage {
|
||||
|
||||
public LogWindow() {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(FXUtilsKt.getStylesheets());
|
||||
setTitle(MainKt.i18n("logwindow.title"));
|
||||
getScene().getStylesheets().addAll(FXUtils.STYLESHEETS);
|
||||
setTitle(Main.i18n("logwindow.title"));
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public final class LogWindow extends Stage {
|
||||
Document document;
|
||||
|
||||
LogWindowImpl() {
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/log.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/log.fxml");
|
||||
|
||||
engine = webView.getEngine();
|
||||
engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(getClass().getResourceAsStream("/assets/log-window-content.html")))
|
||||
|
||||
127
HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java
Normal file
127
HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXMasonryPane;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
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 java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("launcher.title.main"));
|
||||
|
||||
@FXML
|
||||
private JFXButton btnRefresh;
|
||||
|
||||
@FXML
|
||||
private JFXButton btnAdd;
|
||||
|
||||
@FXML
|
||||
private JFXMasonryPane masonryPane;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(() -> Platform.runLater(this::loadVersions));
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), "Install New Game"));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersions());
|
||||
}
|
||||
|
||||
private Node buildNode(Profile profile, String version, String game) {
|
||||
VersionItem item = new VersionItem();
|
||||
item.setGameVersion(game);
|
||||
item.setVersionName(version);
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Main.i18n("login.no_Player007"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(version);
|
||||
});
|
||||
item.setOnDeleteButtonClicked(e -> {
|
||||
profile.getRepository().removeVersionFromDisk(version);
|
||||
Platform.runLater(this::loadVersions);
|
||||
});
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
Controllers.getDecorator().showPage(Controllers.getVersionPage());
|
||||
Controllers.getVersionPage().load(version, profile);
|
||||
});
|
||||
File iconFile = profile.getRepository().getVersionIcon(version);
|
||||
if (iconFile.exists())
|
||||
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
return item;
|
||||
}
|
||||
|
||||
public void onProfilesLoading() {
|
||||
// TODO: Profiles
|
||||
}
|
||||
|
||||
public void onProfileChanged(ProfileChangedEvent event) {
|
||||
Platform.runLater(() -> loadVersions(event.getProfile()));
|
||||
}
|
||||
|
||||
private void loadVersions() {
|
||||
loadVersions(Settings.INSTANCE.getSelectedProfile());
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
List<Node> children = new LinkedList<>();
|
||||
for (Version version : profile.getRepository().getVersions()) {
|
||||
children.add(buildNode(profile, version.getId(), Lang.nonNull(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version.getId())), "Unknown")));
|
||||
}
|
||||
FXUtils.resetChildren(masonryPane, children);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
this.text = text;
|
||||
this.dialog = dialog;
|
||||
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
content.setText(text);
|
||||
acceptButton.setOnMouseClicked(e -> dialog.close());
|
||||
}
|
||||
|
||||
153
HMCL/src/main/java/org/jackhuang/hmcl/ui/ModController.java
Normal file
153
HMCL/src/main/java/org/jackhuang/hmcl/ui/ModController.java
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ModController {
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
|
||||
@FXML private StackPane rootPane;
|
||||
|
||||
@FXML private VBox modPane;
|
||||
|
||||
@FXML private StackPane contentPane;
|
||||
@FXML private JFXSpinner spinner;
|
||||
|
||||
private JFXTabPane parentTab;
|
||||
private ModManager modManager;
|
||||
private String versionId;
|
||||
|
||||
public void initialize() {
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
|
||||
rootPane.setOnDragOver(event -> {
|
||||
if (event.getGestureSource() != rootPane && event.getDragboard().hasFiles())
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
event.consume();
|
||||
});
|
||||
|
||||
rootPane.setOnDragDropped(event -> {
|
||||
List<File> mods = event.getDragboard().getFiles();
|
||||
Stream<File> stream = null;
|
||||
if (mods != null)
|
||||
stream = mods.stream()
|
||||
.filter(it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)));
|
||||
if (stream != null && stream.findAny().isPresent()) {
|
||||
stream.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(versionId, it);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||
}
|
||||
});
|
||||
loadMods(modManager, versionId);
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
public void loadMods(ModManager modManager, String versionId) {
|
||||
this.modManager = modManager;
|
||||
this.versionId = versionId;
|
||||
Task.of(variables -> {
|
||||
synchronized (ModController.this) {
|
||||
Platform.runLater(() -> {
|
||||
rootPane.getChildren().remove(contentPane);
|
||||
spinner.setVisible(true);
|
||||
});
|
||||
|
||||
modManager.refreshMods(versionId);
|
||||
|
||||
// Surprisingly, if there are a great number of mods, this processing will cause a long UI pause,
|
||||
// constructing UI elements.
|
||||
// We must do this asynchronously.
|
||||
LinkedList<ModItem> list = new LinkedList<>();
|
||||
for (ModInfo modInfo : modManager.getMods(versionId)) {
|
||||
ModItem item = new ModItem(modInfo, i -> {
|
||||
modManager.removeMods(versionId, modInfo);
|
||||
loadMods(modManager, versionId);
|
||||
});
|
||||
modInfo.activeProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue)
|
||||
item.getStyleClass().remove("disabled");
|
||||
else
|
||||
item.getStyleClass().add("disabled");
|
||||
});
|
||||
if (!modInfo.isActive())
|
||||
item.getStyleClass().add("disabled");
|
||||
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
rootPane.getChildren().add(contentPane);
|
||||
spinner.setVisible(false);
|
||||
});
|
||||
variables.set("list", list);
|
||||
}
|
||||
}).subscribe(Schedulers.javafx(), variables -> {
|
||||
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
if (newValue != null && newValue.getUserData() == ModController.this)
|
||||
modPane.getChildren().setAll(variables.<List<ModItem>>get("list"));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void onAdd() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Main.i18n("mods.choose_mod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter("Mod", "*.jar", "*.zip", "*.litemod"));
|
||||
File res = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (res == null) return;
|
||||
Task.of(() -> modManager.addMod(versionId, res))
|
||||
.subscribe(Task.of(Schedulers.javafx(), () -> loadMods(modManager, versionId)));
|
||||
}
|
||||
|
||||
public void setParentTab(JFXTabPane parentTab) {
|
||||
this.parentTab = parentTab;
|
||||
}
|
||||
}
|
||||
121
HMCL/src/main/java/org/jackhuang/hmcl/ui/ProfilePage.java
Normal file
121
HMCL/src/main/java/org/jackhuang/hmcl/ui/ProfilePage.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title;
|
||||
private final StringProperty location;
|
||||
private final Profile profile;
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProfileName;
|
||||
@FXML
|
||||
private FileItem gameDir;
|
||||
@FXML private JFXButton btnSave;
|
||||
@FXML private JFXButton btnDelete;
|
||||
|
||||
/**
|
||||
* @param profile null if creating a new profile.
|
||||
*/
|
||||
public ProfilePage(Profile profile) {
|
||||
this.profile = profile;
|
||||
|
||||
title = new SimpleStringProperty(this, "title",
|
||||
profile == null ? Main.i18n("ui.newProfileWindow.title") : Main.i18n("ui.label.profile") + " - " + profile.getName());
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/profile.fxml");
|
||||
|
||||
txtProfileName.setText(Optional.ofNullable(profile).map(Profile::getName).orElse(""));
|
||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
gameDir.setProperty(location);
|
||||
FXUtils.onChangeAndOperate(location, it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
|
||||
if (profile == null)
|
||||
btnDelete.setVisible(false);
|
||||
}
|
||||
|
||||
public void onDelete() {
|
||||
if (profile != null) {
|
||||
Settings.INSTANCE.deleteProfile(profile);
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSave() {
|
||||
if (profile != null) {
|
||||
profile.setName(txtProfileName.getText());
|
||||
if (StringUtils.isNotBlank(getLocation()))
|
||||
profile.setGameDir(new File(getLocation()));
|
||||
} else {
|
||||
if (StringUtils.isBlank(getLocation())) {
|
||||
gameDir.onExplore();
|
||||
}
|
||||
Settings.INSTANCE.putProfile(new Profile(txtProfileName.getText(), new File(getLocation())));
|
||||
}
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location.get();
|
||||
}
|
||||
|
||||
public StringProperty locationProperty() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location.set(location);
|
||||
}
|
||||
}
|
||||
96
HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java
Normal file
96
HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
|
||||
public final class SVG {
|
||||
private SVG() {
|
||||
}
|
||||
|
||||
private static Node createSVGPath(String d, String fill, double width, double height) {
|
||||
SVGPath path = new SVGPath();
|
||||
path.getStyleClass().add("svg");
|
||||
path.setContent(d);
|
||||
path.setStyle("-fx-fill: " + fill + ";");
|
||||
|
||||
Group svg = new Group(path);
|
||||
double scale = Math.min(width / svg.getBoundsInParent().getWidth(), height / svg.getBoundsInParent().getHeight());
|
||||
svg.setScaleX(scale);
|
||||
svg.setScaleY(scale);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
// default fill: white, width: 20, height 20
|
||||
|
||||
public static Node gear(String fill, double width, double height) {
|
||||
return createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node back(String fill, double width, double height) {
|
||||
return createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node close(String fill, double width, double height) {
|
||||
return createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node dotsVertical(String fill, double width, double height) {
|
||||
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node delete(String fill, double width, double height) {
|
||||
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node accountEdit(String fill, double width, double height) {
|
||||
return createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node expand(String fill, double width, double height) {
|
||||
return createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node collapse(String fill, double width, double height) {
|
||||
return createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node navigate(String fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node launch(String fill, double width, double height) {
|
||||
return createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node pencil(String fill, double width, double height) {
|
||||
return createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node refresh(String fill, double width, double height) {
|
||||
return createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node folderOpen(String fill, double width, double height) {
|
||||
return createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,11 @@ import java.util.Optional;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class SafeIntStringConverter extends StringConverter<Integer> {
|
||||
public static final SafeIntStringConverter INSTANCE = new SafeIntStringConverter();
|
||||
|
||||
private SafeIntStringConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer fromString(String string) {
|
||||
return Optional.ofNullable(string).map(Lang::toIntOrNull).orElse(null);
|
||||
|
||||
135
HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java
Normal file
135
HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Locales;
|
||||
import org.jackhuang.hmcl.setting.Proxies;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.FontComboBox;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("launcher.title.launcher"));
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProxyHost;
|
||||
@FXML
|
||||
private JFXTextField txtProxyPort;
|
||||
@FXML
|
||||
private JFXTextField txtProxyUsername;
|
||||
@FXML
|
||||
private JFXTextField txtProxyPassword;
|
||||
@FXML
|
||||
private JFXTextField txtFontSize;
|
||||
@FXML
|
||||
private JFXComboBox<?> cboProxyType;
|
||||
@FXML
|
||||
private JFXComboBox<Label> cboLanguage;
|
||||
@FXML
|
||||
private JFXComboBox<?> cboDownloadSource;
|
||||
@FXML
|
||||
private FontComboBox cboFont;
|
||||
@FXML
|
||||
private FileItem fileCommonLocation;
|
||||
@FXML
|
||||
private FileItem fileBackgroundLocation;
|
||||
@FXML
|
||||
private Label lblDisplay;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/setting.fxml");
|
||||
|
||||
FXUtils.limitWidth(cboLanguage, 400);
|
||||
FXUtils.limitWidth(cboDownloadSource, 400);
|
||||
|
||||
txtProxyHost.setText(Settings.INSTANCE.getProxyHost());
|
||||
txtProxyHost.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyHost(newValue));
|
||||
|
||||
txtProxyPort.setText(Settings.INSTANCE.getProxyPort());
|
||||
txtProxyPort.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyPort(newValue));
|
||||
|
||||
txtProxyUsername.setText(Settings.INSTANCE.getProxyUser());
|
||||
txtProxyUsername.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyUser(newValue));
|
||||
|
||||
txtProxyPassword.setText(Settings.INSTANCE.getProxyPass());
|
||||
txtProxyPassword.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyPass(newValue));
|
||||
|
||||
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.INSTANCE.getDownloadProvider()));
|
||||
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
|
||||
|
||||
cboFont.getSelectionModel().select(Settings.INSTANCE.getFont().getFamily());
|
||||
cboFont.valueProperty().addListener((a, b, newValue) -> {
|
||||
Font font = Font.font(newValue, Settings.INSTANCE.getFont().getSize());
|
||||
Settings.INSTANCE.setFont(font);
|
||||
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
|
||||
});
|
||||
|
||||
txtFontSize.setText(Double.toString(Settings.INSTANCE.getFont().getSize()));
|
||||
txtFontSize.getValidators().add(new Validator(it -> Lang.toDoubleOrNull(it) != null));
|
||||
txtFontSize.textProperty().addListener((a, b, newValue) -> {
|
||||
if (txtFontSize.validate()) {
|
||||
Font font = Font.font(Settings.INSTANCE.getFont().getFamily(), Double.parseDouble(newValue));
|
||||
Settings.INSTANCE.setFont(font);
|
||||
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
|
||||
}
|
||||
});
|
||||
|
||||
lblDisplay.setStyle("-fx-font: " + Settings.INSTANCE.getFont().getSize() + " \"" + Settings.INSTANCE.getFont().getFamily() + "\";");
|
||||
|
||||
ObservableList<Label> list = FXCollections.observableArrayList();
|
||||
for (Locales.SupportedLocale locale : Locales.LOCALES)
|
||||
list.add(new Label(locale.getName(Settings.INSTANCE.getLocale().getResourceBundle())));
|
||||
|
||||
cboLanguage.setItems(list);
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(Settings.INSTANCE.getLocale()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setLocale(Locales.getLocale(newValue.intValue())));
|
||||
|
||||
cboProxyType.getSelectionModel().select(Proxies.PROXIES.indexOf(Settings.INSTANCE.getProxyType()));
|
||||
cboProxyType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyType(Proxies.getProxyType(newValue.intValue())));
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
}
|
||||
@@ -55,16 +55,16 @@ public final class VersionItem extends StackPane {
|
||||
private ImageView iconView;
|
||||
|
||||
public VersionItem() {
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/version-item.fxml");
|
||||
FXUtilsKt.limitWidth(this, 160);
|
||||
FXUtilsKt.limitHeight(this, 156);
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
|
||||
FXUtils.limitWidth(this, 160);
|
||||
FXUtils.limitHeight(this, 156);
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
|
||||
btnSettings.setGraphic(SVG.gear("black", 15, 15));
|
||||
btnDelete.setGraphic(SVG.delete("black", 15, 15));
|
||||
btnLaunch.setGraphic(SVG.launch("black", 15, 15));
|
||||
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
FXUtilsKt.limitSize(iconView, 32, 32);
|
||||
FXUtils.limitSize(iconView, 32, 32);
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
|
||||
@@ -40,21 +40,21 @@ public final class VersionListItem extends StackPane {
|
||||
}
|
||||
|
||||
public VersionListItem(String versionName, String gameVersion) {
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/version-list-item.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version-list-item.fxml");
|
||||
|
||||
lblVersionName.setText(versionName);
|
||||
lblGameVersion.setText(gameVersion);
|
||||
|
||||
FXUtilsKt.limitSize(imageView, 32, 32);
|
||||
FXUtilsKt.limitWidth(imageViewContainer, 32);
|
||||
FXUtilsKt.limitHeight(imageViewContainer, 32);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
FXUtils.limitWidth(imageViewContainer, 32);
|
||||
FXUtils.limitHeight(imageViewContainer, 32);
|
||||
}
|
||||
|
||||
public void onSettings() {
|
||||
handler.run();
|
||||
}
|
||||
|
||||
public void onSettingsButtonClicked(Runnable handler) {
|
||||
public void setOnSettingsButtonClicked(Runnable handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
|
||||
189
HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java
Normal file
189
HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", null);
|
||||
|
||||
@FXML
|
||||
private VersionSettingsController versionSettingsController;
|
||||
@FXML
|
||||
private Tab modTab;
|
||||
@FXML
|
||||
private ModController modController;
|
||||
@FXML
|
||||
private InstallerController installerController;
|
||||
@FXML
|
||||
private JFXListView<?> browseList;
|
||||
@FXML
|
||||
private JFXListView<?> managementList;
|
||||
@FXML
|
||||
private JFXButton btnBrowseMenu;
|
||||
@FXML
|
||||
private JFXButton btnManagementMenu;
|
||||
@FXML
|
||||
private JFXButton btnExport;
|
||||
@FXML
|
||||
private StackPane rootPane;
|
||||
@FXML
|
||||
private StackPane contentPane;
|
||||
@FXML
|
||||
private JFXTabPane tabPane;
|
||||
|
||||
private JFXPopup browsePopup;
|
||||
private JFXPopup managementPopup;
|
||||
|
||||
private Profile profile;
|
||||
private String version;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml");
|
||||
|
||||
getChildren().removeAll(browseList, managementList);
|
||||
|
||||
browsePopup = new JFXPopup(browseList);
|
||||
managementPopup = new JFXPopup(managementList);
|
||||
|
||||
FXUtils.installTooltip(btnBrowseMenu, 0, 5000, 0, new Tooltip(Main.i18n("settings.explore")));
|
||||
FXUtils.installTooltip(btnManagementMenu, 0, 5000, 0, new Tooltip(Main.i18n("settings.manage")));
|
||||
FXUtils.installTooltip(btnExport, 0, 5000, 0, new Tooltip(Main.i18n("settings.save")));
|
||||
}
|
||||
|
||||
public void load(String id, Profile profile) {
|
||||
this.version = id;
|
||||
this.profile = profile;
|
||||
|
||||
title.set(Main.i18n("launcher.title.game") + " - " + id);
|
||||
|
||||
versionSettingsController.loadVersionSetting(profile, id, profile.getVersionSetting(id));
|
||||
modController.setParentTab(tabPane);
|
||||
modTab.setUserData(modController);
|
||||
modController.loadMods(profile.getModManager(), id);
|
||||
installerController.loadVersion(profile, id);
|
||||
}
|
||||
|
||||
public void onBrowseMenu() {
|
||||
browseList.getSelectionModel().select(-1);
|
||||
;
|
||||
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
|
||||
}
|
||||
|
||||
public void onManagementMenu() {
|
||||
managementList.getSelectionModel().select(-1);
|
||||
;
|
||||
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
|
||||
}
|
||||
|
||||
public void onExport() {
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Main.i18n("modpack.wizard"));
|
||||
}
|
||||
|
||||
public void onBrowse() {
|
||||
String sub;
|
||||
switch (browseList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
sub = "";
|
||||
break;
|
||||
case 1:
|
||||
sub = "mods";
|
||||
break;
|
||||
case 2:
|
||||
sub = "coremods";
|
||||
break;
|
||||
case 3:
|
||||
sub = "config";
|
||||
break;
|
||||
case 4:
|
||||
sub = "resourcepacks";
|
||||
break;
|
||||
case 5:
|
||||
sub = "screenshots";
|
||||
break;
|
||||
case 6:
|
||||
sub = "saves";
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
}
|
||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
||||
}
|
||||
|
||||
public void onManagement() {
|
||||
switch (managementList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0: // rename a version
|
||||
Optional<String> res = FXUtils.inputDialog("Input", Main.i18n("versions.manage.rename.message"), null, version);
|
||||
if (res.isPresent()) {
|
||||
if (profile.getRepository().renameVersion(version, res.get())) {
|
||||
profile.getRepository().refreshVersions();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: // remove a version
|
||||
if (FXUtils.alert(Alert.AlertType.CONFIRMATION, "Confirm", Main.i18n("versions.manage.remove.confirm") + version)) {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersions();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2: // redownload asset index
|
||||
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version).resolve(profile.getRepository())).start();
|
||||
break;
|
||||
case 3: // delete libraries
|
||||
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
|
||||
break;
|
||||
case 4:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.NumberValidator;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class VersionSettingsController {
|
||||
private VersionSetting lastVersionSetting = null;
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
|
||||
@FXML private VBox rootPane;
|
||||
@FXML private ScrollPane scroll;
|
||||
@FXML private JFXTextField txtWidth;
|
||||
@FXML private JFXTextField txtHeight;
|
||||
@FXML private JFXTextField txtMaxMemory;
|
||||
@FXML private JFXTextField txtJVMArgs;
|
||||
@FXML private JFXTextField txtGameArgs;
|
||||
@FXML private JFXTextField txtMetaspace;
|
||||
@FXML private JFXTextField txtWrapper;
|
||||
@FXML private JFXTextField txtPrecallingCommand;
|
||||
@FXML private JFXTextField txtServerIP;
|
||||
@FXML private ComponentList advancedSettingsPane;
|
||||
@FXML private JFXComboBox<?> cboLauncherVisibility;
|
||||
@FXML private JFXCheckBox chkFullscreen;
|
||||
@FXML private Label lblPhysicalMemory;
|
||||
@FXML private JFXToggleButton chkNoJVMArgs;
|
||||
@FXML private JFXToggleButton chkNoCommon;
|
||||
@FXML private JFXToggleButton chkNoGameCheck;
|
||||
@FXML private MultiFileItem javaItem;
|
||||
@FXML private MultiFileItem gameDirItem;
|
||||
@FXML private JFXToggleButton chkShowLogs;
|
||||
@FXML private ImageView iconView;
|
||||
|
||||
public void initialize() {
|
||||
lblPhysicalMemory.setText(Main.i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB");
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
double limitWidth = 300;
|
||||
FXUtils.limitWidth(txtMaxMemory, limitWidth);
|
||||
FXUtils.limitWidth(cboLauncherVisibility, limitWidth);
|
||||
|
||||
double limitHeight = 10;
|
||||
FXUtils.limitHeight(chkNoJVMArgs, limitHeight);
|
||||
FXUtils.limitHeight(chkNoCommon, limitHeight);
|
||||
FXUtils.limitHeight(chkNoGameCheck, limitHeight);
|
||||
FXUtils.limitHeight(chkShowLogs, limitHeight);
|
||||
|
||||
NumberValidator nonnull = new NumberValidator("Must be a number.", false);
|
||||
NumberValidator nullable = new NumberValidator("Must be a number.", true);
|
||||
|
||||
txtWidth.setValidators(nonnull);
|
||||
FXUtils.setValidateWhileTextChanged(txtWidth);
|
||||
txtHeight.setValidators(nonnull);
|
||||
FXUtils.setValidateWhileTextChanged(txtHeight);
|
||||
txtMaxMemory.setValidators(nonnull);
|
||||
FXUtils.setValidateWhileTextChanged(txtMaxMemory);
|
||||
txtMetaspace.setValidators(nullable);
|
||||
FXUtils.setValidateWhileTextChanged(txtMetaspace);
|
||||
|
||||
Task.of(variables -> {
|
||||
variables.set("list", JavaVersion.getJREs().values().stream().map(javaVersion ->
|
||||
javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion)
|
||||
).collect(Collectors.toList()));
|
||||
}).subscribe(Schedulers.javafx(), variables ->
|
||||
javaItem.loadChildren(variables.<Collection<Node>>get("list"))
|
||||
);
|
||||
|
||||
gameDirItem.loadChildren(Arrays.asList(
|
||||
gameDirItem.createChildren(Main.i18n("advancedsettings.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(Main.i18n("advancedsettings.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
|
||||
));
|
||||
}
|
||||
|
||||
public void loadVersionSetting(Profile profile, String versionId, VersionSetting versionSetting) {
|
||||
rootPane.getChildren().remove(advancedSettingsPane);
|
||||
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
|
||||
if (lastVersionSetting != null) {
|
||||
lastVersionSetting.widthProperty().unbind();
|
||||
lastVersionSetting.heightProperty().unbind();
|
||||
lastVersionSetting.maxMemoryProperty().unbind();
|
||||
lastVersionSetting.javaArgsProperty().unbind();
|
||||
lastVersionSetting.minecraftArgsProperty().unbind();
|
||||
lastVersionSetting.permSizeProperty().unbind();
|
||||
lastVersionSetting.wrapperProperty().unbind();
|
||||
lastVersionSetting.preLaunchCommandProperty().unbind();
|
||||
lastVersionSetting.serverIpProperty().unbind();
|
||||
lastVersionSetting.fullscreenProperty().unbind();
|
||||
lastVersionSetting.notCheckGameProperty().unbind();
|
||||
lastVersionSetting.noCommonProperty().unbind();
|
||||
lastVersionSetting.javaDirProperty().unbind();
|
||||
lastVersionSetting.showLogsProperty().unbind();
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
}
|
||||
|
||||
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
|
||||
FXUtils.bindInt(txtHeight, versionSetting.heightProperty());
|
||||
FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty());
|
||||
FXUtils.bindString(javaItem.getTxtCustom(), versionSetting.javaDirProperty());
|
||||
FXUtils.bindString(gameDirItem.getTxtCustom(), versionSetting.gameDirProperty());
|
||||
FXUtils.bindString(txtJVMArgs, versionSetting.javaArgsProperty());
|
||||
FXUtils.bindString(txtGameArgs, versionSetting.minecraftArgsProperty());
|
||||
FXUtils.bindString(txtMetaspace, versionSetting.permSizeProperty());
|
||||
FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty());
|
||||
FXUtils.bindString(txtPrecallingCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
|
||||
FXUtils.bindBoolean(chkNoGameCheck, versionSetting.notCheckGameProperty());
|
||||
FXUtils.bindBoolean(chkNoCommon, versionSetting.noCommonProperty());
|
||||
FXUtils.bindBoolean(chkShowLogs, versionSetting.showLogsProperty());
|
||||
|
||||
String javaGroupKey = "java_group.listener";
|
||||
|
||||
Lang.get(javaItem.getGroup().getProperties(), javaGroupKey, ChangeListener.class)
|
||||
.ifPresent(javaItem.getGroup().selectedToggleProperty()::removeListener);
|
||||
|
||||
boolean flag = false;
|
||||
JFXRadioButton defaultToggle = null;
|
||||
for (Toggle toggle : javaItem.getGroup().getToggles()) {
|
||||
if (toggle instanceof JFXRadioButton) {
|
||||
if (toggle.getUserData() == Lang.invoke(versionSetting::getJavaVersion)) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = (JFXRadioButton) toggle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChangeListener<Toggle> listener = (a, b, newValue) -> {
|
||||
if (newValue == javaItem.getRadioCustom()) {
|
||||
versionSetting.setJava("Custom");
|
||||
} else {
|
||||
versionSetting.setJava(((JavaVersion) newValue.getUserData()).getVersion());
|
||||
}
|
||||
};
|
||||
|
||||
javaItem.getGroup().getProperties().put(javaGroupKey, listener);
|
||||
javaItem.getGroup().selectedToggleProperty().addListener(listener);
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
}
|
||||
|
||||
versionSetting.javaDirProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
|
||||
String gameDirKey = "game_dir.listener";
|
||||
Lang.get(gameDirItem.getGroup().getProperties(), gameDirKey, ChangeListener.class)
|
||||
.ifPresent(gameDirItem.getGroup().selectedToggleProperty()::removeListener);
|
||||
|
||||
for (Toggle toggle : gameDirItem.getGroup().getToggles()) {
|
||||
if (toggle instanceof JFXRadioButton) {
|
||||
if (toggle.getUserData() == versionSetting.getGameDirType()) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameDirItem.setCustomUserData(EnumGameDirectory.CUSTOM);
|
||||
|
||||
ChangeListener<Toggle> gameDirListener = (a, b, newValue) -> {
|
||||
versionSetting.setGameDirType((EnumGameDirectory) newValue.getUserData());
|
||||
};
|
||||
|
||||
gameDirItem.getGroup().getProperties().put(gameDirKey, gameDirListener);
|
||||
gameDirItem.getGroup().selectedToggleProperty().addListener(gameDirListener);
|
||||
|
||||
versionSetting.gameDirProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
|
||||
versionSetting.gameDirTypeProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
|
||||
initGameDirSubtitle(versionSetting);
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
loadIcon();
|
||||
}
|
||||
|
||||
private void initJavaSubtitle(VersionSetting versionSetting) {
|
||||
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
|
||||
.subscribe(Task.of(Schedulers.javafx(),
|
||||
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java")
|
||||
.map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory"))));
|
||||
}
|
||||
|
||||
private void initGameDirSubtitle(VersionSetting versionSetting) {
|
||||
gameDirItem.setSubtitle(profile.getRepository().getRunDirectory(versionId).getAbsolutePath());
|
||||
}
|
||||
|
||||
public void onShowAdvanced() {
|
||||
if (!rootPane.getChildren().contains(advancedSettingsPane))
|
||||
rootPane.getChildren().add(advancedSettingsPane);
|
||||
else
|
||||
rootPane.getChildren().remove(advancedSettingsPane);
|
||||
}
|
||||
|
||||
public void onExploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Image", "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
try {
|
||||
FileUtils.copyFile(selectedFile, iconFile);
|
||||
loadIcon();
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadIcon() {
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
if (iconFile.exists())
|
||||
iconView.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
iconView.setImage(FXUtils.DEFAULT_ICON);
|
||||
FXUtils.limitSize(iconView, 32, 32);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class WebStage extends Stage {
|
||||
|
||||
public WebStage() {
|
||||
setScene(new Scene(webView, 800, 480));
|
||||
getScene().getStylesheets().addAll(FXUtilsKt.getStylesheets());
|
||||
getScene().getStylesheets().addAll(FXUtils.STYLESHEETS);
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.game.HMCLMultiCharacterSelector;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class YggdrasilAccountLoginPane extends StackPane {
|
||||
private final YggdrasilAccount oldAccount;
|
||||
private final Consumer<AuthInfo> success;
|
||||
private final Runnable failed;
|
||||
|
||||
@FXML
|
||||
private Label lblUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
private JFXDialog dialog;
|
||||
|
||||
public YggdrasilAccountLoginPane(YggdrasilAccount oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
this.success = success;
|
||||
this.failed = failed;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/yggdrasil-account-login.fxml");
|
||||
|
||||
lblUsername.setText(oldAccount.getUsername());
|
||||
txtPassword.setOnAction(e -> onAccept());
|
||||
}
|
||||
|
||||
public void onAccept() {
|
||||
String username = oldAccount.getUsername();
|
||||
String password = txtPassword.getText();
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult("login", () -> {
|
||||
try {
|
||||
Account account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password);
|
||||
return account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy());
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
}).subscribe(Schedulers.javafx(), variable -> {
|
||||
Object account = variable.get("login");
|
||||
if (account instanceof AuthInfo) {
|
||||
success.accept(((AuthInfo) account));
|
||||
dialog.close();
|
||||
} else if (account instanceof InvalidCredentialsException) {
|
||||
lblCreationWarning.setText(Main.i18n("login.wrong_password"));
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(account.getClass().toString() + ": " + ((Exception) account).getLocalizedMessage());
|
||||
}
|
||||
|
||||
progressBar.setVisible(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void onCancel() {
|
||||
failed.run();
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
public void setDialog(JFXDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public final class TransitionHandler implements AnimationHandler {
|
||||
private final StackPane view;
|
||||
@@ -91,7 +91,7 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
WritableImage image;
|
||||
if (content != null && content instanceof Parent) {
|
||||
view.getChildren().setAll();
|
||||
image = FXUtilsKt.takeSnapshot((Parent) content, view.getWidth(), view.getHeight());
|
||||
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()));
|
||||
|
||||
@@ -28,7 +28,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
/**
|
||||
@@ -100,7 +100,7 @@ public class ComponentListCell extends StackPane {
|
||||
|
||||
VBox container = new VBox();
|
||||
container.setStyle("-fx-padding: 8 0 0 0;");
|
||||
FXUtilsKt.limitHeight(container, 0);
|
||||
FXUtils.limitHeight(container, 0);
|
||||
Rectangle clipRect = new Rectangle();
|
||||
clipRect.widthProperty().bind(container.widthProperty());
|
||||
clipRect.heightProperty().bind(container.heightProperty());
|
||||
@@ -129,8 +129,8 @@ public class ComponentListCell extends StackPane {
|
||||
animatedHeight = newAnimatedHeight;
|
||||
|
||||
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
|
||||
new KeyValue(container.minHeightProperty(), contentHeight, FXUtilsKt.SINE),
|
||||
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtilsKt.SINE)
|
||||
new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE),
|
||||
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE)
|
||||
));
|
||||
|
||||
if (!isExpanded()) {
|
||||
|
||||
@@ -61,7 +61,7 @@ public class FileItem extends BorderPane {
|
||||
public void onExplore() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.titleProperty().bind(titleProperty());
|
||||
File selectedDir = chooser.showDialog(Controllers.INSTANCE.getStage());
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
property.setValue(selectedDir.getAbsolutePath());
|
||||
chooser.titleProperty().unbind();
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -29,7 +29,7 @@ public final class MessageBox {
|
||||
private MessageBox() {
|
||||
}
|
||||
|
||||
private static final String TITLE = MainKt.i18n("message.info");
|
||||
private static final String TITLE = Main.i18n("message.info");
|
||||
|
||||
/**
|
||||
* User Operation: Yes
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
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;
|
||||
import java.util.Collection;
|
||||
|
||||
public class MultiFileItem extends ComponentList {
|
||||
private final StringProperty customText = new SimpleStringProperty(this, "customText", "Custom");
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", "Select a file");
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
private final JFXTextField txtCustom = new JFXTextField();
|
||||
private final JFXButton btnSelect = new JFXButton();
|
||||
private final JFXRadioButton radioCustom = new JFXRadioButton();
|
||||
private final BorderPane custom = new BorderPane();
|
||||
private final VBox pane = new VBox();
|
||||
|
||||
{
|
||||
BorderPane.setAlignment(txtCustom, Pos.CENTER_RIGHT);
|
||||
|
||||
btnSelect.setGraphic(SVG.folderOpen("black", 15, 15));
|
||||
btnSelect.setOnMouseClicked(e -> {
|
||||
// TODO
|
||||
});
|
||||
|
||||
radioCustom.textProperty().bind(customTextProperty());
|
||||
radioCustom.setToggleGroup(group);
|
||||
txtCustom.disableProperty().bind(radioCustom.selectedProperty().not());
|
||||
btnSelect.disableProperty().bind(radioCustom.selectedProperty().not());
|
||||
|
||||
custom.setLeft(radioCustom);
|
||||
custom.setStyle("-fx-padding: 3;");
|
||||
HBox right = new HBox();
|
||||
right.setSpacing(3);
|
||||
right.getChildren().addAll(txtCustom, btnSelect);
|
||||
custom.setRight(right);
|
||||
FXUtils.limitHeight(custom, 20);
|
||||
|
||||
pane.setStyle("-fx-padding: 0 0 10 0;");
|
||||
pane.setSpacing(8);
|
||||
pane.getChildren().add(custom);
|
||||
addChildren(pane);
|
||||
}
|
||||
|
||||
public Node createChildren(String title) {
|
||||
return createChildren(title, null);
|
||||
}
|
||||
|
||||
public Node createChildren(String title, Object userData) {
|
||||
return createChildren(title, "", userData);
|
||||
}
|
||||
|
||||
public Node createChildren(String title, String subtitle, Object userData) {
|
||||
BorderPane pane = new BorderPane();
|
||||
pane.setStyle("-fx-padding: 3;");
|
||||
FXUtils.limitHeight(pane, 20);
|
||||
|
||||
JFXRadioButton left = new JFXRadioButton(title);
|
||||
left.setToggleGroup(group);
|
||||
left.setUserData(userData);
|
||||
pane.setLeft(left);
|
||||
|
||||
Label right = new Label(subtitle);
|
||||
right.getStyleClass().add("subtitle-label");
|
||||
right.setStyle("-fx-font-size: 10;");
|
||||
pane.setRight(right);
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
public void loadChildren(Collection<Node> list) {
|
||||
pane.getChildren().setAll(list);
|
||||
pane.getChildren().add(custom);
|
||||
}
|
||||
|
||||
public void onExploreJavaDir() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(Main.i18n(getChooserTitle()));
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
txtCustom.setText(selectedDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
public ToggleGroup getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public String getCustomText() {
|
||||
return customText.get();
|
||||
}
|
||||
|
||||
public StringProperty customTextProperty() {
|
||||
return customText;
|
||||
}
|
||||
|
||||
public void setCustomText(String customText) {
|
||||
this.customText.set(customText);
|
||||
}
|
||||
|
||||
public String getChooserTitle() {
|
||||
return chooserTitle.get();
|
||||
}
|
||||
|
||||
public StringProperty chooserTitleProperty() {
|
||||
return chooserTitle;
|
||||
}
|
||||
|
||||
public void setChooserTitle(String chooserTitle) {
|
||||
this.chooserTitle.set(chooserTitle);
|
||||
}
|
||||
|
||||
public void setCustomUserData(Object userData) {
|
||||
radioCustom.setUserData(userData);
|
||||
}
|
||||
|
||||
public JFXRadioButton getRadioCustom() {
|
||||
return radioCustom;
|
||||
}
|
||||
|
||||
public JFXTextField getTxtCustom() {
|
||||
return txtCustom;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,11 @@ public class NumberValidator extends ValidatorBase {
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
public NumberValidator(String message, boolean nullable) {
|
||||
super(message);
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
if (srcControl.get() instanceof TextInputControl) {
|
||||
@@ -43,7 +48,7 @@ public class NumberValidator extends ValidatorBase {
|
||||
TextInputControl textField = ((TextInputControl) srcControl.get());
|
||||
|
||||
if (StringUtils.isBlank(textField.getText()))
|
||||
hasErrors.set(false);
|
||||
hasErrors.set(nullable);
|
||||
else
|
||||
try {
|
||||
Integer.parseInt(textField.getText());
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.controls.JFXRippler;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
@DefaultProperty("container")
|
||||
public class RipplerContainer extends StackPane {
|
||||
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
|
||||
private final ObjectProperty<Paint> ripplerFill = new SimpleObjectProperty<>(this, "ripplerFill", null);
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);
|
||||
|
||||
private final StackPane buttonContainer = new StackPane();
|
||||
private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) {
|
||||
@Override
|
||||
protected Node getMask() {
|
||||
StackPane mask = new StackPane();
|
||||
mask.shapeProperty().bind(buttonContainer.shapeProperty());
|
||||
mask.backgroundProperty().bind(Bindings.createObjectBinding(() -> new Background(new BackgroundFill(Color.WHITE, buttonContainer.getBackground() != null && buttonContainer.getBackground().getFills().size() > 0 ? buttonContainer.getBackground().getFills().get(0).getRadii() : defaultRadii, buttonContainer.getBackground() != null && buttonContainer.getBackground().getFills().size() > 0 ? buttonContainer.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)), buttonContainer.backgroundProperty()));
|
||||
mask.resize(buttonContainer.getWidth() - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(), buttonContainer.getHeight() - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset());
|
||||
return mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
this.ripplerPane.setOnMousePressed(event -> {
|
||||
if (releaseManualRippler != null)
|
||||
releaseManualRippler.run();
|
||||
releaseManualRippler = null;
|
||||
createRipple(event.getX(), event.getY());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private Transition clickedAnimation;
|
||||
private final CornerRadii defaultRadii = new CornerRadii(3);
|
||||
private Runnable releaseManualRippler;
|
||||
|
||||
public RipplerContainer(@NamedArg("container") Node container) {
|
||||
setContainer(container);
|
||||
|
||||
getStyleClass().add("rippler-container");
|
||||
buttonContainer.getChildren().add(buttonRippler);
|
||||
setOnMousePressed(event -> {
|
||||
if (clickedAnimation != null) {
|
||||
clickedAnimation.setRate(1);
|
||||
clickedAnimation.play();
|
||||
}
|
||||
});
|
||||
setOnMouseReleased(event -> {
|
||||
if (clickedAnimation != null) {
|
||||
clickedAnimation.setRate(-1);
|
||||
clickedAnimation.play();
|
||||
}
|
||||
});
|
||||
focusedProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue) {
|
||||
if (!isPressed())
|
||||
buttonRippler.showOverlay();
|
||||
} else {
|
||||
buttonRippler.hideOverlay();
|
||||
}
|
||||
});
|
||||
pressedProperty().addListener(o -> buttonRippler.hideOverlay());
|
||||
setPickOnBounds(false);
|
||||
|
||||
buttonContainer.setPickOnBounds(false);
|
||||
buttonContainer.shapeProperty().bind(shapeProperty());
|
||||
buttonContainer.borderProperty().bind(borderProperty());
|
||||
buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
if (getBackground() == null || isJavaDefaultBackground(getBackground()) || isJavaDefaultClickedBackground(getBackground()))
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
|
||||
|
||||
try {
|
||||
return new Background(new BackgroundFill(getBackground() != null ? getBackground().getFills().get(0).getFill() : Color.TRANSPARENT,
|
||||
getBackground() != null ? getBackground().getFills().get(0).getRadii() : defaultRadii, Insets.EMPTY));
|
||||
} catch (Exception e) {
|
||||
return getBackground();
|
||||
}
|
||||
}, backgroundProperty()));
|
||||
|
||||
ripplerFillProperty().addListener((a, b, newValue) -> buttonRippler.setRipplerFill(newValue));
|
||||
if (getBackground() == null || isJavaDefaultBackground(getBackground()))
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
|
||||
|
||||
containerProperty().addListener(o -> updateChildren());
|
||||
updateChildren();
|
||||
|
||||
selectedProperty().addListener(o -> {
|
||||
if (isSelected()) setBackground(new Background(new BackgroundFill(getRipplerFill(), defaultRadii, null)));
|
||||
else setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
|
||||
});
|
||||
|
||||
setShape(Lang.apply(new Rectangle(), rectangle -> {
|
||||
rectangle.widthProperty().bind(widthProperty());
|
||||
rectangle.heightProperty().bind(heightProperty());
|
||||
}));
|
||||
}
|
||||
|
||||
protected void updateChildren() {
|
||||
getChildren().addAll(buttonContainer, getContainer());
|
||||
|
||||
for (int i = 1; i < getChildren().size(); ++i)
|
||||
getChildren().get(i).setPickOnBounds(false);
|
||||
}
|
||||
|
||||
private boolean isJavaDefaultBackground(Background background) {
|
||||
try {
|
||||
String firstFill = background.getFills().get(0).getFill().toString();
|
||||
return "0xffffffba".equals(firstFill) || "0xffffffbf".equals(firstFill) || "0xffffffbd".equals(firstFill);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJavaDefaultClickedBackground(Background background) {
|
||||
try {
|
||||
String firstFill = background.getFills().get(0).getFill().toString();
|
||||
return "0x039ed3ff".equals(firstFill);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Node getContainer() {
|
||||
return container.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> containerProperty() {
|
||||
return container;
|
||||
}
|
||||
|
||||
public void setContainer(Node container) {
|
||||
this.container.set(container);
|
||||
}
|
||||
|
||||
public Paint getRipplerFill() {
|
||||
return ripplerFill.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Paint> ripplerFillProperty() {
|
||||
return ripplerFill;
|
||||
}
|
||||
|
||||
public void setRipplerFill(Paint ripplerFill) {
|
||||
this.ripplerFill.set(ripplerFill);
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected.get();
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected.set(selected);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,12 @@ public final class Validator extends ValidatorBase {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public Validator(String message, Predicate<String> validator) {
|
||||
this(validator);
|
||||
|
||||
setMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
if (this.srcControl.get() instanceof TextInputControl) {
|
||||
|
||||
@@ -24,7 +24,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -55,7 +55,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
this.repository = repository;
|
||||
this.downloadProvider = downloadProvider;
|
||||
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/download/additional-installers.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/additional-installers.fxml");
|
||||
|
||||
lblGameVersion.setText(provider.getGameVersion());
|
||||
lblVersionName.setText(provider.getVersion().getId());
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManifest;
|
||||
import org.jackhuang.hmcl.game.MultiMCInstallVersionSettingTask;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public final class DownloadWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
profile = Settings.INSTANCE.getSelectedProfile();
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||
GameBuilder builder = profile.getDependency().gameBuilder();
|
||||
|
||||
builder.name((String) settings.get("name"));
|
||||
builder.gameVersion((String) settings.get("game"));
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
builder.version("forge", (String) settings.get("forge"));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
builder.version("liteloader", (String) settings.get("liteloader"));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version("optifine", (String) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync();
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = Lang.get(settings, ModpackPage.MODPACK_FILE, File.class, null);
|
||||
Modpack modpack = Lang.get(settings, ModpackPage.MODPACK_CURSEFORGE_MANIFEST, Modpack.class, null);
|
||||
String name = Lang.get(settings, ModpackPage.MODPACK_NAME, String.class, null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
Task finalizeTask = Task.of(() -> {
|
||||
profile.getRepository().refreshVersions();
|
||||
VersionSetting vs = profile.specializeVersionSetting(name);
|
||||
profile.getRepository().undoMark(name);
|
||||
if (vs != null)
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
});
|
||||
|
||||
if (modpack.getManifest() instanceof CurseManifest)
|
||||
return new CurseInstallTask(profile.getDependency(), selected, ((CurseManifest) modpack.getManifest()), name)
|
||||
.with(finalizeTask);
|
||||
else if (modpack.getManifest() instanceof HMCLModpackManifest)
|
||||
return new HMCLModpackInstallTask(profile, selected, modpack, name)
|
||||
.with(finalizeTask);
|
||||
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), selected, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.with(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name))
|
||||
.with(finalizeTask);
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
switch (Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1)) {
|
||||
case 0: return finishVersionDownloadingAsync(settings);
|
||||
case 1: return finishModpackInstallingAsync(settings);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new InstallTypePage(controller);
|
||||
case 1:
|
||||
int subStep = Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1);
|
||||
switch (subStep) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));
|
||||
case 1:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + subStep + ", settings: " + settings);
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final String PROFILE = "PROFILE";
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class InstallTypePage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
|
||||
@FXML private JFXListView<Object> list;
|
||||
|
||||
public InstallTypePage(WizardController controller) {
|
||||
this.controller = controller;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/dltype.fxml");
|
||||
list.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALL_TYPE, list.getSelectionModel().getSelectedIndex());
|
||||
controller.onNext();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALL_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Select an operation";
|
||||
}
|
||||
|
||||
public static final String INSTALL_TYPE = "INSTALL_TYPE";
|
||||
}
|
||||
@@ -89,9 +89,7 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "optifine", (String) settings.get("optifine")));
|
||||
|
||||
return ret.with(Task.of(v -> {
|
||||
profile.getRepository().refreshVersions();
|
||||
}));
|
||||
return ret.with(Task.of(profile.getRepository()::refreshVersions));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
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;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class InstallersPage extends StackPane implements WizardPage {
|
||||
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 lblForge;
|
||||
|
||||
@FXML
|
||||
private Label lblLiteLoader;
|
||||
|
||||
@FXML
|
||||
private Label lblOptiFine;
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtName;
|
||||
|
||||
@FXML
|
||||
private JFXButton btnInstall;
|
||||
|
||||
public InstallersPage(WizardController controller, GameRepository repository, DownloadProvider downloadProvider) {
|
||||
this.controller = controller;
|
||||
this.repository = repository;
|
||||
this.downloadProvider = downloadProvider;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/installers.fxml");
|
||||
|
||||
String gameVersion = (String) controller.getSettings().get("game");
|
||||
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
|
||||
hasVersion.setMessage(Main.i18n("version.already_exists"));
|
||||
txtName.getValidators().add(hasVersion);
|
||||
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
|
||||
txtName.setText(gameVersion);
|
||||
|
||||
btnForge.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 0);
|
||||
controller.onNext(new VersionsPage(controller, gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnLiteLoader.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 1);
|
||||
controller.onNext(new VersionsPage(controller, gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnOptiFine.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 2);
|
||||
controller.onNext(new VersionsPage(controller, gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Choose a game version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText("Current Game Version: " + controller.getSettings().get("game"));
|
||||
if (controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText("Forge Version: " + controller.getSettings().get("forge"));
|
||||
else
|
||||
lblForge.setText("Forge not installed");
|
||||
|
||||
if (controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText("LiteLoader Version: " + controller.getSettings().get("liteloader"));
|
||||
else
|
||||
lblLiteLoader.setText("LiteLoader not installed");
|
||||
|
||||
if (controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText("OptiFine Version: " + controller.getSettings().get("optifine"));
|
||||
else
|
||||
lblOptiFine.setText("OptiFine not installed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALLER_TYPE);
|
||||
}
|
||||
|
||||
public void onInstall() {
|
||||
controller.getSettings().put("name", txtName.getText());
|
||||
controller.onFinish();
|
||||
}
|
||||
|
||||
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WebStage;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public final class ModpackPage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
|
||||
private Modpack manifest = null;
|
||||
|
||||
@FXML
|
||||
private Region borderPane;
|
||||
|
||||
@FXML
|
||||
private Label lblName;
|
||||
|
||||
@FXML
|
||||
private Label lblVersion;
|
||||
|
||||
@FXML
|
||||
private Label lblAuthor;
|
||||
|
||||
@FXML
|
||||
private Label lblModpackLocation;
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtModpackName;
|
||||
|
||||
@FXML
|
||||
private JFXButton btnInstall;
|
||||
|
||||
public ModpackPage(WizardController controller) {
|
||||
this.controller = controller;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/modpack.fxml");
|
||||
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Main.i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) Platform.runLater(controller::onFinish);
|
||||
else {
|
||||
// TODO: original HMCL modpack support.
|
||||
controller.getSettings().put(MODPACK_FILE, selectedFile);
|
||||
lblModpackLocation.setText(selectedFile.getAbsolutePath());
|
||||
txtModpackName.getValidators().add(new Validator(Main.i18n("version.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)));
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_CURSEFORGE_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
} catch (Exception e) {
|
||||
// TODO
|
||||
txtModpackName.setText(Main.i18n("modpack.task.install.error"));
|
||||
}
|
||||
}
|
||||
|
||||
//FXUtils.limitHeight(borderPane, 100.0);
|
||||
FXUtils.limitWidth(borderPane, 500.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(MODPACK_FILE);
|
||||
}
|
||||
|
||||
public void onInstall() {
|
||||
if (!txtModpackName.validate()) return;
|
||||
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
|
||||
controller.onFinish();
|
||||
}
|
||||
|
||||
public void onDescribe() {
|
||||
if (manifest != null) {
|
||||
WebStage stage = new WebStage();
|
||||
stage.getWebView().getEngine().loadContent(manifest.getDescription());
|
||||
stage.setTitle(Main.i18n("modpack.wizard.step.3"));
|
||||
stage.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Main.i18n("modpack.task.install");
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE = "MODPACK_FILE";
|
||||
public static final String MODPACK_NAME = "MODPACK_NAME";
|
||||
public static final String MODPACK_CURSEFORGE_MANIFEST = "CURSEFORGE_MANIFEST";
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
@@ -59,7 +59,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
|
||||
this.versionList = downloadProvider.getVersionListById(libraryId);
|
||||
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
getChildren().setAll(spinner);
|
||||
list.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
controller.getSettings().put(libraryId, newValue.getRemoteVersion().getSelfVersion());
|
||||
@@ -70,7 +70,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
executor = versionList.refreshAsync(downloadProvider).subscribe(Schedulers.javafx(), v -> {
|
||||
executor = versionList.refreshAsync(downloadProvider).subscribe(Schedulers.javafx(), () -> {
|
||||
versionList.getVersions(gameVersion).stream()
|
||||
.sorted(RemoteVersion.RemoteVersionComparator.INSTANCE)
|
||||
.forEach(version -> {
|
||||
|
||||
@@ -21,7 +21,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -36,7 +36,7 @@ public final class VersionsPageItem extends StackPane {
|
||||
public VersionsPageItem(RemoteVersion<?> remoteVersion) {
|
||||
this.remoteVersion = remoteVersion;
|
||||
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/download/versions-list-item.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions-list-item.fxml");
|
||||
lblSelfVersion.setText(remoteVersion.getSelfVersion());
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.game.ModAdviser;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
@@ -59,7 +59,7 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
this.version = version;
|
||||
this.adviser = adviser;
|
||||
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/modpack/selection.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/modpack/selection.fxml");
|
||||
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft");
|
||||
treeView.setRoot(rootNode);
|
||||
treeView.setSelectionModel(NoneMultipleSelectionModel.getInstance());
|
||||
@@ -146,23 +146,23 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return MainKt.i18n("modpack.wizard.step.2.title");
|
||||
return Main.i18n("modpack.wizard.step.2.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
|
||||
private static final Map<String, String> TRANSLATION = Lang.mapOf(
|
||||
new Pair<>("minecraft/servers.dat", MainKt.i18n("modpack.files.servers_dat")),
|
||||
new Pair<>("minecraft/saves", MainKt.i18n("modpack.files.saves")),
|
||||
new Pair<>("minecraft/mods", MainKt.i18n("modpack.files.mods")),
|
||||
new Pair<>("minecraft/config", MainKt.i18n("modpack.files.config")),
|
||||
new Pair<>("minecraft/liteconfig", MainKt.i18n("modpack.files.liteconfig")),
|
||||
new Pair<>("minecraft/resourcepacks", MainKt.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/resources", MainKt.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/options.txt", MainKt.i18n("modpack.files.options_txt")),
|
||||
new Pair<>("minecraft/optionsshaders.txt", MainKt.i18n("modpack.files.optionsshaders_txt")),
|
||||
new Pair<>("minecraft/mods/VoxelMods", MainKt.i18n("modpack.files.mods.voxelmods")),
|
||||
new Pair<>("minecraft/dumps", MainKt.i18n("modpack.files.dumps")),
|
||||
new Pair<>("minecraft/blueprints", MainKt.i18n("modpack.files.blueprints")),
|
||||
new Pair<>("minecraft/scripts", MainKt.i18n("modpack.files.scripts"))
|
||||
new Pair<>("minecraft/servers.dat", Main.i18n("modpack.files.servers_dat")),
|
||||
new Pair<>("minecraft/saves", Main.i18n("modpack.files.saves")),
|
||||
new Pair<>("minecraft/mods", Main.i18n("modpack.files.mods")),
|
||||
new Pair<>("minecraft/config", Main.i18n("modpack.files.config")),
|
||||
new Pair<>("minecraft/liteconfig", Main.i18n("modpack.files.liteconfig")),
|
||||
new Pair<>("minecraft/resourcepacks", Main.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/resources", Main.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/options.txt", Main.i18n("modpack.files.options_txt")),
|
||||
new Pair<>("minecraft/optionsshaders.txt", Main.i18n("modpack.files.optionsshaders_txt")),
|
||||
new Pair<>("minecraft/mods/VoxelMods", Main.i18n("modpack.files.mods.voxelmods")),
|
||||
new Pair<>("minecraft/dumps", Main.i18n("modpack.files.dumps")),
|
||||
new Pair<>("minecraft/blueprints", Main.i18n("modpack.files.blueprints")),
|
||||
new Pair<>("minecraft/scripts", Main.i18n("modpack.files.scripts"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtilsKt;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
@@ -55,8 +55,8 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
|
||||
public ModpackInfoPage(WizardController controller, String version) {
|
||||
this.controller = controller;
|
||||
FXUtilsKt.loadFXML(this, "/assets/fxml/modpack/info.fxml");
|
||||
FXUtilsKt.smoothScrolling(scroll);
|
||||
FXUtils.loadFXML(this, "/assets/fxml/modpack/info.fxml");
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
txtModpackName.setText(version);
|
||||
txtModpackName.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.textProperty().addListener(e -> checkValidation());
|
||||
@@ -71,11 +71,11 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
|
||||
public void onNext() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(MainKt.i18n("modpack.wizard.step.initialization.save"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(MainKt.i18n("modpack"), "*.zip"));
|
||||
File file = fileChooser.showSaveDialog(Controllers.INSTANCE.getStage());
|
||||
fileChooser.setTitle(Main.i18n("modpack.wizard.step.initialization.save"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip"));
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
if (file == null) {
|
||||
Controllers.INSTANCE.navigate(null);
|
||||
Controllers.navigate(null);
|
||||
return;
|
||||
}
|
||||
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
|
||||
@@ -99,7 +99,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return MainKt.i18n("modpack.wizard.step.1.title");
|
||||
return Main.i18n("modpack.wizard.step.1.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_NAME = "modpack.name";
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public interface AbstractWizardDisplayer extends WizardDisplayer {
|
||||
WizardController getWizardController();
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
AtomicInteger finishedTasks = new AtomicInteger(0);
|
||||
|
||||
TaskExecutor executor = task.with(Task.of(Schedulers.javafx(), this::navigateToSuccess)).executor(e -> new TaskListener() {
|
||||
@Override
|
||||
public void onReady(Task task) {
|
||||
Platform.runLater(() -> progressBar.setProgress(finishedTasks.get() * 1.0 / e.getRunningTasks()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(Task task) {
|
||||
Platform.runLater(() -> {
|
||||
label.setText(task.getName());
|
||||
progressBar.setProgress(finishedTasks.incrementAndGet() * 1.0 / e.getRunningTasks());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(Task task, Throwable throwable) {
|
||||
Platform.runLater(() -> {
|
||||
label.setText(task.getName());
|
||||
progressBar.setProgress(finishedTasks.incrementAndGet() * 1.0 / e.getRunningTasks());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
Platform.runLater(AbstractWizardDisplayer.this::navigateToSuccess);
|
||||
}
|
||||
});
|
||||
getCancelQueue().add(executor);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onCancel() {
|
||||
while (!getCancelQueue().isEmpty()) {
|
||||
Object x = getCancelQueue().poll();
|
||||
if (x instanceof TaskExecutor) ((TaskExecutor) x).cancel();
|
||||
else if (x instanceof Thread) ((Thread) x).interrupt();
|
||||
else throw new IllegalStateException("Unrecognized cancel queue element: " + x);
|
||||
}
|
||||
}
|
||||
|
||||
default void navigateToSuccess() {
|
||||
navigateTo(new Label("Successful"), Navigation.NavigationDirection.FINISH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXToolbar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class DefaultWizardDisplayer extends StackPane implements AbstractWizardDisplayer {
|
||||
|
||||
private final String prefix;
|
||||
private final WizardController wizardController;
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private Node nowPage;
|
||||
|
||||
private TransitionHandler transitionHandler;
|
||||
|
||||
@FXML
|
||||
private StackPane root;
|
||||
@FXML
|
||||
private JFXButton backButton;
|
||||
@FXML
|
||||
private JFXToolbar toolbar;
|
||||
@FXML
|
||||
private JFXButton refreshButton;
|
||||
@FXML
|
||||
private Label titleLabel;
|
||||
|
||||
public DefaultWizardDisplayer(String prefix, WizardProvider wizardProvider) {
|
||||
this.prefix = prefix;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/wizard.fxml");
|
||||
toolbar.setEffect(null);
|
||||
|
||||
wizardController = new WizardController(this);
|
||||
wizardController.setProvider(wizardProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WizardController getWizardController() {
|
||||
return wizardController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queue<Object> getCancelQueue() {
|
||||
return cancelQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
backButton.setDisable(!wizardController.canPrev());
|
||||
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
String title = StringUtils.isBlank(prefix) ? "" : prefix + " - ";
|
||||
if (page instanceof WizardPage)
|
||||
titleLabel.setText(title + ((WizardPage) page).getTitle());
|
||||
refreshButton.setVisible(page instanceof Refreshable);
|
||||
nowPage = page;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
transitionHandler = new TransitionHandler(root);
|
||||
wizardController.onStart();
|
||||
}
|
||||
|
||||
public void back() {
|
||||
wizardController.onPrev(true);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
wizardController.onCancel();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
((Refreshable) nowPage).refresh();
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public final class Summary {
|
||||
private final Object result;
|
||||
|
||||
public Summary(String[] items, Object result) {
|
||||
JFXListView view = new JFXListView<String>();
|
||||
JFXListView<String> view = new JFXListView<>();
|
||||
view.getItems().addAll(items);
|
||||
|
||||
this.component = view;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Stack;
|
||||
|
||||
public class WizardController implements Navigation {
|
||||
private final WizardDisplayer displayer;
|
||||
private WizardProvider provider = null;
|
||||
private final Map<String, Object> settings = new HashMap<>();
|
||||
private final Stack<Node> pages = new Stack<>();
|
||||
|
||||
public WizardController(WizardDisplayer displayer) {
|
||||
this.displayer = displayer;
|
||||
}
|
||||
|
||||
public Map<String, Object> getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public WizardDisplayer getDisplayer() {
|
||||
return displayer;
|
||||
}
|
||||
|
||||
public void setProvider(WizardProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
Objects.requireNonNull(provider);
|
||||
|
||||
settings.clear();
|
||||
provider.start(settings);
|
||||
|
||||
pages.clear();
|
||||
Node page = navigatingTo(0);
|
||||
pages.push(page);
|
||||
|
||||
if (page instanceof WizardPage)
|
||||
((WizardPage) page).onNavigate(settings);
|
||||
|
||||
displayer.onStart();
|
||||
displayer.navigateTo(page, NavigationDirection.START);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext() {
|
||||
onNext(navigatingTo(pages.size()));
|
||||
}
|
||||
|
||||
public void onNext(Node page) {
|
||||
pages.push(page);
|
||||
|
||||
if (page instanceof WizardPage)
|
||||
((WizardPage) page).onNavigate(settings);
|
||||
|
||||
displayer.navigateTo(page, NavigationDirection.NEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrev(boolean cleanUp) {
|
||||
Node page = pages.pop();
|
||||
if (cleanUp && page instanceof WizardPage)
|
||||
((WizardPage) page).cleanup(settings);
|
||||
|
||||
Node prevPage = pages.peek();
|
||||
if (prevPage instanceof WizardPage)
|
||||
((WizardPage) prevPage).onNavigate(settings);
|
||||
|
||||
displayer.navigateTo(prevPage, NavigationDirection.PREVIOUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPrev() {
|
||||
return pages.size() > 1;
|
||||
}
|
||||
|
||||
@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);
|
||||
else if (result instanceof Task) displayer.handleTask(settings, ((Task) result));
|
||||
else throw new IllegalStateException("Unrecognized wizard result: " + result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd() {
|
||||
settings.clear();
|
||||
pages.clear();
|
||||
displayer.onEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
displayer.onCancel();
|
||||
onEnd();
|
||||
}
|
||||
|
||||
protected Node navigatingTo(int step) {
|
||||
return provider.createPage(this, step, settings);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -100,24 +100,24 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
if (!(ver instanceof IntVersionNumber))
|
||||
return;
|
||||
IntVersionNumber version = (IntVersionNumber) ver;
|
||||
checker.requestDownloadLink().then(Task.of(v -> {
|
||||
Map<String, String> map = v.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
|
||||
checker.requestDownloadLink().then(Task.of(variables -> {
|
||||
Map<String, String> map = variables.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
|
||||
|
||||
if (MessageBox.confirm(MainKt.i18n("update.newest_version") + version.get(0) + "." + version.get(1) + "." + version.get(2) + "\n"
|
||||
+ MainKt.i18n("update.should_open_link"),
|
||||
if (MessageBox.confirm(Main.i18n("update.newest_version") + version.get(0) + "." + version.get(1) + "." + version.get(2) + "\n"
|
||||
+ Main.i18n("update.should_open_link"),
|
||||
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION)
|
||||
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("jarsha1"))
|
||||
hash = map.get("jarsha1");
|
||||
Controllers.INSTANCE.dialog(MainKt.i18n("ui.message.downloading"));
|
||||
Controllers.dialog(Main.i18n("ui.message.downloading"));
|
||||
if (new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash).test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.INSTANCE.closeDialog();
|
||||
Controllers.closeDialog();
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
@@ -126,13 +126,13 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
String hash = null;
|
||||
if (map.containsKey("packsha1"))
|
||||
hash = map.get("packsha1");
|
||||
Controllers.INSTANCE.dialog(MainKt.i18n("ui.message.downloading"));
|
||||
Controllers.dialog(Main.i18n("ui.message.downloading"));
|
||||
if (new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash).test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.INSTANCE.closeDialog();
|
||||
Controllers.closeDialog();
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
|
||||
OperatingSystem.setClipboard(url);
|
||||
MessageBox.show(MainKt.i18n("update.no_browser"));
|
||||
MessageBox.show(Main.i18n("update.no_browser"));
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
@@ -53,7 +53,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
URL url = requestDownloadLink();
|
||||
if (url == null) return;
|
||||
File newf = new File(url.getFile());
|
||||
Controllers.INSTANCE.dialog(MainKt.i18n("ui.message.downloading"));
|
||||
Controllers.dialog(Main.i18n("ui.message.downloading"));
|
||||
if (new FileDownloadTask(url, newf).test()) {
|
||||
try {
|
||||
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
|
||||
@@ -64,7 +64,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.INSTANCE.closeDialog();
|
||||
Controllers.closeDialog();
|
||||
}
|
||||
|
||||
private static String getRealPath() {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.jackhuang.hmcl.MainKt;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.OutOfDateEvent;
|
||||
@@ -77,7 +77,7 @@ public final class UpdateChecker {
|
||||
if (value == null) {
|
||||
Logging.LOG.warning("Failed to check update...");
|
||||
if (showMessage)
|
||||
MessageBox.show(MainKt.i18n("update.failed"));
|
||||
MessageBox.show(Main.i18n("update.failed"));
|
||||
} else if (base.compareTo(value) < 0)
|
||||
outOfDate = true;
|
||||
if (outOfDate)
|
||||
|
||||
@@ -1,102 +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
|
||||
|
||||
import javafx.application.Application
|
||||
import javafx.application.Platform
|
||||
import javafx.stage.Stage
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import org.jackhuang.hmcl.ui.Controllers
|
||||
import org.jackhuang.hmcl.ui.runOnUiThread
|
||||
import org.jackhuang.hmcl.util.Constants
|
||||
import org.jackhuang.hmcl.util.Logging.LOG
|
||||
import org.jackhuang.hmcl.util.NetworkUtils
|
||||
import org.jackhuang.hmcl.util.OperatingSystem
|
||||
import java.io.File
|
||||
import java.util.logging.Level
|
||||
|
||||
fun i18n(key: String): String {
|
||||
try {
|
||||
return Main.RESOURCE_BUNDLE.getString(key)
|
||||
} catch (e: Exception) {
|
||||
LOG.log(Level.SEVERE, "Cannot find key $key in resource bundle", e)
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
class Main : Application() {
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
// When launcher visibility is set to "hide and reopen" without [Platform.implicitExit] = false,
|
||||
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
|
||||
Platform.setImplicitExit(false)
|
||||
|
||||
Controllers.initialize(stage)
|
||||
|
||||
stage.isResizable = false
|
||||
stage.scene = Controllers.scene
|
||||
stage.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"
|
||||
@JvmField val NAME = "HMCL"
|
||||
@JvmField val TITLE = "$NAME $VERSION"
|
||||
@JvmField val APPDATA = getWorkingDirectory("hmcl")
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
NetworkUtils.setUserAgentSupplier { "Hello Minecraft! Launcher" }
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
|
||||
launch(Main::class.java, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getWorkingDirectory(folder: String): File {
|
||||
val userhome = System.getProperty("user.home", ".")
|
||||
return when (OperatingSystem.CURRENT_OS) {
|
||||
OperatingSystem.LINUX -> File(userhome, ".$folder/")
|
||||
OperatingSystem.WINDOWS -> {
|
||||
val appdata: String? = System.getenv("APPDATA")
|
||||
File(appdata ?: userhome, ".$folder/")
|
||||
}
|
||||
OperatingSystem.OSX -> File(userhome, "Library/Application Support/" + folder)
|
||||
else -> File(userhome, "$folder/")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft")
|
||||
|
||||
fun stop() = runOnUiThread {
|
||||
stopWithoutJavaFXPlatform()
|
||||
Platform.exit()
|
||||
}
|
||||
|
||||
fun stopWithoutJavaFXPlatform() = runOnUiThread {
|
||||
Controllers.stage.close()
|
||||
Schedulers.shutdown()
|
||||
}
|
||||
|
||||
val RESOURCE_BUNDLE = Settings.INSTANCE.locale.resourceBundle
|
||||
}
|
||||
}
|
||||
@@ -1,128 +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
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXProgressBar
|
||||
import com.jfoenix.controls.JFXRadioButton
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.fxml.FXML
|
||||
import javafx.geometry.Rectangle2D
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ToggleGroup
|
||||
import javafx.scene.effect.BlurType
|
||||
import javafx.scene.effect.DropShadow
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.Pane
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.paint.Color
|
||||
import org.jackhuang.hmcl.auth.Account
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.game.AccountHelper
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane() {
|
||||
@FXML lateinit var icon: Pane
|
||||
@FXML lateinit var content: VBox
|
||||
@FXML lateinit var header: StackPane
|
||||
@FXML lateinit var body: StackPane
|
||||
@FXML lateinit var btnDelete: JFXButton
|
||||
@FXML lateinit var btnRefresh: JFXButton
|
||||
@FXML lateinit var lblUser: Label
|
||||
@FXML lateinit var chkSelected: JFXRadioButton
|
||||
@FXML lateinit var lblType: Label
|
||||
@FXML lateinit var pgsSkin: JFXProgressBar
|
||||
@FXML lateinit var portraitView: ImageView
|
||||
@FXML lateinit var buttonPane: HBox
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/account-item.fxml")
|
||||
|
||||
limitWidth(160.0)
|
||||
limitHeight(156.0)
|
||||
|
||||
effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0)
|
||||
|
||||
chkSelected.toggleGroup = group
|
||||
btnDelete.graphic = SVG.delete("black", 15.0, 15.0)
|
||||
btnRefresh.graphic = SVG.refresh("black", 15.0, 15.0)
|
||||
|
||||
// create content
|
||||
val headerColor = getDefaultColor(i % 12)
|
||||
header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor
|
||||
|
||||
// create image view
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 - 32.0 }, header.boundsInParentProperty(), icon.heightProperty()))
|
||||
|
||||
chkSelected.properties["account"] = account
|
||||
chkSelected.isSelected = Settings.INSTANCE.selectedAccount == account
|
||||
lblUser.text = account.username
|
||||
lblType.text = accountType(account)
|
||||
|
||||
if (account is YggdrasilAccount) {
|
||||
btnRefresh.setOnMouseClicked {
|
||||
pgsSkin.isVisible = true
|
||||
AccountHelper.refreshSkinAsync(account)
|
||||
.subscribe(Schedulers.javafx()) { loadSkin() }
|
||||
}
|
||||
AccountHelper.loadSkinAsync(account)
|
||||
.subscribe(Schedulers.javafx()) { loadSkin() }
|
||||
}
|
||||
|
||||
if (account is OfflineAccount) { // Offline Account cannot be refreshed,
|
||||
buttonPane.children -= btnRefresh
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSkin() {
|
||||
if (account !is YggdrasilAccount)
|
||||
return
|
||||
pgsSkin.isVisible = false
|
||||
portraitView.viewport = AccountHelper.getViewport(4.0)
|
||||
portraitView.image = AccountHelper.getSkin(account, 4.0)
|
||||
portraitView.limitSize(32.0, 32.0)
|
||||
}
|
||||
|
||||
private fun getDefaultColor(i: Int): String {
|
||||
var color = "#FFFFFF"
|
||||
when (i) {
|
||||
0 -> color = "#8F3F7E"
|
||||
1 -> color = "#B5305F"
|
||||
2 -> color = "#CE584A"
|
||||
3 -> color = "#DB8D5C"
|
||||
4 -> color = "#DA854E"
|
||||
5 -> color = "#E9AB44"
|
||||
6 -> color = "#FEE435"
|
||||
7 -> color = "#99C286"
|
||||
8 -> color = "#01A05E"
|
||||
9 -> color = "#4A8895"
|
||||
10 -> color = "#16669B"
|
||||
11 -> color = "#2F65A5"
|
||||
12 -> color = "#4E6A9C"
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
return color
|
||||
}
|
||||
}
|
||||
@@ -1,169 +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
|
||||
|
||||
import com.jfoenix.controls.*
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.control.ToggleGroup
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.auth.Account
|
||||
import org.jackhuang.hmcl.auth.MultiCharacterSelector
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||
import org.jackhuang.hmcl.auth.OfflineAccountFactory
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory
|
||||
import org.jackhuang.hmcl.game.HMCLMultiCharacterSelector
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
|
||||
import org.jackhuang.hmcl.util.onChange
|
||||
import org.jackhuang.hmcl.util.onChangeAndOperate
|
||||
import org.jackhuang.hmcl.util.taskResult
|
||||
|
||||
class AccountsPage() : StackPane(), DecoratorPage {
|
||||
private val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
|
||||
override fun titleProperty() = titleProperty
|
||||
|
||||
@FXML lateinit var scrollPane: ScrollPane
|
||||
@FXML lateinit var masonryPane: JFXMasonryPane
|
||||
@FXML lateinit var dialog: JFXDialog
|
||||
@FXML lateinit var txtUsername: JFXTextField
|
||||
@FXML lateinit var txtPassword: JFXPasswordField
|
||||
@FXML lateinit var lblCreationWarning: Label
|
||||
@FXML lateinit var cboType: JFXComboBox<String>
|
||||
@FXML lateinit var progressBar: JFXProgressBar
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/account.fxml")
|
||||
children.remove(dialog)
|
||||
dialog.dialogContainer = this
|
||||
|
||||
scrollPane.smoothScrolling()
|
||||
|
||||
txtUsername.setValidateWhileTextChanged()
|
||||
txtPassword.setValidateWhileTextChanged()
|
||||
|
||||
cboType.selectionModel.selectedIndexProperty().onChange {
|
||||
val visible = it != 0
|
||||
txtPassword.isVisible = visible
|
||||
}
|
||||
cboType.selectionModel.select(0)
|
||||
|
||||
txtPassword.setOnAction { onCreationAccept() }
|
||||
txtUsername.setOnAction { onCreationAccept() }
|
||||
|
||||
Settings.INSTANCE.selectedAccountProperty().onChangeAndOperate { account ->
|
||||
masonryPane.children.forEach { node ->
|
||||
if (node is AccountItem) {
|
||||
node.chkSelected.isSelected = account?.username == node.lblUser.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadAccounts()
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
addNewAccount()
|
||||
}
|
||||
|
||||
fun loadAccounts() {
|
||||
val children = mutableListOf<Node>()
|
||||
var i = 0
|
||||
val group = ToggleGroup()
|
||||
for ((_, account) in Settings.INSTANCE.getAccounts()) {
|
||||
children += buildNode(++i, account, group)
|
||||
}
|
||||
group.selectedToggleProperty().onChange {
|
||||
if (it != null)
|
||||
Settings.INSTANCE.selectedAccount = it.properties["account"] as Account
|
||||
}
|
||||
masonryPane.resetChildren(children)
|
||||
Platform.runLater {
|
||||
masonryPane.requestLayout()
|
||||
scrollPane.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node {
|
||||
return AccountItem(i, account, group).apply {
|
||||
btnDelete.setOnMouseClicked {
|
||||
Settings.INSTANCE.deleteAccount(account.username)
|
||||
Platform.runLater(this@AccountsPage::loadAccounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addNewAccount() {
|
||||
txtUsername.text = ""
|
||||
txtPassword.text = ""
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun onCreationAccept() {
|
||||
val type = cboType.selectionModel.selectedIndex
|
||||
val username = txtUsername.text
|
||||
val password = txtPassword.text
|
||||
progressBar.isVisible = true
|
||||
lblCreationWarning.text = ""
|
||||
taskResult("create_account") {
|
||||
try {
|
||||
val account = when (type) {
|
||||
0 -> OfflineAccountFactory.INSTANCE.fromUsername(username)
|
||||
1 -> YggdrasilAccountFactory.INSTANCE.fromUsername(username, password)
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.proxy)
|
||||
account
|
||||
} catch (e: Exception) {
|
||||
e
|
||||
}
|
||||
}.subscribe(Schedulers.javafx()) {
|
||||
val account: Any = it["create_account"]
|
||||
if (account is Account) {
|
||||
Settings.INSTANCE.addAccount(account)
|
||||
dialog.close()
|
||||
loadAccounts()
|
||||
} else if (account is InvalidCredentialsException) {
|
||||
lblCreationWarning.text = i18n("login.wrong_password")
|
||||
} else if (account is Exception) {
|
||||
lblCreationWarning.text = account.localizedMessage
|
||||
}
|
||||
progressBar.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
fun onCreationCancel() {
|
||||
dialog.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun accountType(account: Account) =
|
||||
when(account) {
|
||||
is OfflineAccount -> i18n("login.methods.offline")
|
||||
is YggdrasilAccount -> i18n("login.methods.yggdrasil")
|
||||
else -> throw Error("${i18n("login.methods.no_method")}: $account")
|
||||
}
|
||||
@@ -1,55 +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
|
||||
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.layout.Pane
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
|
||||
class AdvancedListBox: ScrollPane() {
|
||||
val container = VBox()
|
||||
|
||||
init {
|
||||
content = container
|
||||
|
||||
smoothScrolling()
|
||||
|
||||
isFitToHeight = true
|
||||
isFitToWidth = true
|
||||
hbarPolicy = ScrollBarPolicy.NEVER
|
||||
|
||||
container.spacing = 5.0
|
||||
container.styleClass += "advanced-list-box-content"
|
||||
}
|
||||
|
||||
fun add(child: Node): AdvancedListBox {
|
||||
if (child is Pane) {
|
||||
container.children += child
|
||||
} else {
|
||||
val pane = StackPane()
|
||||
pane.styleClass += "advanced-list-box-item"
|
||||
pane.children.setAll(child)
|
||||
container.children += pane
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun startCategory(category: String): AdvancedListBox = add(ClassTitle(category))
|
||||
}
|
||||
@@ -1,81 +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
|
||||
|
||||
import com.jfoenix.controls.JFXDialog
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.Region
|
||||
import javafx.stage.Stage
|
||||
import org.jackhuang.hmcl.Main
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.util.JavaVersion
|
||||
import org.jackhuang.hmcl.util.task
|
||||
|
||||
object Controllers {
|
||||
lateinit var scene: Scene private set
|
||||
lateinit var stage: Stage private set
|
||||
|
||||
val mainPane = MainPage()
|
||||
val settingsPane by lazy { SettingsPage() }
|
||||
val versionPane by lazy { VersionPage() }
|
||||
|
||||
lateinit var leftPaneController: LeftPaneController
|
||||
|
||||
lateinit var decorator: Decorator
|
||||
|
||||
fun initialize(stage: Stage) {
|
||||
this.stage = stage
|
||||
|
||||
decorator = Decorator(stage, mainPane, Main.TITLE, max = false)
|
||||
decorator.showPage(null)
|
||||
leftPaneController = LeftPaneController(decorator.leftPane)
|
||||
|
||||
Settings.INSTANCE.onProfileLoading()
|
||||
task { JavaVersion.initialize() }.start()
|
||||
|
||||
decorator.isCustomMaximize = false
|
||||
|
||||
scene = Scene(decorator, 804.0, 521.0)
|
||||
scene.stylesheets.addAll(*stylesheets)
|
||||
stage.minWidth = 800.0
|
||||
stage.maxWidth = 800.0
|
||||
stage.maxHeight = 480.0
|
||||
stage.minHeight = 480.0
|
||||
|
||||
stage.icons += Image("/assets/img/icon.png")
|
||||
stage.title = Main.TITLE
|
||||
}
|
||||
|
||||
fun dialog(content: Region): JFXDialog {
|
||||
return decorator.showDialog(content)
|
||||
}
|
||||
|
||||
fun dialog(text: String) {
|
||||
dialog(MessageDialogPane(text, decorator.dialog))
|
||||
}
|
||||
|
||||
fun closeDialog() {
|
||||
decorator.dialog.close()
|
||||
}
|
||||
|
||||
fun navigate(node: Node?) {
|
||||
decorator.showPage(node)
|
||||
}
|
||||
}
|
||||
@@ -1,457 +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
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXDialog
|
||||
import com.jfoenix.controls.JFXDrawer
|
||||
import com.jfoenix.controls.JFXHamburger
|
||||
import com.jfoenix.effects.JFXDepthManager
|
||||
import com.jfoenix.svg.SVGGlyph
|
||||
import javafx.animation.Timeline
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.property.BooleanProperty
|
||||
import javafx.beans.property.ObjectProperty
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.fxml.FXML
|
||||
import javafx.geometry.BoundingBox
|
||||
import javafx.geometry.Bounds
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.Cursor
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.Tooltip
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.layout.*
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.stage.Screen
|
||||
import javafx.stage.Stage
|
||||
import javafx.stage.StageStyle
|
||||
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.wizard.*
|
||||
import org.jackhuang.hmcl.util.getValue
|
||||
import org.jackhuang.hmcl.util.setValue
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val mainPage: Node, title: String, private val max: Boolean = true, min: Boolean = true) : StackPane(), AbstractWizardDisplayer {
|
||||
override val wizardController: WizardController = WizardController(this)
|
||||
|
||||
private var xOffset: Double = 0.0
|
||||
private var yOffset: Double = 0.0
|
||||
private var newX: Double = 0.0
|
||||
private var newY: Double = 0.0
|
||||
private var initX: Double = 0.0
|
||||
private var initY: Double = 0.0
|
||||
private var allowMove: Boolean = false
|
||||
private var isDragging: Boolean = false
|
||||
private var windowDecoratorAnimation: Timeline? = null
|
||||
private var dialogShown = false
|
||||
@FXML lateinit var contentPlaceHolder: StackPane
|
||||
@FXML lateinit var drawerWrapper: StackPane
|
||||
@FXML lateinit var titleContainer: BorderPane
|
||||
@FXML lateinit var leftRootPane: BorderPane
|
||||
@FXML lateinit var buttonsContainer: HBox
|
||||
@FXML lateinit var backNavButton: JFXButton
|
||||
@FXML lateinit var refreshNavButton: JFXButton
|
||||
@FXML lateinit var closeNavButton: JFXButton
|
||||
@FXML lateinit var refreshMenuButton: JFXButton
|
||||
@FXML lateinit var addMenuButton: JFXButton
|
||||
@FXML lateinit var titleLabel: Label
|
||||
@FXML lateinit var lblTitle: Label
|
||||
@FXML lateinit var leftPane: AdvancedListBox
|
||||
@FXML lateinit var drawer: JFXDrawer
|
||||
@FXML lateinit var titleBurgerContainer: StackPane
|
||||
@FXML lateinit var titleBurger: JFXHamburger
|
||||
@FXML lateinit var dialog: JFXDialog
|
||||
|
||||
private val onCloseButtonActionProperty: ObjectProperty<Runnable> = SimpleObjectProperty(Runnable { Main.stop() })
|
||||
@JvmName("onCloseButtonActionProperty") get
|
||||
var onCloseButtonAction: Runnable by onCloseButtonActionProperty
|
||||
|
||||
val customMaximizeProperty: BooleanProperty = SimpleBooleanProperty(false)
|
||||
@JvmName("customMaximizeProperty") get
|
||||
var isCustomMaximize: Boolean by customMaximizeProperty
|
||||
|
||||
private var maximized: Boolean = false
|
||||
private var originalBox: BoundingBox? = null
|
||||
private var maximizedBox: BoundingBox? = null
|
||||
@FXML lateinit var btnMin: JFXButton
|
||||
@FXML lateinit var btnMax: JFXButton
|
||||
@FXML lateinit var btnClose: JFXButton
|
||||
private val minus = 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)
|
||||
.apply { setSize(12.0, 2.0); translateY = 4.0 }
|
||||
private val resizeMax = 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)
|
||||
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||
private val resizeMin = SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE)
|
||||
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||
private val close = SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE)
|
||||
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||
|
||||
val animationHandler: TransitionHandler
|
||||
override val cancelQueue: Queue<Any> = ConcurrentLinkedQueue<Any>()
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/decorator.fxml")
|
||||
|
||||
this.primaryStage.initStyle(StageStyle.UNDECORATED)
|
||||
btnClose.graphic = close
|
||||
btnMin.graphic = minus
|
||||
btnMax.graphic = resizeMax
|
||||
|
||||
lblTitle.text = title
|
||||
|
||||
buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)))
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent ->
|
||||
if (mouseEvent.clickCount == 2) {
|
||||
btnMax.fire()
|
||||
}
|
||||
}
|
||||
|
||||
drawerWrapper.children -= dialog
|
||||
dialog.dialogContainer = drawerWrapper
|
||||
dialog.setOnDialogClosed { dialogShown = false }
|
||||
dialog.setOnDialogOpened { dialogShown = true }
|
||||
|
||||
if (!min) buttonsContainer.children.remove(btnMin)
|
||||
if (!max) buttonsContainer.children.remove(btnMax)
|
||||
|
||||
JFXDepthManager.setDepth(titleContainer, 1)
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { this.allowMove = true }
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false }
|
||||
|
||||
animationHandler = TransitionHandler(contentPlaceHolder)
|
||||
|
||||
(lookup("#contentPlaceHolderRoot") as Pane).setOverflowHidden()
|
||||
drawerWrapper.setOverflowHidden()
|
||||
}
|
||||
|
||||
fun onMouseMoved(mouseEvent: MouseEvent) {
|
||||
if (!this.primaryStage.isMaximized && !this.primaryStage.isFullScreen && !this.maximized) {
|
||||
if (!this.primaryStage.isResizable) {
|
||||
this.updateInitMouseValues(mouseEvent)
|
||||
} else {
|
||||
val x = mouseEvent.x
|
||||
val y = mouseEvent.y
|
||||
val boundsInParent = this.boundsInParent
|
||||
if (this.border != null && this.border.strokes.size > 0) {
|
||||
val borderWidth = this.contentPlaceHolder.snappedLeftInset()
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
this.cursor = Cursor.NE_RESIZE
|
||||
} else if (y > this.height - borderWidth) {
|
||||
this.cursor = Cursor.SE_RESIZE
|
||||
} else {
|
||||
this.cursor = Cursor.E_RESIZE
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
this.cursor = Cursor.NW_RESIZE
|
||||
} else if (y > this.height - borderWidth) {
|
||||
this.cursor = Cursor.SW_RESIZE
|
||||
} else {
|
||||
this.cursor = Cursor.W_RESIZE
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
this.cursor = Cursor.N_RESIZE
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
this.cursor = Cursor.S_RESIZE
|
||||
} else {
|
||||
this.cursor = Cursor.DEFAULT
|
||||
}
|
||||
|
||||
this.updateInitMouseValues(mouseEvent)
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
this.cursor = Cursor.DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
fun onMouseReleased() {
|
||||
this.isDragging = false
|
||||
}
|
||||
|
||||
fun onMouseDragged(mouseEvent: MouseEvent) {
|
||||
this.isDragging = true
|
||||
if (mouseEvent.isPrimaryButtonDown && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen && !mouseEvent.isStillSincePress && !this.primaryStage.isMaximized && !this.maximized) {
|
||||
this.newX = mouseEvent.screenX
|
||||
this.newY = mouseEvent.screenY
|
||||
val deltax = this.newX - this.initX
|
||||
val deltay = this.newY - this.initY
|
||||
val cursor = this.cursor
|
||||
if (Cursor.E_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.width + deltax)
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.NE_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.height - deltay)) {
|
||||
this.primaryStage.y = this.primaryStage.y + deltay
|
||||
}
|
||||
|
||||
this.setStageWidth(this.primaryStage.width + deltax)
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.SE_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.width + deltax)
|
||||
this.setStageHeight(this.primaryStage.height + deltay)
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.S_RESIZE == cursor) {
|
||||
this.setStageHeight(this.primaryStage.height + deltay)
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.W_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.width - deltax)) {
|
||||
this.primaryStage.x = this.primaryStage.x + deltax
|
||||
}
|
||||
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.SW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.width - deltax)) {
|
||||
this.primaryStage.x = this.primaryStage.x + deltax
|
||||
}
|
||||
|
||||
this.setStageHeight(this.primaryStage.height + deltay)
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.NW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.width - deltax)) {
|
||||
this.primaryStage.x = this.primaryStage.x + deltax
|
||||
}
|
||||
|
||||
if (this.setStageHeight(this.primaryStage.height - deltay)) {
|
||||
this.primaryStage.y = this.primaryStage.y + deltay
|
||||
}
|
||||
|
||||
mouseEvent.consume()
|
||||
} else if (Cursor.N_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.height - deltay)) {
|
||||
this.primaryStage.y = this.primaryStage.y + deltay
|
||||
}
|
||||
|
||||
mouseEvent.consume()
|
||||
} else if (this.allowMove) {
|
||||
this.primaryStage.x = mouseEvent.screenX - this.xOffset
|
||||
this.primaryStage.y = mouseEvent.screenY - this.yOffset
|
||||
mouseEvent.consume()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onMin() {
|
||||
this.primaryStage.isIconified = true
|
||||
}
|
||||
|
||||
fun onMax() {
|
||||
if (!max) return
|
||||
if (!this.isCustomMaximize) {
|
||||
this.primaryStage.isMaximized = !this.primaryStage.isMaximized
|
||||
this.maximized = this.primaryStage.isMaximized
|
||||
if (this.primaryStage.isMaximized) {
|
||||
this.btnMax.graphic = resizeMin
|
||||
this.btnMax.tooltip = Tooltip("Restore Down")
|
||||
} else {
|
||||
this.btnMax.graphic = resizeMax
|
||||
this.btnMax.tooltip = Tooltip("Maximize")
|
||||
}
|
||||
} else {
|
||||
if (!this.maximized) {
|
||||
this.originalBox = BoundingBox(primaryStage.x, primaryStage.y, primaryStage.width, primaryStage.height)
|
||||
val screen = Screen.getScreensForRectangle(primaryStage.x, primaryStage.y, primaryStage.width, primaryStage.height)[0] as Screen
|
||||
val bounds = screen.visualBounds
|
||||
this.maximizedBox = BoundingBox(bounds.minX, bounds.minY, bounds.width, bounds.height)
|
||||
primaryStage.x = this.maximizedBox!!.minX
|
||||
primaryStage.y = this.maximizedBox!!.minY
|
||||
primaryStage.width = this.maximizedBox!!.width
|
||||
primaryStage.height = this.maximizedBox!!.height
|
||||
this.btnMax.graphic = resizeMin
|
||||
this.btnMax.tooltip = Tooltip("Restore Down")
|
||||
} else {
|
||||
primaryStage.x = this.originalBox!!.minX
|
||||
primaryStage.y = this.originalBox!!.minY
|
||||
primaryStage.width = this.originalBox!!.width
|
||||
primaryStage.height = this.originalBox!!.height
|
||||
this.originalBox = null
|
||||
this.btnMax.graphic = resizeMax
|
||||
this.btnMax.tooltip = Tooltip("Maximize")
|
||||
}
|
||||
|
||||
this.maximized = !this.maximized
|
||||
}
|
||||
}
|
||||
|
||||
fun onClose() {
|
||||
this.onCloseButtonAction.run()
|
||||
}
|
||||
|
||||
private fun updateInitMouseValues(mouseEvent: MouseEvent) {
|
||||
this.initX = mouseEvent.screenX
|
||||
this.initY = mouseEvent.screenY
|
||||
this.xOffset = mouseEvent.sceneX
|
||||
this.yOffset = mouseEvent.sceneY
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isRightEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return x < this.width && x > this.width - this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isTopEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return y >= 0.0 && y < this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isBottomEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return y < this.height && y > this.height - this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isLeftEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return x >= 0.0 && x < this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
internal fun setStageWidth(width: Double): Boolean {
|
||||
if (width >= this.primaryStage.minWidth && width >= this.titleContainer.minWidth) {
|
||||
this.primaryStage.width = width
|
||||
this.initX = this.newX
|
||||
return true
|
||||
} else {
|
||||
if (width >= this.primaryStage.minWidth && width <= this.titleContainer.minWidth) {
|
||||
this.primaryStage.width = this.titleContainer.minWidth
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setStageHeight(height: Double): Boolean {
|
||||
if (height >= this.primaryStage.minHeight && height >= this.titleContainer.height) {
|
||||
this.primaryStage.height = height
|
||||
this.initY = this.newY
|
||||
return true
|
||||
} else {
|
||||
if (height >= this.primaryStage.minHeight && height <= this.titleContainer.height) {
|
||||
this.primaryStage.height = this.titleContainer.height
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun setMaximized(maximized: Boolean) {
|
||||
if (this.maximized != maximized) {
|
||||
Platform.runLater { this.btnMax.fire() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setContent(content: Node, animation: AnimationProducer) {
|
||||
animationHandler.setContent(content, animation)
|
||||
|
||||
if (content is Region) {
|
||||
content.setMinSize(0.0, 0.0)
|
||||
content.setOverflowHidden()
|
||||
}
|
||||
|
||||
backNavButton.isDisable = !wizardController.canPrev()
|
||||
|
||||
if (content is Refreshable)
|
||||
refreshNavButton.isVisible = true
|
||||
|
||||
if (content != mainPage)
|
||||
closeNavButton.isVisible = true
|
||||
|
||||
val prefix = if (category == null) "" else category + " - "
|
||||
|
||||
titleLabel.textProperty().unbind()
|
||||
|
||||
if (content is WizardPage)
|
||||
titleLabel.text = prefix + content.title
|
||||
|
||||
if (content is DecoratorPage)
|
||||
titleLabel.textProperty().bind(content.titleProperty())
|
||||
}
|
||||
|
||||
var category: String? = null
|
||||
var nowPage: Node? = null
|
||||
|
||||
fun showPage(content: Node?) {
|
||||
val c = content ?: mainPage
|
||||
onEnd()
|
||||
val nowPage = nowPage
|
||||
if (nowPage is DecoratorPage)
|
||||
nowPage.onClose()
|
||||
this.nowPage = content
|
||||
setContent(c, ContainerAnimations.FADE.animationProducer)
|
||||
|
||||
if (c is Region)
|
||||
// Let root pane fix window size.
|
||||
with(c.parent as StackPane) {
|
||||
c.prefWidthProperty().bind(widthProperty())
|
||||
c.prefHeightProperty().bind(heightProperty())
|
||||
}
|
||||
}
|
||||
|
||||
fun showDialog(content: Region): JFXDialog {
|
||||
dialog.content = content
|
||||
if (!dialogShown)
|
||||
dialog.show()
|
||||
return dialog
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun startWizard(wizardProvider: WizardProvider, category: String? = null) {
|
||||
this.category = category
|
||||
wizardController.provider = wizardProvider
|
||||
wizardController.onStart()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
backNavButton.isVisible = true
|
||||
backNavButton.isDisable = false
|
||||
closeNavButton.isVisible = true
|
||||
refreshNavButton.isVisible = false
|
||||
}
|
||||
|
||||
override fun onEnd() {
|
||||
backNavButton.isVisible = false
|
||||
closeNavButton.isVisible = false
|
||||
refreshNavButton.isVisible = false
|
||||
}
|
||||
|
||||
override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) {
|
||||
setContent(page, nav.animation.animationProducer)
|
||||
}
|
||||
|
||||
fun onRefresh() {
|
||||
(contentPlaceHolder.children.single() as Refreshable).refresh()
|
||||
}
|
||||
|
||||
fun onCloseNav() {
|
||||
wizardController.onCancel()
|
||||
showPage(null)
|
||||
}
|
||||
|
||||
fun onBack() {
|
||||
wizardController.onPrev(true)
|
||||
}
|
||||
}
|
||||
@@ -1,270 +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
|
||||
|
||||
import com.jfoenix.adapters.ReflectionHelper
|
||||
import com.jfoenix.concurrency.JFXUtilities
|
||||
import com.jfoenix.controls.*
|
||||
import javafx.animation.Animation
|
||||
import javafx.animation.Interpolator
|
||||
import javafx.animation.KeyFrame
|
||||
import javafx.animation.Timeline
|
||||
import javafx.beans.property.Property
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.event.EventHandler
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.image.WritableImage
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.input.ScrollEvent
|
||||
import javafx.scene.layout.Region
|
||||
import javafx.scene.shape.Rectangle
|
||||
import javafx.util.Duration
|
||||
import org.jackhuang.hmcl.Main
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import org.jackhuang.hmcl.util.Logging.LOG
|
||||
import org.jackhuang.hmcl.util.ReflectionHelper.call
|
||||
import org.jackhuang.hmcl.util.ReflectionHelper.construct
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.logging.Level
|
||||
|
||||
fun Node.loadFXML(absolutePath: String) {
|
||||
val fxmlLoader = FXMLLoader(this.javaClass.getResource(absolutePath), Main.RESOURCE_BUNDLE)
|
||||
fxmlLoader.setRoot(this)
|
||||
fxmlLoader.setController(this)
|
||||
fxmlLoader.load<Any>()
|
||||
}
|
||||
|
||||
fun ListView<*>.smoothScrolling() {
|
||||
skinProperty().onInvalidated {
|
||||
val bar = lookup(".scroll-bar") as ScrollBar
|
||||
val virtualFlow = lookup(".virtual-flow")
|
||||
val frictions = doubleArrayOf(0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001)
|
||||
val pushes = doubleArrayOf(1.0)
|
||||
val derivatives = DoubleArray(frictions.size)
|
||||
|
||||
val timeline = Timeline()
|
||||
bar.addEventHandler(MouseEvent.DRAG_DETECTED) { timeline.stop() }
|
||||
|
||||
val scrollEventHandler = EventHandler<ScrollEvent> { event ->
|
||||
if (event.eventType == ScrollEvent.SCROLL) {
|
||||
val direction = if (event.deltaY > 0) -1 else 1
|
||||
for (i in pushes.indices) {
|
||||
derivatives[i] += direction * pushes[i]
|
||||
}
|
||||
if (timeline.status == Animation.Status.STOPPED) {
|
||||
timeline.play()
|
||||
}
|
||||
event.consume()
|
||||
}
|
||||
}
|
||||
|
||||
bar.addEventHandler(ScrollEvent.ANY, scrollEventHandler)
|
||||
virtualFlow.onScroll = scrollEventHandler
|
||||
|
||||
timeline.keyFrames.add(KeyFrame(Duration.millis(3.0), EventHandler<ActionEvent> {
|
||||
for (i in derivatives.indices) {
|
||||
derivatives[i] *= frictions[i]
|
||||
}
|
||||
for (i in 1 until derivatives.size) {
|
||||
derivatives[i] += derivatives[i - 1]
|
||||
}
|
||||
val dy = derivatives[derivatives.size - 1]
|
||||
val height = layoutBounds.height
|
||||
bar.value = Math.min(Math.max(bar.value + dy / height, 0.0), 1.0)
|
||||
if (Math.abs(dy) < 0.001) {
|
||||
timeline.stop()
|
||||
}
|
||||
requestLayout()
|
||||
}))
|
||||
timeline.cycleCount = Animation.INDEFINITE
|
||||
}
|
||||
}
|
||||
|
||||
fun ScrollPane.smoothScrolling() = JFXScrollPane.smoothScrolling(this)
|
||||
|
||||
fun runOnUiThread(runnable: () -> Unit) = JFXUtilities.runInFX(runnable)
|
||||
|
||||
fun takeSnapshot(node: Parent, width: Double, height: Double): WritableImage {
|
||||
val scene = Scene(node, width, height)
|
||||
scene.stylesheets.addAll(*stylesheets)
|
||||
return scene.snapshot(null)
|
||||
}
|
||||
|
||||
fun Region.setOverflowHidden() {
|
||||
val rectangle = Rectangle()
|
||||
rectangle.widthProperty().bind(widthProperty())
|
||||
rectangle.heightProperty().bind(heightProperty())
|
||||
clip = rectangle
|
||||
}
|
||||
|
||||
val stylesheets = arrayOf(
|
||||
Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(),
|
||||
Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(),
|
||||
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
|
||||
|
||||
fun Region.limitWidth(width: Double) {
|
||||
maxWidth = width
|
||||
minWidth = width
|
||||
prefWidth = width
|
||||
}
|
||||
|
||||
fun Region.limitHeight(height: Double) {
|
||||
maxHeight = height
|
||||
minHeight = height
|
||||
prefHeight = height
|
||||
}
|
||||
|
||||
fun bindInt(textField: JFXTextField, property: Property<*>) {
|
||||
textField.textProperty().unbind()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
textField.textProperty().bindBidirectional(property as Property<Int>, SafeIntStringConverter())
|
||||
}
|
||||
|
||||
fun bindString(textField: JFXTextField, property: Property<String>) {
|
||||
textField.textProperty().unbind()
|
||||
textField.textProperty().bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun bindBoolean(toggleButton: JFXToggleButton, property: Property<Boolean>) {
|
||||
toggleButton.selectedProperty().unbind()
|
||||
toggleButton.selectedProperty().bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun bindBoolean(checkBox: JFXCheckBox, property: Property<Boolean>) {
|
||||
checkBox.selectedProperty().unbind()
|
||||
checkBox.selectedProperty().bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun bindEnum(comboBox: JFXComboBox<*>, property: Property<out Enum<*>>) {
|
||||
unbindEnum(comboBox)
|
||||
val listener = ChangeListener<Number> { _, _, newValue ->
|
||||
property.value = property.value.javaClass.enumConstants[newValue.toInt()]
|
||||
}
|
||||
comboBox.selectionModel.select(property.value.ordinal)
|
||||
comboBox.properties["listener"] = listener
|
||||
comboBox.selectionModel.selectedIndexProperty().addListener(listener)
|
||||
}
|
||||
|
||||
fun unbindEnum(comboBox: JFXComboBox<*>) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val listener = comboBox.properties["listener"] as? ChangeListener<Number> ?: return
|
||||
comboBox.selectionModel.selectedIndexProperty().removeListener(listener)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Built-in interpolator that provides discrete time interpolation. The
|
||||
* return value of `interpolate()` is `endValue` only when the
|
||||
* input `fraction` is 1.0, and `startValue` otherwise.
|
||||
*/
|
||||
@JvmField val SINE: Interpolator = object : Interpolator() {
|
||||
override fun curve(t: Double): Double {
|
||||
return Math.sin(t * Math.PI / 2)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Interpolator.DISCRETE"
|
||||
}
|
||||
}
|
||||
|
||||
fun JFXMasonryPane.resetChildren(children: List<Node>) {
|
||||
// Fixes mis-repositioning.
|
||||
ReflectionHelper.setFieldContent(JFXMasonryPane::class.java, this, "oldBoxes", null)
|
||||
this.children.setAll(children)
|
||||
}
|
||||
|
||||
fun openFolder(f: File) {
|
||||
f.mkdirs()
|
||||
val path = f.absolutePath
|
||||
when (OperatingSystem.CURRENT_OS) {
|
||||
OperatingSystem.OSX ->
|
||||
try {
|
||||
Runtime.getRuntime().exec(arrayOf("/usr/bin/open", path));
|
||||
} catch (ex: IOException) {
|
||||
LOG.log(Level.SEVERE, "Failed to open $path through /usr/bin/open", ex);
|
||||
}
|
||||
else ->
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(f);
|
||||
} catch (ex: Throwable) {
|
||||
LOG.log(Level.SEVERE, "Failed to open $path through java.awt.Desktop.getDesktop().open()", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun alert(type: Alert.AlertType, title: String, contentText: String, headerText: String? = null): Boolean {
|
||||
Alert(type).apply {
|
||||
this.title = title
|
||||
this.headerText = headerText
|
||||
this.contentText = contentText
|
||||
}.showAndWait().run {
|
||||
return isPresent && get() == ButtonType.OK
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun inputDialog(title: String, contentText: String, headerText: String? = null, defaultValue: String = "") =
|
||||
TextInputDialog(defaultValue).apply {
|
||||
this.title = title
|
||||
this.headerText = headerText
|
||||
this.contentText = contentText
|
||||
}.showAndWait()
|
||||
|
||||
fun Node.installTooltip(openDelay: Double = 1000.0, visibleDelay: Double = 5000.0, closeDelay: Double = 200.0, tooltip: Tooltip) {
|
||||
try {
|
||||
call(construct(Class.forName("javafx.scene.control.Tooltip\$TooltipBehavior"), Duration(openDelay), Duration(visibleDelay), Duration(closeDelay), false),
|
||||
"install", this, tooltip);
|
||||
} catch (e: Throwable) {
|
||||
LOG.log(Level.SEVERE, "Cannot install tooltip by reflection", e)
|
||||
Tooltip.install(this, tooltip)
|
||||
}
|
||||
}
|
||||
|
||||
fun JFXTextField.setValidateWhileTextChanged() {
|
||||
textProperty().onInvalidated(this::validate)
|
||||
validate()
|
||||
}
|
||||
|
||||
fun JFXPasswordField.setValidateWhileTextChanged() {
|
||||
textProperty().onInvalidated(this::validate)
|
||||
validate()
|
||||
}
|
||||
|
||||
fun ImageView.limitSize(maxWidth: Double, maxHeight: Double) {
|
||||
isPreserveRatio = true
|
||||
imageProperty().onChangeAndOperate {
|
||||
if (it != null && (it.width > maxWidth || it.height > maxHeight)) {
|
||||
fitHeight = maxHeight
|
||||
fitWidth = maxWidth
|
||||
} else {
|
||||
fitHeight = -1.0
|
||||
fitWidth = -1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField val DEFAULT_ICON = Image("/assets/img/icon.png")
|
||||
@@ -1,116 +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
|
||||
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.paint.Paint
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.event.EventBus
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent
|
||||
import org.jackhuang.hmcl.game.AccountHelper
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer
|
||||
import org.jackhuang.hmcl.util.channel
|
||||
import org.jackhuang.hmcl.util.onChangeAndOperate
|
||||
import org.jackhuang.hmcl.util.plusAssign
|
||||
import java.util.*
|
||||
|
||||
class LeftPaneController(private val leftPane: AdvancedListBox) {
|
||||
val profilePane = VBox()
|
||||
val accountItem = VersionListItem("No Account", "unknown")
|
||||
|
||||
init {
|
||||
leftPane
|
||||
.startCategory("ACCOUNTS")
|
||||
.add(RipplerContainer(accountItem).apply {
|
||||
setOnMouseClicked {
|
||||
Controllers.navigate(AccountsPage())
|
||||
}
|
||||
accountItem.onSettingsButtonClicked {
|
||||
Controllers.navigate(AccountsPage())
|
||||
}
|
||||
})
|
||||
.startCategory("LAUNCHER")
|
||||
.add(IconedItem(SVG.gear("black"), i18n("launcher.title.launcher")).apply {
|
||||
prefWidthProperty().bind(leftPane.widthProperty())
|
||||
setOnMouseClicked {
|
||||
Controllers.navigate(Controllers.settingsPane)
|
||||
}
|
||||
})
|
||||
.startCategory(i18n("ui.label.profile"))
|
||||
.add(profilePane)
|
||||
|
||||
EventBus.EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
|
||||
EventBus.EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
|
||||
|
||||
Controllers.decorator.addMenuButton.setOnMouseClicked {
|
||||
Controllers.decorator.showPage(ProfilePage(null))
|
||||
}
|
||||
|
||||
Settings.INSTANCE.selectedAccountProperty().onChangeAndOperate {
|
||||
if (it == null) {
|
||||
accountItem.setVersionName("mojang@mojang.com")
|
||||
accountItem.setGameVersion("Yggdrasil")
|
||||
} else {
|
||||
accountItem.setVersionName(it.username)
|
||||
accountItem.setGameVersion(accountType(it))
|
||||
}
|
||||
if (it is YggdrasilAccount) {
|
||||
accountItem.setImage(AccountHelper.getSkin(it, 4.0), AccountHelper.getViewport(4.0))
|
||||
} else {
|
||||
accountItem.setImage(DEFAULT_ICON, null)
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
Controllers.navigate(AccountsPage())
|
||||
}
|
||||
|
||||
fun onProfileChanged(event: ProfileChangedEvent) {
|
||||
val profile = event.profile
|
||||
|
||||
profilePane.children
|
||||
.filter { it is RipplerContainer && it.properties["profile"] is Pair<*, *> }
|
||||
.forEach { (it as RipplerContainer).selected = (it.properties["profile"] as Pair<*, *>).first == profile.name }
|
||||
}
|
||||
|
||||
fun onProfilesLoading() {
|
||||
val list = LinkedList<RipplerContainer>()
|
||||
Settings.INSTANCE.profiles.forEach { profile ->
|
||||
val item = VersionListItem(profile.name)
|
||||
val ripplerContainer = RipplerContainer(item)
|
||||
item.onSettingsButtonClicked {
|
||||
Controllers.decorator.showPage(ProfilePage(profile))
|
||||
}
|
||||
ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9")
|
||||
ripplerContainer.setOnMouseClicked {
|
||||
// clean selected property
|
||||
profilePane.children.forEach { if (it is RipplerContainer) it.selected = false }
|
||||
ripplerContainer.selected = true
|
||||
Settings.INSTANCE.selectedProfile = profile
|
||||
}
|
||||
ripplerContainer.properties["profile"] = profile.name to item
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty())
|
||||
list += ripplerContainer
|
||||
}
|
||||
runOnUiThread { profilePane.children.setAll(list) }
|
||||
}
|
||||
}
|
||||
@@ -1,107 +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
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXMasonryPane
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.event.EventBus
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
|
||||
import org.jackhuang.hmcl.game.GameVersion.minecraftVersion
|
||||
import org.jackhuang.hmcl.game.LauncherHelper
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
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.channel
|
||||
import org.jackhuang.hmcl.util.plusAssign
|
||||
|
||||
/**
|
||||
* @see /assets/fxml/main.fxml
|
||||
*/
|
||||
class MainPage : StackPane(), DecoratorPage {
|
||||
private val titleProperty = SimpleStringProperty(this, "title", i18n("launcher.title.main"))
|
||||
override fun titleProperty() = titleProperty
|
||||
|
||||
@FXML lateinit var btnRefresh: JFXButton
|
||||
@FXML lateinit var btnAdd: JFXButton
|
||||
@FXML lateinit var masonryPane: JFXMasonryPane
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/main.fxml")
|
||||
|
||||
EventBus.EVENT_BUS.channel<RefreshedVersionsEvent>() += { -> runOnUiThread { loadVersions() } }
|
||||
EventBus.EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
|
||||
EventBus.EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
|
||||
|
||||
btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") }
|
||||
btnRefresh.setOnMouseClicked { Settings.INSTANCE.selectedProfile.repository.refreshVersions() }
|
||||
}
|
||||
|
||||
private fun buildNode(i: Int, profile: Profile, version: String, game: String): Node {
|
||||
return VersionItem().apply {
|
||||
setGameVersion(game)
|
||||
setVersionName(version)
|
||||
|
||||
setOnLaunchButtonClicked {
|
||||
if (Settings.INSTANCE.selectedAccount == null) {
|
||||
Controllers.dialog(i18n("login.no_Player007"))
|
||||
} else
|
||||
LauncherHelper.INSTANCE.launch(version)
|
||||
}
|
||||
setOnDeleteButtonClicked {
|
||||
profile.repository.removeVersionFromDisk(version)
|
||||
Platform.runLater { loadVersions() }
|
||||
}
|
||||
setOnSettingsButtonClicked {
|
||||
Controllers.decorator.showPage(Controllers.versionPane)
|
||||
Controllers.versionPane.load(version, profile)
|
||||
}
|
||||
val iconFile = profile.repository.getVersionIcon(version)
|
||||
if (iconFile.exists())
|
||||
setImage(Image("file:" + iconFile.absolutePath))
|
||||
}
|
||||
}
|
||||
|
||||
fun onProfilesLoading() {
|
||||
// TODO: Profiles
|
||||
}
|
||||
|
||||
fun onProfileChanged(event: ProfileChangedEvent) = runOnUiThread {
|
||||
val profile = event.profile
|
||||
loadVersions(profile)
|
||||
}
|
||||
|
||||
private fun loadVersions(profile: Profile = Settings.INSTANCE.selectedProfile) {
|
||||
val children = mutableListOf<Node>()
|
||||
var i = 0
|
||||
profile.repository.versions.forEach { version ->
|
||||
children += buildNode(++i, profile, version.id, minecraftVersion(profile.repository.getVersionJar(version.id)) ?: "Unknown")
|
||||
}
|
||||
masonryPane.resetChildren(children)
|
||||
}
|
||||
}
|
||||
@@ -1,114 +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
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner
|
||||
import com.jfoenix.controls.JFXTabPane
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.input.TransferMode
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.FileChooser
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.mod.ModManager
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import org.jackhuang.hmcl.util.onChange
|
||||
import org.jackhuang.hmcl.util.onChangeAndOperateWeakly
|
||||
import org.jackhuang.hmcl.util.task
|
||||
import java.util.*
|
||||
|
||||
class ModController {
|
||||
@FXML lateinit var scrollPane: ScrollPane
|
||||
@FXML lateinit var rootPane: StackPane
|
||||
@FXML lateinit var modPane: VBox
|
||||
@FXML lateinit var contentPane: StackPane
|
||||
@FXML lateinit var spinner: JFXSpinner
|
||||
lateinit var parentTab: JFXTabPane
|
||||
private lateinit var modManager: ModManager
|
||||
private lateinit var versionId: String
|
||||
|
||||
fun initialize() {
|
||||
scrollPane.smoothScrolling()
|
||||
|
||||
rootPane.setOnDragOver { event ->
|
||||
if (event.gestureSource != rootPane && event.dragboard.hasFiles())
|
||||
event.acceptTransferModes(*TransferMode.COPY_OR_MOVE)
|
||||
event.consume()
|
||||
}
|
||||
rootPane.setOnDragDropped { event ->
|
||||
val mods = event.dragboard.files
|
||||
?.filter { it.extension in listOf("jar", "zip", "litemod") }
|
||||
if (mods != null && mods.isNotEmpty()) {
|
||||
mods.forEach { modManager.addMod(versionId, it) }
|
||||
loadMods(modManager, versionId)
|
||||
event.isDropCompleted = true
|
||||
}
|
||||
event.consume()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMods(modManager: ModManager, versionId: String) {
|
||||
this.modManager = modManager
|
||||
this.versionId = versionId
|
||||
task {
|
||||
synchronized(contentPane) {
|
||||
runOnUiThread { rootPane.children -= contentPane; spinner.isVisible = true }
|
||||
modManager.refreshMods(versionId)
|
||||
|
||||
// Surprisingly, if there are a great number of mods, this processing will cause a UI pause.
|
||||
// We must do this asynchronously.
|
||||
val list = LinkedList<ModItem>()
|
||||
for (modInfo in modManager.getMods(versionId)) {
|
||||
list += ModItem(modInfo) {
|
||||
modManager.removeMods(versionId, modInfo)
|
||||
loadMods(modManager, versionId)
|
||||
}.apply {
|
||||
modInfo.activeProperty().onChange {
|
||||
if (it)
|
||||
styleClass -= "disabled"
|
||||
else
|
||||
styleClass += "disabled"
|
||||
}
|
||||
|
||||
if (!modInfo.isActive)
|
||||
styleClass += "disabled"
|
||||
}
|
||||
}
|
||||
runOnUiThread { rootPane.children += contentPane; spinner.isVisible = false }
|
||||
it["list"] = list
|
||||
}
|
||||
}.subscribe(Schedulers.javafx()) { variables ->
|
||||
parentTab.selectionModel.selectedItemProperty().onChangeAndOperateWeakly {
|
||||
if (it?.userData == this) {
|
||||
modPane.children.setAll(variables.get<List<ModItem>>("list"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onAdd() {
|
||||
val chooser = FileChooser()
|
||||
chooser.title = i18n("mods.choose_mod")
|
||||
chooser.extensionFilters.setAll(FileChooser.ExtensionFilter("Mod", "*.jar", "*.zip", "*.litemod"))
|
||||
val res = chooser.showOpenDialog(Controllers.stage) ?: return
|
||||
task { modManager.addMod(versionId, res) }
|
||||
.subscribe(task(Schedulers.javafx()) { loadMods(modManager, versionId) })
|
||||
}
|
||||
}
|
||||
@@ -1,88 +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
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
|
||||
import org.jackhuang.hmcl.util.onChangeAndOperate
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @param profile null if creating a new profile.
|
||||
*/
|
||||
class ProfilePage(private val profile: Profile?): StackPane(), DecoratorPage {
|
||||
private val titleProperty = SimpleStringProperty(this, "title",
|
||||
if (profile == null) i18n("ui.newProfileWindow.title") else i18n("ui.label.profile") + " - " + profile.name)
|
||||
|
||||
override fun titleProperty() = titleProperty
|
||||
|
||||
private val locationProperty = SimpleStringProperty(this, "location",
|
||||
profile?.gameDir?.absolutePath ?: "")
|
||||
@FXML lateinit var txtProfileName: JFXTextField
|
||||
@FXML lateinit var gameDir: FileItem
|
||||
@FXML lateinit var btnSave: JFXButton
|
||||
@FXML lateinit var btnDelete: JFXButton
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/profile.fxml")
|
||||
|
||||
txtProfileName.text = profile?.name ?: ""
|
||||
txtProfileName.textProperty().onChangeAndOperate {
|
||||
btnSave.isDisable = !txtProfileName.validate() || locationProperty.get().isNullOrBlank()
|
||||
}
|
||||
gameDir.setProperty(locationProperty)
|
||||
locationProperty.onChangeAndOperate {
|
||||
btnSave.isDisable = !txtProfileName.validate() || locationProperty.get().isNullOrBlank()
|
||||
}
|
||||
|
||||
if (profile == null)
|
||||
btnDelete.isVisible = false
|
||||
}
|
||||
|
||||
fun onDelete() {
|
||||
if (profile != null) {
|
||||
Settings.INSTANCE.deleteProfile(profile)
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSave() {
|
||||
if (profile != null) { // editing a profile
|
||||
profile.name = txtProfileName.text
|
||||
if (locationProperty.get() != null)
|
||||
profile.gameDir = File(locationProperty.get())
|
||||
} else {
|
||||
if (locationProperty.get().isNullOrBlank()) {
|
||||
gameDir.onExplore()
|
||||
}
|
||||
Settings.INSTANCE.putProfile(Profile(txtProfileName.text, File(locationProperty.get())))
|
||||
}
|
||||
|
||||
Settings.INSTANCE.onProfileLoading()
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
}
|
||||
@@ -1,65 +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
|
||||
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.shape.SVGPath
|
||||
|
||||
object SVG {
|
||||
val svgNames = setOf("gear")
|
||||
val svgs: Map<String, Group>
|
||||
|
||||
init {
|
||||
val svgsImpl = HashMap<String, Group>()
|
||||
for (svgName in svgNames) {
|
||||
svgsImpl[svgName] = FXMLLoader(Controllers::class.java.getResource("/assets/svg/$svgName.fxml")).load()
|
||||
}
|
||||
svgs = svgsImpl
|
||||
}
|
||||
|
||||
private fun createSVGPath(d: String, fill: String = "black", width: Double = 20.0, height: Double = 20.0): Node {
|
||||
val path = SVGPath()
|
||||
path.styleClass += "svg"
|
||||
path.content = d
|
||||
path.style = "-fx-fill: $fill;"
|
||||
|
||||
val svg = Group(path)
|
||||
val scale = minOf(width / svg.boundsInParent.width, height / svg.boundsInParent.height)
|
||||
svg.scaleX = scale
|
||||
svg.scaleY = scale
|
||||
svg.maxWidth(width)
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
@JvmStatic fun gear(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height)
|
||||
@JvmStatic fun back(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height)
|
||||
@JvmStatic fun close(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height)
|
||||
@JvmStatic fun dotsVertical(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height)
|
||||
@JvmStatic fun delete(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height)
|
||||
@JvmStatic fun accountEdit(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height)
|
||||
@JvmStatic fun expand(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height)
|
||||
@JvmStatic fun collapse(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height)
|
||||
@JvmStatic fun navigate(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height)
|
||||
@JvmStatic fun launch(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height)
|
||||
@JvmStatic fun pencil(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height)
|
||||
@JvmStatic fun refresh(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height)
|
||||
@JvmStatic fun folderOpen(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height)
|
||||
}
|
||||
@@ -1,117 +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
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.text.Font
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders
|
||||
import org.jackhuang.hmcl.setting.Locales
|
||||
import org.jackhuang.hmcl.setting.Proxies
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem
|
||||
import org.jackhuang.hmcl.ui.construct.FontComboBox
|
||||
import org.jackhuang.hmcl.ui.construct.Validator
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
|
||||
import org.jackhuang.hmcl.util.onChange
|
||||
|
||||
class SettingsPage : StackPane(), DecoratorPage {
|
||||
private val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.launcher"))
|
||||
|
||||
override fun titleProperty() = titleProperty
|
||||
|
||||
@FXML lateinit var txtProxyHost: JFXTextField
|
||||
@FXML lateinit var txtProxyPort: JFXTextField
|
||||
@FXML lateinit var txtProxyUsername: JFXTextField
|
||||
@FXML lateinit var txtProxyPassword: JFXTextField
|
||||
@FXML lateinit var cboProxyType: JFXComboBox<*>
|
||||
@FXML lateinit var cboFont: FontComboBox
|
||||
@FXML lateinit var cboLanguage: JFXComboBox<*>
|
||||
@FXML lateinit var cboDownloadSource: JFXComboBox<*>
|
||||
@FXML lateinit var fileCommonLocation: FileItem
|
||||
@FXML lateinit var fileBackgroundLocation: FileItem
|
||||
@FXML lateinit var lblDisplay: Label
|
||||
@FXML lateinit var txtFontSize: JFXTextField
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/setting.fxml")
|
||||
|
||||
cboLanguage.limitWidth(400.0)
|
||||
cboDownloadSource.limitWidth(400.0)
|
||||
|
||||
txtProxyHost.text = Settings.INSTANCE.proxyHost
|
||||
txtProxyHost.textProperty().onChange { Settings.INSTANCE.proxyHost = it }
|
||||
|
||||
txtProxyPort.text = Settings.INSTANCE.proxyPort
|
||||
txtProxyPort.textProperty().onChange { Settings.INSTANCE.proxyPort = it }
|
||||
|
||||
txtProxyUsername.text = Settings.INSTANCE.proxyUser
|
||||
txtProxyUsername.textProperty().onChange { Settings.INSTANCE.proxyUser = it }
|
||||
|
||||
txtProxyPassword.text = Settings.INSTANCE.proxyPass
|
||||
txtProxyPassword.textProperty().onChange { Settings.INSTANCE.proxyPass = it }
|
||||
|
||||
cboDownloadSource.selectionModel.select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.INSTANCE.downloadProvider))
|
||||
cboDownloadSource.selectionModel.selectedIndexProperty().onChange {
|
||||
Settings.INSTANCE.downloadProvider = DownloadProviders.getDownloadProvider(it)
|
||||
}
|
||||
|
||||
cboFont.selectionModel.select(Settings.INSTANCE.font.family)
|
||||
cboFont.valueProperty().onChange {
|
||||
val font = Font.font(it, Settings.INSTANCE.font.size)
|
||||
Settings.INSTANCE.font = font
|
||||
lblDisplay.style = "-fx-font: ${Settings.INSTANCE.font.size} \"${font.family}\";"
|
||||
}
|
||||
txtFontSize.text = Settings.INSTANCE.font.size.toString()
|
||||
txtFontSize.validators += Validator { it.toDoubleOrNull() != null }
|
||||
txtFontSize.textProperty().onChange {
|
||||
if (txtFontSize.validate()) {
|
||||
val font = Font.font(Settings.INSTANCE.font.family, it!!.toDouble())
|
||||
Settings.INSTANCE.font = font
|
||||
lblDisplay.style = "-fx-font: ${font.size} \"${Settings.INSTANCE.font.family}\";"
|
||||
}
|
||||
}
|
||||
lblDisplay.style = "-fx-font: ${Settings.INSTANCE.font.size} \"${Settings.INSTANCE.font.family}\";"
|
||||
|
||||
val list = FXCollections.observableArrayList<Label>()
|
||||
for (locale in Locales.LOCALES) {
|
||||
list += Label(locale.getName(Settings.INSTANCE.locale.resourceBundle))
|
||||
}
|
||||
cboLanguage.items = list
|
||||
cboLanguage.selectionModel.select(Locales.LOCALES.indexOf(Settings.INSTANCE.locale))
|
||||
cboLanguage.selectionModel.selectedIndexProperty().onChange {
|
||||
Settings.INSTANCE.locale = Locales.getLocale(it)
|
||||
}
|
||||
|
||||
cboProxyType.selectionModel.select(Proxies.PROXIES.indexOf(Settings.INSTANCE.proxyType))
|
||||
cboProxyType.selectionModel.selectedIndexProperty().onChange {
|
||||
Settings.INSTANCE.proxyType = Proxies.getProxyType(it)
|
||||
}
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,139 +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
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXListView
|
||||
import com.jfoenix.controls.JFXPopup
|
||||
import com.jfoenix.controls.JFXTabPane
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Alert
|
||||
import javafx.scene.control.Tab
|
||||
import javafx.scene.control.Tooltip
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
|
||||
|
||||
class VersionPage : StackPane(), DecoratorPage {
|
||||
private val titleProperty: StringProperty = SimpleStringProperty(this, "title", null)
|
||||
override fun titleProperty() = titleProperty
|
||||
|
||||
@FXML lateinit var versionSettingsController: VersionSettingsController
|
||||
@FXML lateinit var modTab: Tab
|
||||
@FXML lateinit var modController: ModController
|
||||
@FXML lateinit var installerController: InstallerController
|
||||
|
||||
@FXML lateinit var browseList: JFXListView<*>
|
||||
@FXML lateinit var managementList: JFXListView<*>
|
||||
@FXML lateinit var btnBrowseMenu: JFXButton
|
||||
@FXML lateinit var btnManagementMenu: JFXButton
|
||||
@FXML lateinit var btnExport: JFXButton
|
||||
@FXML lateinit var rootPane: StackPane
|
||||
@FXML lateinit var contentPane: StackPane
|
||||
@FXML lateinit var tabPane: JFXTabPane
|
||||
val browsePopup: JFXPopup
|
||||
val managementPopup: JFXPopup
|
||||
lateinit var profile: Profile
|
||||
lateinit var version: String
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/version/version.fxml")
|
||||
|
||||
children -= browseList
|
||||
children -= managementList
|
||||
|
||||
browsePopup = JFXPopup(browseList)
|
||||
managementPopup = JFXPopup(managementList)
|
||||
|
||||
btnBrowseMenu.installTooltip(openDelay = 0.0, closeDelay = 0.0, tooltip = Tooltip(i18n("settings.explore")))
|
||||
btnManagementMenu.installTooltip(openDelay = 0.0, closeDelay = 0.0, tooltip = Tooltip(i18n("settings.manage")))
|
||||
btnExport.installTooltip(openDelay = 0.0, closeDelay = 0.0, tooltip = Tooltip(i18n("modpack.task.save")))
|
||||
}
|
||||
|
||||
fun load(id: String, profile: Profile) {
|
||||
this.version = id
|
||||
this.profile = profile
|
||||
titleProperty.set(i18n("launcher.title.game") + " - " + id)
|
||||
|
||||
versionSettingsController.loadVersionSetting(profile, id, profile.getVersionSetting(id))
|
||||
modController.parentTab = tabPane
|
||||
modTab.userData = modController
|
||||
modController.loadMods(profile.modManager, id)
|
||||
installerController.loadVersion(profile, id)
|
||||
}
|
||||
|
||||
fun onBrowseMenu() {
|
||||
browseList.selectionModel.select(-1)
|
||||
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12.0, 15.0)
|
||||
}
|
||||
|
||||
fun onManagementMenu() {
|
||||
managementList.selectionModel.select(-1)
|
||||
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12.0, 15.0)
|
||||
}
|
||||
|
||||
fun onExport() {
|
||||
Controllers.decorator.startWizard(ExportWizardProvider(profile, version), i18n("modpack.wizard"))
|
||||
}
|
||||
|
||||
fun onBrowse() {
|
||||
openFolder(profile.repository.getRunDirectory(version).resolve(when (browseList.selectionModel.selectedIndex) {
|
||||
0 -> ""
|
||||
1 -> "mods"
|
||||
2 -> "coremods"
|
||||
3 -> "config"
|
||||
4 -> "resourcepacks"
|
||||
5 -> "screenshots"
|
||||
6 -> "saves"
|
||||
else -> throw Error()
|
||||
}))
|
||||
}
|
||||
|
||||
fun onManagement() {
|
||||
when(managementList.selectionModel.selectedIndex) {
|
||||
0 -> { // rename a version
|
||||
val res = inputDialog(title = "Input", contentText = i18n("versions.manage.rename.message"), defaultValue = version)
|
||||
if (res.isPresent)
|
||||
if (profile.repository.renameVersion(version, res.get())) {
|
||||
profile.repository.refreshVersions()
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
}
|
||||
1 -> { // remove a version
|
||||
if (alert(Alert.AlertType.CONFIRMATION, "Confirm", i18n("versions.manage.remove.confirm") + version))
|
||||
if (profile.repository.removeVersionFromDisk(version)) {
|
||||
profile.repository.refreshVersions()
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
}
|
||||
2 -> { // redownload asset index
|
||||
GameAssetIndexDownloadTask(profile.dependency, profile.repository.getVersion(version).resolve(profile.repository)).start()
|
||||
}
|
||||
3 -> { // delete libraries
|
||||
profile.repository.baseDirectory.resolve("libraries").deleteRecursively()
|
||||
}
|
||||
else -> throw Error()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,252 +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
|
||||
|
||||
import com.jfoenix.controls.*
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.control.Toggle
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.FileChooser
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.setting.VersionSetting
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem
|
||||
import org.jackhuang.hmcl.ui.construct.NumberValidator
|
||||
import org.jackhuang.hmcl.util.JavaVersion
|
||||
import org.jackhuang.hmcl.util.OperatingSystem
|
||||
import org.jackhuang.hmcl.util.task
|
||||
|
||||
class VersionSettingsController {
|
||||
var lastVersionSetting: VersionSetting? = null
|
||||
@FXML lateinit var rootPane: VBox
|
||||
@FXML lateinit var scroll: ScrollPane
|
||||
@FXML lateinit var txtWidth: JFXTextField
|
||||
@FXML lateinit var txtHeight: JFXTextField
|
||||
@FXML lateinit var txtMaxMemory: JFXTextField
|
||||
@FXML lateinit var txtJVMArgs: JFXTextField
|
||||
@FXML lateinit var txtGameArgs: JFXTextField
|
||||
@FXML lateinit var txtMetaspace: JFXTextField
|
||||
@FXML lateinit var txtWrapper: JFXTextField
|
||||
@FXML lateinit var txtPrecallingCommand: JFXTextField
|
||||
@FXML lateinit var txtServerIP: JFXTextField
|
||||
@FXML lateinit var advancedSettingsPane: ComponentList
|
||||
@FXML lateinit var cboLauncherVisibility: JFXComboBox<*>
|
||||
@FXML lateinit var chkFullscreen: JFXCheckBox
|
||||
@FXML lateinit var lblPhysicalMemory: Label
|
||||
@FXML lateinit var chkNoJVMArgs: JFXToggleButton
|
||||
@FXML lateinit var chkNoCommon: JFXToggleButton
|
||||
@FXML lateinit var chkNoGameCheck: JFXToggleButton
|
||||
@FXML lateinit var javaItem: MultiFileItem
|
||||
@FXML lateinit var gameDirItem: MultiFileItem
|
||||
@FXML lateinit var chkShowLogs: JFXToggleButton
|
||||
@FXML lateinit var btnIconSelection: JFXButton
|
||||
@FXML lateinit var iconView: ImageView
|
||||
|
||||
lateinit var profile: Profile
|
||||
lateinit var versionId: String
|
||||
|
||||
fun initialize() {
|
||||
lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OperatingSystem.TOTAL_MEMORY}MB"
|
||||
|
||||
scroll.smoothScrolling()
|
||||
|
||||
val limit = 300.0
|
||||
//txtJavaDir.limitWidth(limit)
|
||||
txtMaxMemory.limitWidth(limit)
|
||||
cboLauncherVisibility.limitWidth(limit)
|
||||
|
||||
val limitHeight = 10.0
|
||||
chkNoJVMArgs.limitHeight(limitHeight)
|
||||
chkNoCommon.limitHeight(limitHeight)
|
||||
chkNoGameCheck.limitHeight(limitHeight)
|
||||
chkShowLogs.limitHeight(limitHeight)
|
||||
|
||||
fun validator(nullable: Boolean = false) = NumberValidator(nullable).apply { message = "Must be a number." }
|
||||
|
||||
txtWidth.setValidators(validator())
|
||||
txtWidth.setValidateWhileTextChanged()
|
||||
txtHeight.setValidators(validator())
|
||||
txtHeight.setValidateWhileTextChanged()
|
||||
txtMaxMemory.setValidators(validator())
|
||||
txtMaxMemory.setValidateWhileTextChanged()
|
||||
txtMetaspace.setValidators(validator(true))
|
||||
txtMetaspace.setValidateWhileTextChanged()
|
||||
|
||||
task {
|
||||
it["list"] = JavaVersion.getJREs().values.map { javaVersion ->
|
||||
javaItem.createChildren(javaVersion.version, javaVersion.binary.absolutePath, javaVersion)
|
||||
}
|
||||
}.subscribe(Schedulers.javafx()) {
|
||||
javaItem.loadChildren(it.get<Collection<Node>>("list"))
|
||||
}
|
||||
|
||||
gameDirItem.loadChildren(listOf(
|
||||
gameDirItem.createChildren(i18n("advancedsettings.game_dir.default"), userData = EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(i18n("advancedsettings.game_dir.independent"), userData = EnumGameDirectory.VERSION_FOLDER)
|
||||
))
|
||||
}
|
||||
|
||||
fun loadVersionSetting(profile: Profile, versionId: String, version: VersionSetting) {
|
||||
rootPane.children -= advancedSettingsPane
|
||||
|
||||
this.profile = profile
|
||||
this.versionId = versionId
|
||||
|
||||
lastVersionSetting?.apply {
|
||||
widthProperty().unbind()
|
||||
heightProperty().unbind()
|
||||
maxMemoryProperty().unbind()
|
||||
javaArgsProperty().unbind()
|
||||
minecraftArgsProperty().unbind()
|
||||
permSizeProperty().unbind()
|
||||
wrapperProperty().unbind()
|
||||
preLaunchCommandProperty().unbind()
|
||||
serverIpProperty().unbind()
|
||||
fullscreenProperty().unbind()
|
||||
notCheckGameProperty().unbind()
|
||||
noCommonProperty().unbind()
|
||||
javaDirProperty().unbind()
|
||||
showLogsProperty().unbind()
|
||||
unbindEnum(cboLauncherVisibility)
|
||||
}
|
||||
|
||||
bindInt(txtWidth, version.widthProperty())
|
||||
bindInt(txtHeight, version.heightProperty())
|
||||
bindInt(txtMaxMemory, version.maxMemoryProperty())
|
||||
bindString(javaItem.txtCustom, version.javaDirProperty())
|
||||
bindString(gameDirItem.txtCustom, version.gameDirProperty())
|
||||
bindString(txtJVMArgs, version.javaArgsProperty())
|
||||
bindString(txtGameArgs, version.minecraftArgsProperty())
|
||||
bindString(txtMetaspace, version.permSizeProperty())
|
||||
bindString(txtWrapper, version.wrapperProperty())
|
||||
bindString(txtPrecallingCommand, version.preLaunchCommandProperty())
|
||||
bindString(txtServerIP, version.serverIpProperty())
|
||||
bindEnum(cboLauncherVisibility, version.launcherVisibilityProperty())
|
||||
bindBoolean(chkFullscreen, version.fullscreenProperty())
|
||||
bindBoolean(chkNoGameCheck, version.notCheckGameProperty())
|
||||
bindBoolean(chkNoCommon, version.noCommonProperty())
|
||||
bindBoolean(chkShowLogs, version.showLogsProperty())
|
||||
|
||||
val javaGroupKey = "java_group.listener"
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(javaItem.group.properties[javaGroupKey] as? ChangeListener<in Toggle>?)
|
||||
?.run(javaItem.group.selectedToggleProperty()::removeListener)
|
||||
|
||||
var flag = false
|
||||
var defaultToggle: JFXRadioButton? = null
|
||||
javaItem.group.toggles.filter { it is JFXRadioButton }.forEach { toggle ->
|
||||
if (toggle.userData == version.javaVersion) {
|
||||
toggle.isSelected = true
|
||||
flag = true
|
||||
} else if (toggle.userData == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle as JFXRadioButton
|
||||
}
|
||||
}
|
||||
|
||||
val listener = ChangeListener<Toggle> { _, _, newValue ->
|
||||
if (newValue == javaItem.radioCustom) { // Custom
|
||||
version.java = "Custom"
|
||||
} else {
|
||||
version.java = ((newValue as JFXRadioButton).userData as JavaVersion).version
|
||||
}
|
||||
}
|
||||
javaItem.group.properties[javaGroupKey] = listener
|
||||
javaItem.group.selectedToggleProperty().addListener(listener)
|
||||
|
||||
if (!flag) {
|
||||
defaultToggle?.isSelected = true
|
||||
}
|
||||
|
||||
version.javaDirProperty().setChangedListener { initJavaSubtitle(version) }
|
||||
version.javaProperty().setChangedListener { initJavaSubtitle(version) }
|
||||
initJavaSubtitle(version)
|
||||
|
||||
val gameDirKey = "game_dir.listener"
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(gameDirItem.group.properties[gameDirKey] as? ChangeListener<in Toggle>?)
|
||||
?.run(gameDirItem.group.selectedToggleProperty()::removeListener)
|
||||
|
||||
gameDirItem.group.toggles.filter { it is JFXRadioButton }.forEach { toggle ->
|
||||
if (toggle.userData == version.gameDirType) {
|
||||
toggle.isSelected = true
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
|
||||
gameDirItem.radioCustom.userData = EnumGameDirectory.CUSTOM
|
||||
|
||||
val gameDirListener = ChangeListener<Toggle> { _, _, newValue ->
|
||||
version.gameDirType = (newValue as JFXRadioButton).userData as EnumGameDirectory
|
||||
}
|
||||
gameDirItem.group.properties[gameDirKey] = gameDirListener
|
||||
gameDirItem.group.selectedToggleProperty().addListener(gameDirListener)
|
||||
|
||||
version.gameDirProperty().setChangedListener { initGameDirSubtitle(version) }
|
||||
version.gameDirTypeProperty().setChangedListener { initGameDirSubtitle(version) }
|
||||
initGameDirSubtitle(version)
|
||||
|
||||
lastVersionSetting = version
|
||||
|
||||
loadIcon()
|
||||
}
|
||||
|
||||
private fun initJavaSubtitle(version: VersionSetting) {
|
||||
task { it["java"] = version.javaVersion }
|
||||
.subscribe(task(Schedulers.javafx()) { javaItem.subtitle = it.get<JavaVersion?>("java")?.binary?.absolutePath ?: "Invalid Java Directory" })
|
||||
}
|
||||
|
||||
private fun initGameDirSubtitle(version: VersionSetting) {
|
||||
gameDirItem.subtitle = profile.repository.getRunDirectory(versionId).absolutePath
|
||||
}
|
||||
|
||||
fun onShowAdvanced() {
|
||||
if (!rootPane.children.contains(advancedSettingsPane))
|
||||
rootPane.children += advancedSettingsPane
|
||||
else
|
||||
rootPane.children.remove(advancedSettingsPane)
|
||||
}
|
||||
|
||||
fun onExploreIcon() {
|
||||
val chooser = FileChooser()
|
||||
chooser.extensionFilters += FileChooser.ExtensionFilter("Image", "*.png")
|
||||
val selectedFile = chooser.showOpenDialog(Controllers.stage)
|
||||
if (selectedFile != null) {
|
||||
val iconFile = profile.repository.getVersionIcon(versionId)
|
||||
selectedFile.copyTo(iconFile, overwrite = true)
|
||||
loadIcon()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadIcon() {
|
||||
val iconFile = profile.repository.getVersionIcon(versionId)
|
||||
if (iconFile.exists())
|
||||
iconView.image = Image("file:" + iconFile.absolutePath)
|
||||
else
|
||||
iconView.image = DEFAULT_ICON
|
||||
iconView.limitSize(32.0, 32.0)
|
||||
}
|
||||
}
|
||||
@@ -1,82 +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
|
||||
|
||||
import com.jfoenix.controls.JFXDialog
|
||||
import com.jfoenix.controls.JFXPasswordField
|
||||
import com.jfoenix.controls.JFXProgressBar
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.auth.AuthInfo
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory
|
||||
import org.jackhuang.hmcl.game.HMCLMultiCharacterSelector
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import org.jackhuang.hmcl.util.taskResult
|
||||
|
||||
class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, private val success: (AuthInfo) -> Unit, private val failed: () -> Unit) : StackPane() {
|
||||
@FXML lateinit var lblUsername: Label
|
||||
@FXML lateinit var txtPassword: JFXPasswordField
|
||||
@FXML lateinit var lblCreationWarning: Label
|
||||
@FXML lateinit var progressBar: JFXProgressBar
|
||||
lateinit var dialog: JFXDialog
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/yggdrasil-account-login.fxml")
|
||||
|
||||
lblUsername.text = oldAccount.username
|
||||
txtPassword.setOnAction {
|
||||
onAccept()
|
||||
}
|
||||
}
|
||||
|
||||
fun onAccept() {
|
||||
val username = oldAccount.username
|
||||
val password = txtPassword.text
|
||||
progressBar.isVisible = true
|
||||
lblCreationWarning.text = ""
|
||||
taskResult("login") {
|
||||
try {
|
||||
val account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password)
|
||||
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.proxy)
|
||||
} catch (e: Exception) {
|
||||
e
|
||||
}
|
||||
}.subscribe(Schedulers.javafx()) {
|
||||
val account: Any = it["login"]
|
||||
if (account is AuthInfo) {
|
||||
success(account)
|
||||
dialog.close()
|
||||
} else if (account is InvalidCredentialsException) {
|
||||
lblCreationWarning.text = i18n("login.wrong_password")
|
||||
} else if (account is Exception) {
|
||||
lblCreationWarning.text = account.javaClass.toString() + ": " + account.localizedMessage
|
||||
}
|
||||
progressBar.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
fun onCancel() {
|
||||
failed()
|
||||
dialog.close()
|
||||
}
|
||||
}
|
||||
@@ -1,111 +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.construct
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXRadioButton
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ToggleGroup
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.DirectoryChooser
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.ui.Controllers
|
||||
import org.jackhuang.hmcl.ui.SVG
|
||||
import org.jackhuang.hmcl.ui.limitHeight
|
||||
import org.jackhuang.hmcl.util.*
|
||||
|
||||
class MultiFileItem : ComponentList() {
|
||||
val customTextProperty = SimpleStringProperty(this, "customText", "Custom")
|
||||
var customText by customTextProperty
|
||||
|
||||
val chooserTitleProperty = SimpleStringProperty(this, "chooserTitle", "Select a file")
|
||||
var chooserTitle by chooserTitleProperty
|
||||
|
||||
val group = ToggleGroup()
|
||||
val txtCustom = JFXTextField().apply {
|
||||
BorderPane.setAlignment(this, Pos.CENTER_RIGHT)
|
||||
}
|
||||
val btnSelect = JFXButton().apply {
|
||||
graphic = SVG.folderOpen("black", 15.0, 15.0)
|
||||
setOnMouseClicked {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
val radioCustom = JFXRadioButton().apply {
|
||||
textProperty().bind(customTextProperty)
|
||||
toggleGroup = group
|
||||
}
|
||||
val custom = BorderPane().apply {
|
||||
left = radioCustom
|
||||
style = "-fx-padding: 3;"
|
||||
right = HBox().apply {
|
||||
spacing = 3.0
|
||||
children += txtCustom
|
||||
children += btnSelect
|
||||
}
|
||||
limitHeight(20.0)
|
||||
}
|
||||
|
||||
val pane = VBox().apply {
|
||||
style = "-fx-padding: 0 0 10 0;"
|
||||
spacing = 8.0
|
||||
children += custom
|
||||
}
|
||||
|
||||
init {
|
||||
addChildren(pane)
|
||||
|
||||
txtCustom.disableProperty().bind(radioCustom.selectedProperty().not())
|
||||
btnSelect.disableProperty().bind(radioCustom.selectedProperty().not())
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun createChildren(title: String, subtitle: String = "", userData: Any? = null): Node {
|
||||
return BorderPane().apply {
|
||||
style = "-fx-padding: 3;"
|
||||
limitHeight(20.0)
|
||||
left = JFXRadioButton(title).apply {
|
||||
toggleGroup = group
|
||||
this.userData = userData
|
||||
}
|
||||
right = Label(subtitle).apply {
|
||||
styleClass += "subtitle-label"
|
||||
style += "-fx-font-size: 10;"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadChildren(list: Collection<Node>) {
|
||||
pane.children.setAll(list)
|
||||
pane.children += custom
|
||||
}
|
||||
|
||||
fun onExploreJavaDir() {
|
||||
val chooser = DirectoryChooser()
|
||||
chooser.title = i18n(chooserTitle)
|
||||
val selectedDir = chooser.showDialog(Controllers.stage)
|
||||
if (selectedDir != null)
|
||||
txtCustom.text = selectedDir.absolutePath
|
||||
}
|
||||
}
|
||||
@@ -1,179 +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.construct
|
||||
|
||||
import com.jfoenix.controls.JFXRippler
|
||||
import javafx.animation.Transition
|
||||
import javafx.beans.DefaultProperty
|
||||
import javafx.beans.NamedArg
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.Paint
|
||||
import javafx.scene.shape.Rectangle
|
||||
import org.jackhuang.hmcl.util.getValue
|
||||
import org.jackhuang.hmcl.util.onChange
|
||||
import org.jackhuang.hmcl.util.onInvalidated
|
||||
import org.jackhuang.hmcl.util.setValue
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
@DefaultProperty("container")
|
||||
open class RipplerContainer(@NamedArg("container") container: Node): StackPane() {
|
||||
val containerProperty = SimpleObjectProperty<Node>(this, "container", null)
|
||||
@JvmName("containerProperty") get
|
||||
var container: Node by containerProperty
|
||||
|
||||
val ripplerFillProperty = SimpleObjectProperty<Paint>(this, "ripplerFill", null)
|
||||
@JvmName("ripplerFillProperty") get
|
||||
var ripplerFill: Paint? by ripplerFillProperty
|
||||
|
||||
val selectedProperty = SimpleBooleanProperty(this, "selected", false)
|
||||
@JvmName("selectedProperty") get
|
||||
var selected: Boolean by selectedProperty
|
||||
|
||||
private val buttonContainer = StackPane()
|
||||
private val buttonRippler = object : JFXRippler(StackPane()) {
|
||||
override fun getMask(): Node {
|
||||
val mask = StackPane()
|
||||
mask.shapeProperty().bind(buttonContainer.shapeProperty())
|
||||
mask.backgroundProperty().bind(Bindings.createObjectBinding<Background>(Callable<Background> { Background(BackgroundFill(Color.WHITE, if (buttonContainer.backgroundProperty().get() != null && buttonContainer.getBackground().getFills().size > 0) buttonContainer.background.fills[0].radii else defaultRadii, if (buttonContainer.backgroundProperty().get() != null && buttonContainer.background.fills.size > 0) buttonContainer.background.fills[0].insets else Insets.EMPTY)) }, buttonContainer.backgroundProperty()))
|
||||
mask.resize(buttonContainer.width - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(), buttonContainer.height - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset())
|
||||
return mask
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
this.ripplerPane.setOnMousePressed { event ->
|
||||
if (releaseManualRippler != null) {
|
||||
releaseManualRippler!!.run()
|
||||
}
|
||||
|
||||
releaseManualRippler = null
|
||||
this.createRipple(event.x, event.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
private var clickedAnimation: Transition? = null
|
||||
private val defaultRadii = CornerRadii(3.0)
|
||||
private var invalid = true
|
||||
private var releaseManualRippler: Runnable? = null
|
||||
|
||||
init {
|
||||
styleClass += "rippler-container"
|
||||
this.container = container
|
||||
this.buttonContainer.children.add(this.buttonRippler)
|
||||
setOnMousePressed {
|
||||
if (this.clickedAnimation != null) {
|
||||
this.clickedAnimation!!.rate = 1.0
|
||||
this.clickedAnimation!!.play()
|
||||
}
|
||||
|
||||
}
|
||||
setOnMouseReleased {
|
||||
if (this.clickedAnimation != null) {
|
||||
this.clickedAnimation!!.rate = -1.0
|
||||
this.clickedAnimation!!.play()
|
||||
}
|
||||
|
||||
}
|
||||
focusedProperty().onChange {
|
||||
if (it) {
|
||||
if (!isPressed) {
|
||||
this.buttonRippler.showOverlay()
|
||||
}
|
||||
} else {
|
||||
this.buttonRippler.hideOverlay()
|
||||
}
|
||||
|
||||
}
|
||||
pressedProperty().onInvalidated(this.buttonRippler::hideOverlay)
|
||||
isPickOnBounds = false
|
||||
this.buttonContainer.isPickOnBounds = false
|
||||
this.buttonContainer.shapeProperty().bind(shapeProperty())
|
||||
this.buttonContainer.borderProperty().bind(borderProperty())
|
||||
this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding<Background>(Callable<Background> {
|
||||
if (background == null || this.isJavaDefaultBackground(background) || this.isJavaDefaultClickedBackground(background)) {
|
||||
background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null))
|
||||
}
|
||||
|
||||
try {
|
||||
return@Callable(
|
||||
if (background != null && (background.fills[0] as BackgroundFill).insets == Insets(-0.2, -0.2, -0.2, -0.2))
|
||||
Background(BackgroundFill((if (background != null) (background.fills[0] as BackgroundFill).fill else Color.TRANSPARENT) as Paint,
|
||||
if (backgroundProperty().get() != null) background.fills[0].radii else defaultRadii, Insets.EMPTY))
|
||||
else
|
||||
Background(BackgroundFill((if (background != null) background.fills[0].fill else Color.TRANSPARENT) as Paint,
|
||||
if (background != null) background.fills[0].radii else defaultRadii, Insets.EMPTY))
|
||||
)
|
||||
} catch (var3: Exception) {
|
||||
return@Callable background
|
||||
}
|
||||
}, backgroundProperty()))
|
||||
ripplerFillProperty.onChange { this.buttonRippler.ripplerFill = it }
|
||||
if (background == null || this.isJavaDefaultBackground(background)) {
|
||||
background = Background(BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null))
|
||||
}
|
||||
|
||||
this.updateChildren()
|
||||
|
||||
containerProperty.onInvalidated(this::updateChildren)
|
||||
selectedProperty.onInvalidated {
|
||||
if (selected) background = Background(BackgroundFill(ripplerFill, defaultRadii, null))
|
||||
else background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null))
|
||||
}
|
||||
|
||||
shape = Rectangle().apply {
|
||||
widthProperty().bind(this@RipplerContainer.widthProperty())
|
||||
heightProperty().bind(this@RipplerContainer.heightProperty())
|
||||
}
|
||||
}
|
||||
|
||||
protected fun updateChildren() {
|
||||
children.addAll(buttonContainer, container)
|
||||
|
||||
for (i in 1..this.children.size - 1) {
|
||||
this.children[i].isPickOnBounds = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun isJavaDefaultBackground(background: Background): Boolean {
|
||||
try {
|
||||
val firstFill = (background.fills[0] as BackgroundFill).fill.toString()
|
||||
return "0xffffffba" == firstFill || "0xffffffbf" == firstFill || "0xffffffbd" == firstFill
|
||||
} catch (var3: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun isJavaDefaultClickedBackground(background: Background): Boolean {
|
||||
try {
|
||||
return "0x039ed3ff" == (background.fills[0] as BackgroundFill).fill.toString()
|
||||
} catch (var3: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,117 +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.download
|
||||
|
||||
import javafx.scene.Node
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManifest
|
||||
import org.jackhuang.hmcl.game.MultiMCInstallVersionSettingTask
|
||||
import org.jackhuang.hmcl.mod.*
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider
|
||||
import org.jackhuang.hmcl.util.task
|
||||
import java.io.File
|
||||
|
||||
class DownloadWizardProvider(): WizardProvider {
|
||||
lateinit var profile: Profile
|
||||
|
||||
override fun start(settings: MutableMap<String, Any>) {
|
||||
profile = Settings.INSTANCE.selectedProfile
|
||||
settings[PROFILE] = profile
|
||||
}
|
||||
|
||||
private fun finishVersionDownloading(settings: MutableMap<String, Any>): Task {
|
||||
|
||||
val builder = profile.dependency.gameBuilder()
|
||||
|
||||
builder.name(settings["name"] as String)
|
||||
builder.gameVersion(settings["game"] as String)
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
builder.version("forge", settings["forge"] as String)
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
builder.version("liteloader", settings["liteloader"] as String)
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version("optifine", settings["optifine"] as String)
|
||||
|
||||
return builder.buildAsync()
|
||||
}
|
||||
|
||||
private fun finishModpackInstalling(settings: MutableMap<String, Any>): Task? {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null
|
||||
|
||||
val selectedFile = settings[ModpackPage.MODPACK_FILE] as? File? ?: return null
|
||||
val modpack = settings[ModpackPage.MODPACK_CURSEFORGE_MANIFEST] as? Modpack? ?: return null
|
||||
val name = settings[ModpackPage.MODPACK_NAME] as? String? ?: return null
|
||||
|
||||
profile.repository.markVersionAsModpack(name)
|
||||
|
||||
val finalizeTask = task {
|
||||
profile.repository.refreshVersions()
|
||||
val vs = profile.specializeVersionSetting(name)
|
||||
profile.repository.undoMark(name)
|
||||
if (vs != null) {
|
||||
vs.gameDirType = EnumGameDirectory.VERSION_FOLDER
|
||||
}
|
||||
}
|
||||
|
||||
return when (modpack.manifest) {
|
||||
is CurseManifest -> CurseInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseManifest, name)
|
||||
is HMCLModpackManifest -> HMCLModpackInstallTask(profile, selectedFile, modpack, name)
|
||||
is MultiMCInstanceConfiguration -> MultiMCModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as MultiMCInstanceConfiguration, name).with(MultiMCInstallVersionSettingTask(profile, modpack.manifest as MultiMCInstanceConfiguration, name))
|
||||
else -> throw Error()
|
||||
}.with(finalizeTask)
|
||||
}
|
||||
|
||||
override fun finish(settings: MutableMap<String, Any>): Any? {
|
||||
return when (settings[InstallTypePage.INSTALL_TYPE]) {
|
||||
0 -> finishVersionDownloading(settings)
|
||||
1 -> finishModpackInstalling(settings)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPage(controller: WizardController, step: Int, settings: MutableMap<String, Any>): Node {
|
||||
val provider = profile.dependency.downloadProvider
|
||||
return when (step) {
|
||||
0 -> InstallTypePage(controller)
|
||||
1 -> when (settings[InstallTypePage.INSTALL_TYPE]) {
|
||||
0 -> VersionsPage(controller, "", provider, "game") { controller.onNext(InstallersPage(controller, profile.repository, provider)) }
|
||||
1 -> ModpackPage(controller)
|
||||
else -> throw Error()
|
||||
}
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancel(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROFILE = "PROFILE"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +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.download
|
||||
|
||||
import com.jfoenix.controls.JFXListView
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.ui.loadFXML
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage
|
||||
|
||||
class InstallTypePage(private val controller: WizardController): StackPane(), WizardPage {
|
||||
|
||||
@FXML lateinit var list: JFXListView<Any>
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/download/dltype.fxml")
|
||||
|
||||
list.setOnMouseClicked {
|
||||
controller.settings[INSTALL_TYPE] = list.selectionModel.selectedIndex
|
||||
controller.onNext()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cleanup(settings: MutableMap<String, Any>) {
|
||||
settings.remove(INSTALL_TYPE)
|
||||
}
|
||||
|
||||
override fun getTitle() = "Select an operation"
|
||||
|
||||
companion object {
|
||||
const val INSTALL_TYPE: String = "INSTALL_TYPE"
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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.download
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXListView
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.ui.construct.Validator
|
||||
import org.jackhuang.hmcl.ui.loadFXML
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage
|
||||
import org.jackhuang.hmcl.util.onInvalidated
|
||||
|
||||
class InstallersPage(private val controller: WizardController, private val repository: GameRepository, private val downloadProvider: DownloadProvider): StackPane(), WizardPage {
|
||||
|
||||
@FXML lateinit var list: VBox
|
||||
@FXML lateinit var btnForge: JFXButton
|
||||
@FXML lateinit var btnLiteLoader: JFXButton
|
||||
@FXML lateinit var btnOptiFine: JFXButton
|
||||
@FXML lateinit var lblGameVersion: Label
|
||||
@FXML lateinit var lblForge: Label
|
||||
@FXML lateinit var lblLiteLoader: Label
|
||||
@FXML lateinit var lblOptiFine: Label
|
||||
@FXML lateinit var txtName: JFXTextField
|
||||
@FXML lateinit var btnInstall: JFXButton
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/download/installers.fxml")
|
||||
|
||||
val gameVersion = controller.settings["game"] as String
|
||||
txtName.validators += Validator { !repository.hasVersion(it) && it.isNotBlank() }.apply { message = i18n("version.already_exists") }
|
||||
txtName.textProperty().onInvalidated { btnInstall.isDisable = !txtName.validate() }
|
||||
txtName.text = gameVersion
|
||||
|
||||
btnForge.setOnMouseClicked {
|
||||
controller.settings[INSTALLER_TYPE] = 0
|
||||
controller.onNext(VersionsPage(controller, gameVersion, downloadProvider, "forge") { controller.onPrev(false) })
|
||||
}
|
||||
|
||||
btnLiteLoader.setOnMouseClicked {
|
||||
controller.settings[INSTALLER_TYPE] = 1
|
||||
controller.onNext(VersionsPage(controller, gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) })
|
||||
}
|
||||
|
||||
btnOptiFine.setOnMouseClicked {
|
||||
controller.settings[INSTALLER_TYPE] = 2
|
||||
controller.onNext(VersionsPage(controller, gameVersion, downloadProvider, "optifine") { controller.onPrev(false) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTitle() = "Choose a game version"
|
||||
|
||||
override fun onNavigate(settings: MutableMap<String, Any>) {
|
||||
lblGameVersion.text = "Current Game Version: ${controller.settings["game"]}"
|
||||
if (controller.settings.containsKey("forge"))
|
||||
lblForge.text = "Forge Versoin: ${controller.settings["forge"]}"
|
||||
else
|
||||
lblForge.text = "Forge not installed"
|
||||
|
||||
if (controller.settings.containsKey("liteloader"))
|
||||
lblLiteLoader.text = "LiteLoader Versoin: ${controller.settings["liteloader"]}"
|
||||
else
|
||||
lblLiteLoader.text = "LiteLoader not installed"
|
||||
|
||||
if (controller.settings.containsKey("optifine"))
|
||||
lblOptiFine.text = "OptiFine Versoin: ${controller.settings["optifine"]}"
|
||||
else
|
||||
lblOptiFine.text = "OptiFine not installed"
|
||||
}
|
||||
|
||||
override fun cleanup(settings: MutableMap<String, Any>) {
|
||||
settings.remove(INSTALLER_TYPE)
|
||||
}
|
||||
|
||||
fun onInstall() {
|
||||
controller.settings["name"] = txtName.text
|
||||
controller.onFinish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val INSTALLER_TYPE = "INSTALLER_TYPE"
|
||||
}
|
||||
}
|
||||
@@ -1,108 +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.download
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.application.Platform
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.Region
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.stage.FileChooser
|
||||
import org.jackhuang.hmcl.game.ModpackHelper
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.mod.Modpack
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.ui.*
|
||||
import org.jackhuang.hmcl.ui.construct.Validator
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage
|
||||
import org.jackhuang.hmcl.util.onInvalidated
|
||||
|
||||
class ModpackPage(private val controller: WizardController): StackPane(), WizardPage {
|
||||
private val title: String = i18n("modpack.task.install")
|
||||
override fun getTitle() = title
|
||||
|
||||
@FXML lateinit var borderPane: Region
|
||||
@FXML lateinit var lblName: Label
|
||||
@FXML lateinit var lblVersion: Label
|
||||
@FXML lateinit var lblAuthor: Label
|
||||
@FXML lateinit var lblModpackLocation: Label
|
||||
@FXML lateinit var txtModpackName: JFXTextField
|
||||
@FXML lateinit var btnInstall: JFXButton
|
||||
var manifest: Modpack? = null
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/download/modpack.fxml")
|
||||
|
||||
val profile = controller.settings["PROFILE"] as Profile
|
||||
|
||||
val chooser = FileChooser()
|
||||
chooser.title = i18n("modpack.choose")
|
||||
chooser.extensionFilters += FileChooser.ExtensionFilter(i18n("modpack"), "*.zip")
|
||||
val selectedFile = chooser.showOpenDialog(Controllers.stage)
|
||||
if (selectedFile == null) Platform.runLater { controller.onFinish() }
|
||||
else {
|
||||
// TODO: original HMCL modpack support.
|
||||
controller.settings[MODPACK_FILE] = selectedFile
|
||||
lblModpackLocation.text = selectedFile.absolutePath
|
||||
txtModpackName.validators += Validator { !profile.repository.hasVersion(it) && it.isNotBlank() }.apply { message = i18n("version.already_exists") }
|
||||
txtModpackName.textProperty().onInvalidated { btnInstall.isDisable = !txtModpackName.validate() }
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile)
|
||||
controller.settings[MODPACK_CURSEFORGE_MANIFEST] = manifest!!
|
||||
lblName.text = manifest!!.name
|
||||
lblVersion.text = manifest!!.version
|
||||
lblAuthor.text = manifest!!.author
|
||||
txtModpackName.text = manifest!!.name + (if (manifest!!.version.isNullOrBlank()) "" else ("-" + manifest!!.version))
|
||||
} catch (e: Exception) {
|
||||
// TODO
|
||||
txtModpackName.text = i18n("modpack.task.install.error")
|
||||
}
|
||||
}
|
||||
|
||||
//borderPane.limitHeight(100.0)
|
||||
borderPane.limitWidth(500.0)
|
||||
}
|
||||
|
||||
override fun cleanup(settings: MutableMap<String, Any>) {
|
||||
settings.remove(MODPACK_FILE)
|
||||
}
|
||||
|
||||
fun onInstall() {
|
||||
if (!txtModpackName.validate()) return
|
||||
controller.settings[MODPACK_NAME] = txtModpackName.text
|
||||
controller.onFinish()
|
||||
}
|
||||
|
||||
fun onDescribe() {
|
||||
if (manifest != null)
|
||||
WebStage().apply {
|
||||
webView.engine.loadContent(manifest!!.description)
|
||||
title = i18n("modpack.wizard.step.3")
|
||||
}.showAndWait()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val MODPACK_FILE = "MODPACK_FILE"
|
||||
val MODPACK_NAME = "MODPACK_NAME"
|
||||
val MODPACK_CURSEFORGE_MANIFEST = "CURSEFORGE_MANIFEST"
|
||||
}
|
||||
}
|
||||
@@ -1,137 +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
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities
|
||||
import com.jfoenix.controls.JFXProgressBar
|
||||
import javafx.application.Platform
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import org.jackhuang.hmcl.task.*
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
interface AbstractWizardDisplayer : WizardDisplayer {
|
||||
val wizardController: WizardController
|
||||
val cancelQueue: Queue<Any>
|
||||
|
||||
override fun handleDeferredWizardResult(settings: Map<String, Any>, deferredResult: DeferredWizardResult) {
|
||||
val vbox = VBox()
|
||||
val progressBar = JFXProgressBar()
|
||||
val label = Label()
|
||||
progressBar.maxHeight = 10.0
|
||||
vbox.children += progressBar
|
||||
vbox.children += label
|
||||
|
||||
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
|
||||
|
||||
cancelQueue.add(thread {
|
||||
deferredResult.start(settings, object : ResultProgressHandle {
|
||||
private var running = true
|
||||
|
||||
override fun setProgress(currentStep: Int, totalSteps: Int) {
|
||||
progressBar.progress = 1.0 * currentStep / totalSteps
|
||||
}
|
||||
|
||||
override fun setProgress(description: String, currentStep: Int, totalSteps: Int) {
|
||||
label.text = description
|
||||
progressBar.progress = 1.0 * currentStep / totalSteps
|
||||
}
|
||||
|
||||
override fun setBusy(description: String) {
|
||||
progressBar.progress = JFXProgressBar.INDETERMINATE_PROGRESS
|
||||
}
|
||||
|
||||
override fun finished(result: Any) {
|
||||
running = false
|
||||
}
|
||||
|
||||
override fun failed(message: String, canNavigateBack: Boolean) {
|
||||
label.text = message
|
||||
running = false
|
||||
}
|
||||
|
||||
override fun isRunning() = running
|
||||
|
||||
})
|
||||
|
||||
if (!Thread.currentThread().isInterrupted)
|
||||
JFXUtilities.runInFX {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun handleTask(settings: Map<String, Any>, task: Task) {
|
||||
val vbox = VBox()
|
||||
val tasksBar = JFXProgressBar()
|
||||
val label = Label()
|
||||
tasksBar.maxHeight = 10.0
|
||||
vbox.children += tasksBar
|
||||
vbox.children += label
|
||||
|
||||
var finishedTasks = 0
|
||||
|
||||
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
|
||||
|
||||
task.with(org.jackhuang.hmcl.util.task(Schedulers.javafx()) {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
}).executor().apply {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
taskListener = object : TaskListener() {
|
||||
override fun onReady(task: Task) {
|
||||
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks) }
|
||||
}
|
||||
|
||||
override fun onFinished(task: Task) {
|
||||
Platform.runLater {
|
||||
label.text = task.name
|
||||
++finishedTasks
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailed(task: Task, throwable: Throwable) {
|
||||
Platform.runLater {
|
||||
label.text = task.name
|
||||
++finishedTasks
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
Platform.runLater { navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cancelQueue.add(this)
|
||||
}.start()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
while (cancelQueue.isNotEmpty()) {
|
||||
val x = cancelQueue.poll()
|
||||
when (x) {
|
||||
is TaskExecutor -> x.cancel()
|
||||
is Thread -> x.interrupt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +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
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXToolbar
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.ui.Controllers
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||
import org.jackhuang.hmcl.ui.loadFXML
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider: WizardProvider) : StackPane(), AbstractWizardDisplayer {
|
||||
|
||||
override val wizardController = WizardController(this).apply { provider = wizardProvider }
|
||||
override val cancelQueue: Queue<Any> = ConcurrentLinkedQueue<Any>()
|
||||
|
||||
lateinit var transitionHandler: TransitionHandler
|
||||
|
||||
@FXML lateinit var root: StackPane
|
||||
@FXML lateinit var backButton: JFXButton
|
||||
@FXML lateinit var toolbar: JFXToolbar
|
||||
/**
|
||||
* Only shown if it is needed in now step.
|
||||
*/
|
||||
@FXML lateinit var refreshButton: JFXButton
|
||||
@FXML lateinit var titleLabel: Label
|
||||
|
||||
lateinit var nowPage: Node
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/wizard.fxml")
|
||||
toolbar.effect = null
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
transitionHandler = TransitionHandler(root)
|
||||
|
||||
wizardController.onStart()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
}
|
||||
|
||||
override fun onEnd() {
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
|
||||
}
|
||||
|
||||
fun back() {
|
||||
wizardController.onPrev(true)
|
||||
}
|
||||
|
||||
fun close() {
|
||||
wizardController.onCancel()
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
(nowPage as Refreshable).refresh()
|
||||
}
|
||||
|
||||
override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) {
|
||||
backButton.isDisable = !wizardController.canPrev()
|
||||
transitionHandler.setContent(page, nav.animation.animationProducer)
|
||||
val title = if (prefix.isEmpty()) "" else "$prefix - "
|
||||
if (page is WizardPage)
|
||||
titleLabel.text = title + page.title
|
||||
refreshButton.isVisible = page is Refreshable
|
||||
nowPage = page
|
||||
}
|
||||
}
|
||||
@@ -1,95 +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
|
||||
|
||||
import javafx.scene.Node
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import java.util.*
|
||||
|
||||
class WizardController(protected val displayer: WizardDisplayer) : Navigation {
|
||||
lateinit var provider: WizardProvider
|
||||
val settings = mutableMapOf<String, Any>()
|
||||
val pages = Stack<Node>()
|
||||
|
||||
override fun onStart() {
|
||||
settings.clear()
|
||||
provider.start(settings)
|
||||
|
||||
pages.clear()
|
||||
val page = navigatingTo(0)
|
||||
pages.push(page)
|
||||
|
||||
if (page is WizardPage)
|
||||
page.onNavigate(settings)
|
||||
|
||||
displayer.onStart()
|
||||
displayer.navigateTo(page, Navigation.NavigationDirection.START)
|
||||
}
|
||||
|
||||
override fun onNext() {
|
||||
onNext(navigatingTo(pages.size))
|
||||
}
|
||||
|
||||
fun onNext(page: Node) {
|
||||
pages.push(page)
|
||||
|
||||
if (page is WizardPage)
|
||||
page.onNavigate(settings)
|
||||
|
||||
displayer.navigateTo(page, Navigation.NavigationDirection.NEXT)
|
||||
}
|
||||
|
||||
override fun onPrev(cleanUp: Boolean) {
|
||||
val page = pages.pop()
|
||||
if (cleanUp && page is WizardPage)
|
||||
page.cleanup(settings)
|
||||
|
||||
val prevPage = pages.peek()
|
||||
if (prevPage is WizardPage)
|
||||
prevPage.onNavigate(settings)
|
||||
|
||||
displayer.navigateTo(prevPage, Navigation.NavigationDirection.PREVIOUS)
|
||||
}
|
||||
|
||||
override fun canPrev() = pages.size > 1
|
||||
|
||||
override fun onFinish() {
|
||||
val result = provider.finish(settings)
|
||||
when (result) {
|
||||
is DeferredWizardResult -> displayer.handleDeferredWizardResult(settings, result)
|
||||
is Summary -> displayer.navigateTo(result.component, Navigation.NavigationDirection.NEXT)
|
||||
is Task -> displayer.handleTask(settings, result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnd() {
|
||||
settings.clear()
|
||||
pages.clear()
|
||||
displayer.onEnd()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
displayer.onCancel()
|
||||
onEnd()
|
||||
}
|
||||
|
||||
fun navigatingTo(step: Int): Node {
|
||||
return provider.createPage(this, step, settings)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,123 +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.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import javafx.beans.property.Property
|
||||
import javafx.event.Event.fireEvent
|
||||
import org.jackhuang.hmcl.event.Event
|
||||
import org.jackhuang.hmcl.event.EventBus
|
||||
import org.jackhuang.hmcl.event.EventManager
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Schedulers
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.util.Constants.UI_THREAD_SCHEDULER
|
||||
import java.io.InputStream
|
||||
import java.lang.reflect.Type
|
||||
import java.net.URL
|
||||
import java.rmi.activation.Activatable.unregister
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
inline fun ignoreException(func: () -> Unit) {
|
||||
try {
|
||||
func()
|
||||
} catch(ignore: Exception) {}
|
||||
}
|
||||
|
||||
inline fun ignoreThrowable(func: () -> Unit) {
|
||||
try {
|
||||
func()
|
||||
} catch (ignore: Throwable) {}
|
||||
}
|
||||
|
||||
fun isBlank(str: String?) = str?.isBlank() ?: true
|
||||
fun isNotBlank(str: String?) = !isBlank(str)
|
||||
|
||||
fun String.tokenize(delim: String = " \t\n\r"): List<String> {
|
||||
val list = mutableListOf<String>()
|
||||
val tokenizer = StringTokenizer(this, delim)
|
||||
while (tokenizer.hasMoreTokens())
|
||||
list.add(tokenizer.nextToken())
|
||||
return list
|
||||
}
|
||||
|
||||
fun String.asVersion(): String? {
|
||||
if (count { it != '.' && (it < '0' || it > '9') } > 0 || isBlank())
|
||||
return null
|
||||
val s = split(".")
|
||||
for (i in s) if (i.isBlank()) return null
|
||||
val builder = StringBuilder()
|
||||
var last = s.size - 1
|
||||
for (i in s.size - 1 downTo 0)
|
||||
if (s[i].toInt() == 0)
|
||||
last = i
|
||||
for (i in 0 .. last)
|
||||
builder.append(s[i]).append('.')
|
||||
return builder.deleteCharAt(builder.length - 1).toString()
|
||||
}
|
||||
|
||||
fun Any?.toStringOrEmpty() = this?.toString().orEmpty()
|
||||
|
||||
fun String.toURL() = URL(this)
|
||||
|
||||
fun Collection<String>.containsOne(vararg matcher: String): Boolean {
|
||||
for (a in this)
|
||||
for (b in matcher)
|
||||
if (a.toLowerCase().contains(b.toLowerCase()))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun <T> Property<in T>.updateAsync(newValue: T, update: AtomicReference<T>) {
|
||||
if (update.getAndSet(newValue) == null) {
|
||||
UI_THREAD_SCHEDULER.accept(Runnable {
|
||||
val current = update.getAndSet(null)
|
||||
this.value = current
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> typeOf(): Type = object : TypeToken<T>() {}.type
|
||||
|
||||
inline fun <reified T> Gson.fromJson(json: String): T? = fromJson<T>(json, T::class.java)
|
||||
|
||||
inline fun <reified T> Gson.fromJsonQuietly(json: String): T? {
|
||||
try {
|
||||
return fromJson<T>(json)
|
||||
} catch (json: JsonParseException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun task(scheduler: Scheduler = Schedulers.defaultScheduler(), closure: (AutoTypingMap<String>) -> Unit): Task = Task.of(closure, scheduler)
|
||||
fun <V> taskResult(id: String, callable: Callable<V>): TaskResult<V> = Task.ofResult(id, callable)
|
||||
fun <V> taskResult(id: String, callable: (AutoTypingMap<String>) -> V): TaskResult<V> = Task.ofResult(id, callable)
|
||||
|
||||
fun InputStream.readFullyAsString() = IOUtils.readFullyAsString(this)
|
||||
inline fun <reified T : Event> EventBus.channel() = channel(T::class.java)
|
||||
|
||||
operator fun <T : Event> EventManager<T>.plusAssign(func: (T) -> Unit) = register(func)
|
||||
operator fun <T : Event> EventManager<T>.plusAssign(func: () -> Unit) = register(func)
|
||||
operator fun <T : Event> EventManager<T>.minusAssign(func: (T) -> Unit) = unregister(func)
|
||||
operator fun <T : Event> EventManager<T>.minusAssign(func: () -> Unit) = unregister(func)
|
||||
operator fun <T : Event> EventManager<T>.invoke(event: T) = fireEvent(event)
|
||||
@@ -1,44 +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.util
|
||||
|
||||
import javafx.beans.value.*
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
||||
|
||||
fun <T> ObservableValue<T>.onChange(op: (T?) -> Unit) = apply { addListener { _, _, new -> op(new) } }
|
||||
fun <T> ObservableValue<T>.onChangeAndOperate(op: (T?) -> Unit) = apply { addListener { _, _, new -> op(new) }; op(value) }
|
||||
fun <T> ObservableValue<T>.onChangeAndOperateWeakly(op: (T?) -> Unit) = apply { addListener(WeakChangeListener { _, _, new -> op(new) }); op(value) }
|
||||
fun ObservableBooleanValue.onChange(op: (Boolean) -> Unit) = apply { addListener { _, _, new -> op(new ?: false) } }
|
||||
fun ObservableIntegerValue.onChange(op: (Int) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0).toInt()) } }
|
||||
fun ObservableLongValue.onChange(op: (Long) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0L).toLong()) } }
|
||||
fun ObservableFloatValue.onChange(op: (Float) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0f).toFloat()) } }
|
||||
fun ObservableDoubleValue.onChange(op: (Double) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0.0).toDouble()) } }
|
||||
fun <T> ObservableList<T>.onChange(op: (ListChangeListener.Change<out T>) -> Unit) = apply {
|
||||
addListener(ListChangeListener { op(it) })
|
||||
}
|
||||
|
||||
fun <T> ObservableValue<*>.onInvalidated(op: () -> T) = apply { addListener { _ -> op() } }
|
||||
|
||||
fun <T> ObservableValue<T>.setOnChangeListener(properties: MutableMap<Any, Any>, key: String = "changeListener", changeListener: ChangeListener<in T>) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (properties.containsKey(key))
|
||||
removeListener(properties[key] as ChangeListener<in T>)
|
||||
properties[key] = changeListener
|
||||
addListener(changeListener)
|
||||
}
|
||||
@@ -1,767 +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.util
|
||||
|
||||
import javafx.beans.Observable
|
||||
import javafx.beans.binding.*
|
||||
import javafx.beans.property.*
|
||||
import javafx.beans.value.*
|
||||
import javafx.collections.ObservableList
|
||||
import java.lang.ref.WeakReference
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.Callable
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
fun <T> property(value: T? = null) = PropertyDelegate(SimpleObjectProperty<T>(value))
|
||||
fun <T> property(block: () -> Property<T>) = PropertyDelegate(block())
|
||||
|
||||
class PropertyDelegate<T>(val fxProperty: Property<T>) : ReadWriteProperty<Any, T> {
|
||||
|
||||
override fun getValue(thisRef: Any, property: KProperty<*>): T {
|
||||
return fxProperty.value
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
|
||||
fxProperty.value = value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun <T> Any.getProperty(prop: KMutableProperty1<*, T>): ObjectProperty<T> {
|
||||
// avoid kotlin-reflect dependency
|
||||
val field = javaClass.findFieldByName("${prop.name}\$delegate")
|
||||
?: throw IllegalArgumentException("No delegate field then name '${prop.name}' found")
|
||||
|
||||
field.isAccessible = true
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val delegate = field.get(this) as PropertyDelegate<T>
|
||||
return delegate.fxProperty as ObjectProperty<T>
|
||||
}
|
||||
|
||||
fun Class<*>.findFieldByName(name: String): Field? {
|
||||
val field = (declaredFields + fields).find { it.name == name }
|
||||
if (field != null) return field
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
if (superclass == java.lang.Object::class.java) return null
|
||||
return superclass.findFieldByName(name)
|
||||
}
|
||||
|
||||
fun Class<*>.findMethodByName(name: String): Method? {
|
||||
val method = (declaredMethods + methods).find { it.name == name }
|
||||
if (method != null) return method
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
if (superclass == java.lang.Object::class.java) return null
|
||||
return superclass.findMethodByName(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an owner instance and a corresponding property reference into an observable
|
||||
*/
|
||||
fun <S, T> S.observable(prop: KMutableProperty1<S, T>) = observable(this, prop)
|
||||
|
||||
/**
|
||||
* Convert an owner instance and a corresponding property reference into an observable
|
||||
*/
|
||||
@JvmName("observableFromMutableProperty")
|
||||
fun <S, T> observable(owner: S, prop: KMutableProperty1<S, T>): ObjectProperty<T> {
|
||||
return object : SimpleObjectProperty<T>(owner, prop.name) {
|
||||
override fun get() = prop.get(owner)
|
||||
override fun set(v: T) = prop.set(owner, v)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an owner instance and a corresponding property reference into a readonly observable
|
||||
*/
|
||||
fun <S, T> observable(owner: S, prop: KProperty1<S, T>): ReadOnlyObjectProperty<T> {
|
||||
return object : ReadOnlyObjectWrapper<T>(owner, prop.name) {
|
||||
override fun get() = prop.get(owner)
|
||||
}
|
||||
}
|
||||
|
||||
open class PojoProperty<T>(bean: Any, propName: String) : SimpleObjectProperty<T>(bean, propName) {
|
||||
fun refresh() {
|
||||
fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
enum class SingleAssignThreadSafetyMode {
|
||||
SYNCHRONIZED,
|
||||
NONE
|
||||
}
|
||||
|
||||
fun <T> singleAssign(threadSafeyMode: SingleAssignThreadSafetyMode = SingleAssignThreadSafetyMode.SYNCHRONIZED): SingleAssign<T> =
|
||||
if (threadSafeyMode.equals(SingleAssignThreadSafetyMode.SYNCHRONIZED)) SynchronizedSingleAssign<T>() else UnsynchronizedSingleAssign<T>()
|
||||
|
||||
private object UNINITIALIZED_VALUE
|
||||
|
||||
interface SingleAssign<T> {
|
||||
fun isInitialized(): Boolean
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
|
||||
}
|
||||
|
||||
private class SynchronizedSingleAssign<T> : SingleAssign<T> {
|
||||
|
||||
@Volatile
|
||||
private var initialized = false
|
||||
|
||||
@Volatile
|
||||
private var _value: Any? = UNINITIALIZED_VALUE
|
||||
|
||||
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
if (!initialized)
|
||||
throw Exception("Value has not been assigned yet!")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return _value as T
|
||||
}
|
||||
|
||||
override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
synchronized(this) {
|
||||
if (initialized) {
|
||||
throw Exception("Value has already been assigned!")
|
||||
}
|
||||
_value = value
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInitialized() = initialized
|
||||
}
|
||||
|
||||
private class UnsynchronizedSingleAssign<T> : SingleAssign<T> {
|
||||
|
||||
private var initialized = false
|
||||
private var _value: Any? = UNINITIALIZED_VALUE
|
||||
|
||||
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
if (!initialized)
|
||||
throw Exception("Value has not been assigned yet!")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return _value as T
|
||||
}
|
||||
|
||||
override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
if (initialized) {
|
||||
throw Exception("Value has already been assigned!")
|
||||
}
|
||||
_value = value
|
||||
initialized = true
|
||||
}
|
||||
|
||||
override fun isInitialized() = initialized
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds this property to an observable, automatically unbinding it before if already bound.
|
||||
*/
|
||||
fun <T> Property<T>.cleanBind(observable: ObservableValue<T>) {
|
||||
unbind()
|
||||
bind(observable)
|
||||
}
|
||||
|
||||
operator fun <T> ObservableValue<T>.getValue(thisRef: Any, property: KProperty<*>) = value
|
||||
operator fun <T> Property<T?>.setValue(thisRef: Any, property: KProperty<*>, value: T?) = setValue(value)
|
||||
|
||||
operator fun ObservableDoubleValue.getValue(thisRef: Any, property: KProperty<*>) = get()
|
||||
operator fun DoubleProperty.setValue(thisRef: Any, property: KProperty<*>, value: Double) = set(value)
|
||||
|
||||
operator fun ObservableFloatValue.getValue(thisRef: Any, property: KProperty<*>) = get()
|
||||
operator fun FloatProperty.setValue(thisRef: Any, property: KProperty<*>, value: Float) = set(value)
|
||||
|
||||
operator fun ObservableLongValue.getValue(thisRef: Any, property: KProperty<*>) = get()
|
||||
operator fun LongProperty.setValue(thisRef: Any, property: KProperty<*>, value: Long) = set(value)
|
||||
|
||||
operator fun ObservableIntegerValue.getValue(thisRef: Any, property: KProperty<*>) = get()
|
||||
operator fun IntegerProperty.setValue(thisRef: Any, property: KProperty<*>, value: Int) = set(value)
|
||||
|
||||
operator fun ObservableBooleanValue.getValue(thisRef: Any, property: KProperty<*>) = get()
|
||||
operator fun BooleanProperty.setValue(thisRef: Any, property: KProperty<*>, value: Boolean) = set(value)
|
||||
|
||||
operator fun DoubleExpression.plus(other: Number): DoubleBinding = add(other.toDouble())
|
||||
operator fun DoubleExpression.plus(other: ObservableNumberValue): DoubleBinding = add(other)
|
||||
|
||||
operator fun DoubleProperty.plusAssign(other: Number) { value += other.toDouble() }
|
||||
operator fun DoubleProperty.plusAssign(other: ObservableNumberValue) { value += other.doubleValue() }
|
||||
|
||||
operator fun DoubleProperty.inc(): DoubleProperty {
|
||||
value++
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun DoubleExpression.minus(other: Number): DoubleBinding = subtract(other.toDouble())
|
||||
operator fun DoubleExpression.minus(other: ObservableNumberValue): DoubleBinding = subtract(other)
|
||||
|
||||
operator fun DoubleProperty.minusAssign(other: Number) { value -= other.toDouble() }
|
||||
operator fun DoubleProperty.minusAssign(other: ObservableNumberValue) { value -= other.doubleValue() }
|
||||
|
||||
operator fun DoubleExpression.unaryMinus(): DoubleBinding = negate()
|
||||
|
||||
|
||||
operator fun DoubleProperty.dec(): DoubleProperty {
|
||||
value--
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun DoubleExpression.times(other: Number): DoubleBinding = multiply(other.toDouble())
|
||||
operator fun DoubleExpression.times(other: ObservableNumberValue): DoubleBinding = multiply(other)
|
||||
|
||||
operator fun DoubleProperty.timesAssign(other: Number) { value *= other.toDouble() }
|
||||
operator fun DoubleProperty.timesAssign(other: ObservableNumberValue) { value *= other.doubleValue() }
|
||||
|
||||
operator fun DoubleExpression.div(other: Number): DoubleBinding = divide(other.toDouble())
|
||||
operator fun DoubleExpression.div(other: ObservableNumberValue): DoubleBinding = divide(other)
|
||||
|
||||
operator fun DoubleProperty.divAssign(other: Number) { value /= other.toDouble() }
|
||||
operator fun DoubleProperty.divAssign(other: ObservableNumberValue) { value /= other.doubleValue() }
|
||||
|
||||
|
||||
operator fun DoubleExpression.rem(other: Number): DoubleBinding = doubleBinding(this) { get() % other.toDouble() }
|
||||
operator fun DoubleExpression.rem(other: ObservableNumberValue): DoubleBinding = doubleBinding(this, other) { get() % other.doubleValue() }
|
||||
|
||||
operator fun DoubleProperty.remAssign(other: Number) { value %= other.toDouble() }
|
||||
operator fun DoubleProperty.remAssign(other: ObservableNumberValue) { value %= other.doubleValue() }
|
||||
|
||||
operator fun ObservableDoubleValue.compareTo(other: Number): Int {
|
||||
if (get() > other.toDouble())
|
||||
return 1
|
||||
else if (get() < other.toDouble())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
operator fun ObservableDoubleValue.compareTo(other: ObservableNumberValue): Int {
|
||||
if (get() > other.doubleValue())
|
||||
return 1
|
||||
else if (get() < other.doubleValue())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
operator fun FloatExpression.plus(other: Number): FloatBinding = add(other.toFloat())
|
||||
operator fun FloatExpression.plus(other: Double): DoubleBinding = add(other)
|
||||
operator fun FloatExpression.plus(other: ObservableNumberValue): FloatBinding = add(other) as FloatBinding
|
||||
operator fun FloatExpression.plus(other: ObservableDoubleValue): DoubleBinding = add(other) as DoubleBinding
|
||||
|
||||
operator fun FloatProperty.plusAssign(other: Number) { value += other.toFloat() }
|
||||
operator fun FloatProperty.plusAssign(other: ObservableNumberValue) { value += other.floatValue() }
|
||||
|
||||
operator fun FloatProperty.inc(): FloatProperty {
|
||||
value++
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun FloatExpression.minus(other: Number): FloatBinding = subtract(other.toFloat())
|
||||
operator fun FloatExpression.minus(other: Double): DoubleBinding = subtract(other)
|
||||
operator fun FloatExpression.minus(other: ObservableNumberValue): FloatBinding = subtract(other) as FloatBinding
|
||||
operator fun FloatExpression.minus(other: ObservableDoubleValue): DoubleBinding = subtract(other) as DoubleBinding
|
||||
|
||||
operator fun FloatProperty.minusAssign(other: Number) { value -= other.toFloat() }
|
||||
operator fun FloatProperty.minusAssign(other: ObservableNumberValue) { value -= other.floatValue() }
|
||||
|
||||
operator fun FloatExpression.unaryMinus(): FloatBinding = negate()
|
||||
|
||||
operator fun FloatProperty.dec(): FloatProperty {
|
||||
value--
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun FloatExpression.times(other: Number): FloatBinding = multiply(other.toFloat())
|
||||
operator fun FloatExpression.times(other: Double): DoubleBinding = multiply(other)
|
||||
operator fun FloatExpression.times(other: ObservableNumberValue): FloatBinding = multiply(other) as FloatBinding
|
||||
operator fun FloatExpression.times(other: ObservableDoubleValue): DoubleBinding = multiply(other) as DoubleBinding
|
||||
|
||||
operator fun FloatProperty.timesAssign(other: Number) { value *= other.toFloat() }
|
||||
operator fun FloatProperty.timesAssign(other: ObservableNumberValue) { value *= other.floatValue() }
|
||||
|
||||
|
||||
operator fun FloatExpression.div(other: Number): FloatBinding = divide(other.toFloat())
|
||||
operator fun FloatExpression.div(other: Double): DoubleBinding = divide(other)
|
||||
operator fun FloatExpression.div(other: ObservableNumberValue): FloatBinding = divide(other) as FloatBinding
|
||||
operator fun FloatExpression.div(other: ObservableDoubleValue): DoubleBinding = divide(other) as DoubleBinding
|
||||
|
||||
operator fun FloatProperty.divAssign(other: Number) { value /= other.toFloat() }
|
||||
operator fun FloatProperty.divAssign(other: ObservableNumberValue) { value /= other.floatValue() }
|
||||
|
||||
|
||||
operator fun FloatExpression.rem(other: Number): FloatBinding = floatBinding(this) { get() % other.toFloat() }
|
||||
operator fun FloatExpression.rem(other: Double): DoubleBinding = doubleBinding(this) { get() % other }
|
||||
operator fun FloatExpression.rem(other: ObservableNumberValue): FloatBinding = floatBinding(this, other) { get() % other.floatValue() }
|
||||
operator fun FloatExpression.rem(other: ObservableDoubleValue): DoubleBinding = doubleBinding(this, other) { get() % other.get() }
|
||||
|
||||
operator fun FloatProperty.remAssign(other: Number) { value %= other.toFloat() }
|
||||
operator fun FloatProperty.remAssign(other: ObservableNumberValue) { value %= other.floatValue() }
|
||||
|
||||
operator fun ObservableFloatValue.compareTo(other: Number): Int {
|
||||
if (get() > other.toFloat())
|
||||
return 1
|
||||
else if (get() < other.toFloat())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
operator fun ObservableFloatValue.compareTo(other: ObservableNumberValue): Int {
|
||||
if (get() > other.floatValue())
|
||||
return 1
|
||||
else if (get() < other.floatValue())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
operator fun IntegerExpression.plus(other: Int): IntegerBinding = add(other)
|
||||
operator fun IntegerExpression.plus(other: Long): LongBinding = add(other)
|
||||
operator fun IntegerExpression.plus(other: Float): FloatBinding = add(other)
|
||||
operator fun IntegerExpression.plus(other: Double): DoubleBinding = add(other)
|
||||
operator fun IntegerExpression.plus(other: ObservableIntegerValue): IntegerBinding = add(other) as IntegerBinding
|
||||
operator fun IntegerExpression.plus(other: ObservableLongValue): LongBinding = add(other) as LongBinding
|
||||
operator fun IntegerExpression.plus(other: ObservableFloatValue): FloatBinding = add(other) as FloatBinding
|
||||
operator fun IntegerExpression.plus(other: ObservableDoubleValue): DoubleBinding = add(other) as DoubleBinding
|
||||
|
||||
operator fun IntegerProperty.plusAssign(other: Number) { value += other.toInt() }
|
||||
operator fun IntegerProperty.plusAssign(other: ObservableNumberValue) { value += other.intValue() }
|
||||
|
||||
operator fun IntegerProperty.inc(): IntegerProperty {
|
||||
value++
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun IntegerExpression.minus(other: Int): IntegerBinding = subtract(other)
|
||||
operator fun IntegerExpression.minus(other: Long): LongBinding = subtract(other)
|
||||
operator fun IntegerExpression.minus(other: Float): FloatBinding = subtract(other)
|
||||
operator fun IntegerExpression.minus(other: Double): DoubleBinding = subtract(other)
|
||||
operator fun IntegerExpression.minus(other: ObservableIntegerValue): IntegerBinding = subtract(other) as IntegerBinding
|
||||
operator fun IntegerExpression.minus(other: ObservableLongValue): LongBinding = subtract(other) as LongBinding
|
||||
operator fun IntegerExpression.minus(other: ObservableFloatValue): FloatBinding = subtract(other) as FloatBinding
|
||||
operator fun IntegerExpression.minus(other: ObservableDoubleValue): DoubleBinding = subtract(other) as DoubleBinding
|
||||
|
||||
operator fun IntegerProperty.minusAssign(other: Number) { value -= other.toInt() }
|
||||
operator fun IntegerProperty.minusAssign(other: ObservableNumberValue) { value -= other.intValue() }
|
||||
|
||||
operator fun IntegerExpression.unaryMinus(): IntegerBinding = negate()
|
||||
|
||||
operator fun IntegerProperty.dec(): IntegerProperty {
|
||||
value--
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun IntegerExpression.times(other: Int): IntegerBinding = multiply(other)
|
||||
operator fun IntegerExpression.times(other: Long): LongBinding = multiply(other)
|
||||
operator fun IntegerExpression.times(other: Float): FloatBinding = multiply(other)
|
||||
operator fun IntegerExpression.times(other: Double): DoubleBinding = multiply(other)
|
||||
operator fun IntegerExpression.times(other: ObservableIntegerValue): IntegerBinding = multiply(other) as IntegerBinding
|
||||
operator fun IntegerExpression.times(other: ObservableLongValue): LongBinding = multiply(other) as LongBinding
|
||||
operator fun IntegerExpression.times(other: ObservableFloatValue): FloatBinding = multiply(other) as FloatBinding
|
||||
operator fun IntegerExpression.times(other: ObservableDoubleValue): DoubleBinding = multiply(other) as DoubleBinding
|
||||
|
||||
operator fun IntegerProperty.timesAssign(other: Number) { value *= other.toInt() }
|
||||
operator fun IntegerProperty.timesAssign(other: ObservableNumberValue) { value *= other.intValue() }
|
||||
|
||||
operator fun IntegerExpression.div(other: Int): IntegerBinding = divide(other)
|
||||
operator fun IntegerExpression.div(other: Long): LongBinding = divide(other)
|
||||
operator fun IntegerExpression.div(other: Float): FloatBinding = divide(other)
|
||||
operator fun IntegerExpression.div(other: Double): DoubleBinding = divide(other)
|
||||
operator fun IntegerExpression.div(other: ObservableIntegerValue): IntegerBinding = divide(other) as IntegerBinding
|
||||
operator fun IntegerExpression.div(other: ObservableLongValue): LongBinding = divide(other) as LongBinding
|
||||
operator fun IntegerExpression.div(other: ObservableFloatValue): FloatBinding = divide(other) as FloatBinding
|
||||
operator fun IntegerExpression.div(other: ObservableDoubleValue): DoubleBinding = divide(other) as DoubleBinding
|
||||
|
||||
operator fun IntegerProperty.divAssign(other: Number) { value /= other.toInt() }
|
||||
operator fun IntegerProperty.divAssign(other: ObservableNumberValue) { value /= other.intValue() }
|
||||
|
||||
operator fun IntegerExpression.rem(other: Int): IntegerBinding = integerBinding(this) { get() % other }
|
||||
operator fun IntegerExpression.rem(other: Long): LongBinding = longBinding(this) { get() % other }
|
||||
operator fun IntegerExpression.rem(other: Float): FloatBinding = floatBinding(this) { get() % other }
|
||||
operator fun IntegerExpression.rem(other: Double): DoubleBinding = doubleBinding(this) { get() % other }
|
||||
operator fun IntegerExpression.rem(other: ObservableIntegerValue): IntegerBinding = integerBinding(this, other) { get() % other.get() }
|
||||
operator fun IntegerExpression.rem(other: ObservableLongValue): LongBinding = longBinding(this, other) { get() % other.get() }
|
||||
operator fun IntegerExpression.rem(other: ObservableFloatValue): FloatBinding = floatBinding(this, other) { get() % other.get() }
|
||||
operator fun IntegerExpression.rem(other: ObservableDoubleValue): DoubleBinding = doubleBinding(this, other) { get() % other.get() }
|
||||
|
||||
operator fun IntegerProperty.remAssign(other: Number) { value %= other.toInt() }
|
||||
operator fun IntegerProperty.remAssign(other: ObservableNumberValue) { value %= other.intValue() }
|
||||
|
||||
operator fun ObservableIntegerValue.rangeTo(other: ObservableIntegerValue): Sequence<IntegerProperty> {
|
||||
val sequence = mutableListOf<IntegerProperty>()
|
||||
for (i in get()..other.get()) {
|
||||
sequence += SimpleIntegerProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableIntegerValue.rangeTo(other: Int): Sequence<IntegerProperty> {
|
||||
val sequence = mutableListOf<IntegerProperty>()
|
||||
for (i in get()..other) {
|
||||
sequence += SimpleIntegerProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableIntegerValue.rangeTo(other: ObservableLongValue): Sequence<LongProperty> {
|
||||
val sequence = mutableListOf<LongProperty>()
|
||||
for (i in get()..other.get()) {
|
||||
sequence += SimpleLongProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableIntegerValue.rangeTo(other: Long): Sequence<LongProperty> {
|
||||
val sequence = mutableListOf<LongProperty>()
|
||||
for (i in get()..other) {
|
||||
sequence += SimpleLongProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableIntegerValue.compareTo(other: Number): Int {
|
||||
if (get() > other.toDouble())
|
||||
return 1
|
||||
else if (get() < other.toDouble())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
operator fun ObservableIntegerValue.compareTo(other: ObservableNumberValue): Int {
|
||||
if (get() > other.doubleValue())
|
||||
return 1
|
||||
else if (get() < other.doubleValue())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
operator fun LongExpression.plus(other: Number): LongBinding = add(other.toLong())
|
||||
operator fun LongExpression.plus(other: Float): FloatBinding = add(other)
|
||||
operator fun LongExpression.plus(other: Double): DoubleBinding = add(other)
|
||||
operator fun LongExpression.plus(other: ObservableNumberValue): LongBinding = add(other) as LongBinding
|
||||
operator fun LongExpression.plus(other: ObservableFloatValue): FloatBinding = add(other) as FloatBinding
|
||||
operator fun LongExpression.plus(other: ObservableDoubleValue): DoubleBinding = add(other) as DoubleBinding
|
||||
|
||||
operator fun LongProperty.plusAssign(other: Number) { value += other.toLong() }
|
||||
operator fun LongProperty.plusAssign(other: ObservableNumberValue) { value += other.longValue() }
|
||||
|
||||
operator fun LongProperty.inc(): LongProperty {
|
||||
value++
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun LongExpression.minus(other: Number): LongBinding = subtract(other.toLong())
|
||||
operator fun LongExpression.minus(other: Float): FloatBinding = subtract(other)
|
||||
operator fun LongExpression.minus(other: Double): DoubleBinding = subtract(other)
|
||||
operator fun LongExpression.minus(other: ObservableNumberValue): LongBinding = subtract(other) as LongBinding
|
||||
operator fun LongExpression.minus(other: ObservableFloatValue): FloatBinding = subtract(other) as FloatBinding
|
||||
operator fun LongExpression.minus(other: ObservableDoubleValue): DoubleBinding = subtract(other) as DoubleBinding
|
||||
|
||||
operator fun LongProperty.minusAssign(other: Number) { value -= other.toLong() }
|
||||
operator fun LongProperty.minusAssign(other: ObservableNumberValue) { value -= other.longValue() }
|
||||
|
||||
operator fun LongExpression.unaryMinus(): LongBinding = negate()
|
||||
|
||||
operator fun LongProperty.dec(): LongProperty {
|
||||
value--
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun LongExpression.times(other: Number): LongBinding = multiply(other.toLong())
|
||||
operator fun LongExpression.times(other: Float): FloatBinding = multiply(other)
|
||||
operator fun LongExpression.times(other: Double): DoubleBinding = multiply(other)
|
||||
operator fun LongExpression.times(other: ObservableNumberValue): LongBinding = multiply(other) as LongBinding
|
||||
operator fun LongExpression.times(other: ObservableFloatValue): FloatBinding = multiply(other) as FloatBinding
|
||||
operator fun LongExpression.times(other: ObservableDoubleValue): DoubleBinding = multiply(other) as DoubleBinding
|
||||
|
||||
operator fun LongProperty.timesAssign(other: Number) { value *= other.toLong() }
|
||||
operator fun LongProperty.timesAssign(other: ObservableNumberValue) { value *= other.longValue() }
|
||||
|
||||
operator fun LongExpression.div(other: Number): LongBinding = divide(other.toLong())
|
||||
operator fun LongExpression.div(other: Float): FloatBinding = divide(other)
|
||||
operator fun LongExpression.div(other: Double): DoubleBinding = divide(other)
|
||||
operator fun LongExpression.div(other: ObservableNumberValue): LongBinding = divide(other) as LongBinding
|
||||
operator fun LongExpression.div(other: ObservableFloatValue): FloatBinding = divide(other) as FloatBinding
|
||||
operator fun LongExpression.div(other: ObservableDoubleValue): DoubleBinding = divide(other) as DoubleBinding
|
||||
|
||||
operator fun LongProperty.divAssign(other: Number) { value /= other.toLong() }
|
||||
operator fun LongProperty.divAssign(other: ObservableNumberValue) { value /= other.longValue() }
|
||||
|
||||
operator fun LongExpression.rem(other: Number): LongBinding = longBinding(this) { get() % other.toLong() }
|
||||
operator fun LongExpression.rem(other: Float): FloatBinding = floatBinding(this) { get() % other }
|
||||
operator fun LongExpression.rem(other: Double): DoubleBinding = doubleBinding(this) { get() % other }
|
||||
|
||||
operator fun LongExpression.rem(other: ObservableNumberValue): LongBinding = longBinding(this, other) { this.get() % other.longValue() }
|
||||
operator fun LongExpression.rem(other: ObservableFloatValue): FloatBinding = floatBinding(this, other) { this.get() % other.get() }
|
||||
operator fun LongExpression.rem(other: ObservableDoubleValue): DoubleBinding = doubleBinding(this, other) { this.get() % other.get() }
|
||||
|
||||
operator fun LongProperty.remAssign(other: Number) { value %= other.toLong() }
|
||||
operator fun LongProperty.remAssign(other: ObservableNumberValue) { value %= other.longValue() }
|
||||
|
||||
operator fun ObservableLongValue.rangeTo(other: ObservableLongValue): Sequence<LongProperty> {
|
||||
val sequence = mutableListOf<LongProperty>()
|
||||
for (i in get()..other.get()) {
|
||||
sequence += SimpleLongProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableLongValue.rangeTo(other: Long): Sequence<LongProperty> {
|
||||
val sequence = mutableListOf<LongProperty>()
|
||||
for (i in get()..other) {
|
||||
sequence += SimpleLongProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableLongValue.rangeTo(other: ObservableIntegerValue): Sequence<LongProperty> {
|
||||
val sequence = mutableListOf<LongProperty>()
|
||||
for (i in get()..other.get()) {
|
||||
sequence += SimpleLongProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableLongValue.rangeTo(other: Int): Sequence<LongProperty> {
|
||||
val sequence = mutableListOf<LongProperty>()
|
||||
for (i in get()..other) {
|
||||
sequence += SimpleLongProperty(i)
|
||||
}
|
||||
return sequence.asSequence()
|
||||
}
|
||||
|
||||
operator fun ObservableLongValue.compareTo(other: Number): Int {
|
||||
if (get() > other.toDouble())
|
||||
return 1
|
||||
else if (get() < other.toDouble())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
operator fun ObservableLongValue.compareTo(other: ObservableNumberValue): Int {
|
||||
if (get() > other.doubleValue())
|
||||
return 1
|
||||
else if (get() < other.doubleValue())
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
infix fun NumberExpression.gt(other: Int): BooleanBinding = greaterThan(other)
|
||||
infix fun NumberExpression.gt(other: Long): BooleanBinding = greaterThan(other)
|
||||
infix fun NumberExpression.gt(other: Float): BooleanBinding = greaterThan(other)
|
||||
infix fun NumberExpression.gt(other: Double): BooleanBinding = greaterThan(other)
|
||||
infix fun NumberExpression.gt(other: ObservableNumberValue): BooleanBinding = greaterThan(other)
|
||||
|
||||
infix fun NumberExpression.ge(other: Int): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
infix fun NumberExpression.ge(other: Long): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
infix fun NumberExpression.ge(other: Float): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
infix fun NumberExpression.ge(other: Double): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
infix fun NumberExpression.ge(other: ObservableNumberValue): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
|
||||
infix fun NumberExpression.eq(other: Int): BooleanBinding = isEqualTo(other)
|
||||
infix fun NumberExpression.eq(other: Long): BooleanBinding = isEqualTo(other)
|
||||
infix fun NumberExpression.eq(other: ObservableNumberValue): BooleanBinding = isEqualTo(other)
|
||||
|
||||
infix fun NumberExpression.le(other: Int): BooleanBinding = lessThanOrEqualTo(other)
|
||||
infix fun NumberExpression.le(other: Long): BooleanBinding = lessThanOrEqualTo(other)
|
||||
infix fun NumberExpression.le(other: Float): BooleanBinding = lessThanOrEqualTo(other)
|
||||
infix fun NumberExpression.le(other: Double): BooleanBinding = lessThanOrEqualTo(other)
|
||||
infix fun NumberExpression.le(other: ObservableNumberValue): BooleanBinding = lessThanOrEqualTo(other)
|
||||
|
||||
infix fun NumberExpression.lt(other: Int): BooleanBinding = lessThan(other)
|
||||
infix fun NumberExpression.lt(other: Long): BooleanBinding = lessThan(other)
|
||||
infix fun NumberExpression.lt(other: Float): BooleanBinding = lessThan(other)
|
||||
infix fun NumberExpression.lt(other: Double): BooleanBinding = lessThan(other)
|
||||
infix fun NumberExpression.lt(other: ObservableNumberValue): BooleanBinding = lessThan(other)
|
||||
|
||||
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
operator fun BooleanExpression.not(): BooleanBinding = not()
|
||||
|
||||
infix fun BooleanExpression.and(other: Boolean): BooleanBinding = and(SimpleBooleanProperty(other))
|
||||
infix fun BooleanExpression.and(other: ObservableBooleanValue): BooleanBinding = and(other)
|
||||
|
||||
infix fun BooleanExpression.or(other: Boolean): BooleanBinding = or(SimpleBooleanProperty(other))
|
||||
infix fun BooleanExpression.or(other: ObservableBooleanValue): BooleanBinding = or(other)
|
||||
|
||||
infix fun BooleanExpression.xor(other: Boolean): BooleanBinding = booleanBinding(this) { get() xor other }
|
||||
infix fun BooleanExpression.xor(other: ObservableBooleanValue): BooleanBinding = booleanBinding(this, other) { get() xor other.get() }
|
||||
|
||||
infix fun BooleanExpression.eq(other: Boolean): BooleanBinding = isEqualTo(SimpleBooleanProperty(other))
|
||||
infix fun BooleanExpression.eq(other: ObservableBooleanValue): BooleanBinding = isEqualTo(other)
|
||||
|
||||
|
||||
operator fun StringExpression.plus(other: Any): StringExpression = concat(other)
|
||||
operator fun StringProperty.plusAssign(other: Any) { value += other }
|
||||
|
||||
operator fun StringExpression.get(index: Int): Binding<Char?> = objectBinding(this) {
|
||||
if (index < get().length)
|
||||
get()[index]
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
operator fun StringExpression.get(index: ObservableIntegerValue): Binding<Char?> = objectBinding(this, index) {
|
||||
if (index < get().length)
|
||||
get()[index.get()]
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
operator fun StringExpression.get(start: Int, end: Int): StringBinding = stringBinding(this) { get().subSequence(start, end).toString() }
|
||||
operator fun StringExpression.get(start: ObservableIntegerValue, end: Int): StringBinding = stringBinding(this, start) { get().subSequence(start.get(), end).toString() }
|
||||
operator fun StringExpression.get(start: Int, end: ObservableIntegerValue): StringBinding = stringBinding(this, end) { get().subSequence(start, end.get()).toString() }
|
||||
operator fun StringExpression.get(start: ObservableIntegerValue, end: ObservableIntegerValue): StringBinding = stringBinding(this, start, end) { get().subSequence(start.get(), end.get()).toString() }
|
||||
|
||||
operator fun StringExpression.unaryMinus(): StringBinding = stringBinding(this) { get().reversed() }
|
||||
|
||||
operator fun StringExpression.compareTo(other: String): Int = get().compareTo(other)
|
||||
operator fun StringExpression.compareTo(other: ObservableStringValue): Int = get().compareTo(other.get())
|
||||
|
||||
infix fun StringExpression.gt(other: String): BooleanBinding = greaterThan(other)
|
||||
infix fun StringExpression.gt(other: ObservableStringValue): BooleanBinding = greaterThan(other)
|
||||
|
||||
infix fun StringExpression.ge(other: String): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
infix fun StringExpression.ge(other: ObservableStringValue): BooleanBinding = greaterThanOrEqualTo(other)
|
||||
|
||||
infix fun StringExpression.eq(other: String): BooleanBinding = isEqualTo(other)
|
||||
infix fun StringExpression.eq(other: ObservableStringValue): BooleanBinding = isEqualTo(other)
|
||||
|
||||
infix fun StringExpression.le(other: String): BooleanBinding = lessThanOrEqualTo(other)
|
||||
infix fun StringExpression.le(other: ObservableStringValue): BooleanBinding = lessThanOrEqualTo(other)
|
||||
|
||||
infix fun StringExpression.lt(other: String): BooleanBinding = lessThan(other)
|
||||
infix fun StringExpression.lt(other: ObservableStringValue): BooleanBinding = lessThan(other)
|
||||
|
||||
infix fun StringExpression.eqIgnoreCase(other: String): BooleanBinding = isEqualToIgnoreCase(other)
|
||||
infix fun StringExpression.eqIgnoreCase(other: ObservableStringValue): BooleanBinding = isEqualToIgnoreCase(other)
|
||||
|
||||
|
||||
fun <T> ObservableValue<T>.integerBinding(vararg dependencies: Observable, op: (T?) -> Int): IntegerBinding
|
||||
= Bindings.createIntegerBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any> integerBinding(receiver: T, vararg dependencies: Observable, op: T.() -> Int): IntegerBinding
|
||||
= Bindings.createIntegerBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
fun <T> ObservableValue<T>.longBinding(vararg dependencies: Observable, op: (T?) -> Long): LongBinding
|
||||
= Bindings.createLongBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any> longBinding(receiver: T, vararg dependencies: Observable, op: T.() -> Long): LongBinding
|
||||
= Bindings.createLongBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
fun <T> ObservableValue<T>.doubleBinding(vararg dependencies: Observable, op: (T?) -> Double): DoubleBinding
|
||||
= Bindings.createDoubleBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any> doubleBinding(receiver: T, vararg dependencies: Observable, op: T.() -> Double): DoubleBinding
|
||||
= Bindings.createDoubleBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
fun <T> ObservableValue<T>.floatBinding(vararg dependencies: Observable, op: (T?) -> Float): FloatBinding
|
||||
= Bindings.createFloatBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any> floatBinding(receiver: T, vararg dependencies: Observable, op: T.() -> Float): FloatBinding
|
||||
= Bindings.createFloatBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
fun <T> ObservableValue<T>.booleanBinding(vararg dependencies: Observable, op: (T?) -> Boolean): BooleanBinding =
|
||||
Bindings.createBooleanBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any> booleanBinding(receiver: T, vararg dependencies: Observable, op: T.() -> Boolean): BooleanBinding
|
||||
= Bindings.createBooleanBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
/**
|
||||
* A Boolean binding that tracks all items in an observable list and create an observable boolean
|
||||
* value by anding together an observable boolean representing each element in the observable list.
|
||||
* Whenever the list changes, the binding is updated as well
|
||||
*/
|
||||
fun <T : Any> booleanListBinding(list: ObservableList<T>, itemToBooleanExpr: T.() -> BooleanExpression): BooleanExpression {
|
||||
val facade = SimpleBooleanProperty()
|
||||
fun rebind() {
|
||||
if (list.isEmpty()) {
|
||||
facade.unbind()
|
||||
facade.value = false
|
||||
} else {
|
||||
facade.cleanBind(list.map(itemToBooleanExpr).reduce { a, b -> a.and(b) })
|
||||
}
|
||||
}
|
||||
list.onChange { rebind() }
|
||||
rebind()
|
||||
return facade
|
||||
}
|
||||
|
||||
fun <T> ObservableValue<T>.stringBinding(vararg dependencies: Observable, op: (T?) -> String?): StringBinding
|
||||
= Bindings.createStringBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any> stringBinding(receiver: T, vararg dependencies: Observable, op: T.() -> String?): StringBinding =
|
||||
Bindings.createStringBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
fun <T, R> ObservableValue<T>.objectBinding(vararg dependencies: Observable, op: (T?) -> R?): Binding<R?>
|
||||
= Bindings.createObjectBinding(Callable { op(value) }, this, *dependencies)
|
||||
|
||||
fun <T : Any, R> objectBinding(receiver: T, vararg dependencies: Observable, op: T.() -> R?): ObjectBinding<R?>
|
||||
= Bindings.createObjectBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
fun <T : Any, R> nonNullObjectBinding(receiver: T, vararg dependencies: Observable, op: T.() -> R): ObjectBinding<R>
|
||||
= Bindings.createObjectBinding(Callable { receiver.op() }, *createObservableArray(receiver, *dependencies))
|
||||
|
||||
private fun <T> createObservableArray(receiver: T, vararg dependencies: Observable): Array<out Observable> =
|
||||
if (receiver is Observable) arrayOf(receiver, *dependencies) else dependencies
|
||||
|
||||
/**
|
||||
* Assign the value from the creator to this WritableValue if and only if it is currently null
|
||||
*/
|
||||
fun <T> WritableValue<T>.assignIfNull(creator: () -> T) {
|
||||
if (value == null) value = creator()
|
||||
}
|
||||
|
||||
fun Double.toProperty(): DoubleProperty = SimpleDoubleProperty(this)
|
||||
fun Float.toProperty(): FloatProperty = SimpleFloatProperty(this)
|
||||
fun Long.toProperty(): LongProperty = SimpleLongProperty(this)
|
||||
fun Int.toProperty(): IntegerProperty = SimpleIntegerProperty(this)
|
||||
fun Boolean.toProperty(): BooleanProperty = SimpleBooleanProperty(this)
|
||||
fun String.toProperty(): StringProperty = SimpleStringProperty(this)
|
||||
|
||||
fun String?.toProperty() = SimpleStringProperty(this ?: "")
|
||||
fun Double?.toProperty() = SimpleDoubleProperty(this ?: 0.0)
|
||||
fun Float?.toProperty() = SimpleFloatProperty(this ?: 0.0F)
|
||||
fun Long?.toProperty() = SimpleLongProperty(this ?: 0L)
|
||||
fun Boolean?.toProperty() = SimpleBooleanProperty(this ?: false)
|
||||
fun <T : Any> T?.toProperty() = SimpleObjectProperty<T>(this)
|
||||
|
||||
class WeakReferenceDelegate<T>(val creator: () -> T) {
|
||||
var weakReference : WeakReference<T>? = null
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
var ret = weakReference?.get()
|
||||
if (ret == null) {
|
||||
ret = creator()
|
||||
weakReference = WeakReference(ret)
|
||||
}
|
||||
return ret!!
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -183,9 +183,9 @@ public abstract class Task {
|
||||
return new TaskExecutor(this);
|
||||
}
|
||||
|
||||
public final TaskExecutor executor(TaskListener taskListener) {
|
||||
public final TaskExecutor executor(Function<TaskExecutor, TaskListener> taskListener) {
|
||||
TaskExecutor executor = new TaskExecutor(this);
|
||||
executor.setTaskListener(taskListener);
|
||||
executor.setTaskListener(taskListener.apply(executor));
|
||||
return executor;
|
||||
}
|
||||
|
||||
@@ -204,13 +204,21 @@ public abstract class Task {
|
||||
}
|
||||
|
||||
public final TaskExecutor subscribe(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return subscribe(of(closure, scheduler));
|
||||
return subscribe(of(scheduler, closure));
|
||||
}
|
||||
|
||||
public final TaskExecutor subscribe(Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return subscribe(of(scheduler, i -> closure.run()));
|
||||
}
|
||||
|
||||
public final TaskExecutor subscribe(ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return subscribe(of(closure));
|
||||
}
|
||||
|
||||
public final TaskExecutor subscribe(ExceptionalRunnable<?> closure) {
|
||||
return subscribe(of(closure));
|
||||
}
|
||||
|
||||
public final Task then(Task b) {
|
||||
return then(s -> b);
|
||||
}
|
||||
@@ -237,13 +245,17 @@ public abstract class Task {
|
||||
}
|
||||
|
||||
public static Task of(ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return of(closure, Schedulers.defaultScheduler());
|
||||
return of(Schedulers.defaultScheduler(), closure);
|
||||
}
|
||||
|
||||
public static Task of(ExceptionalConsumer<AutoTypingMap<String>, ?> closure, Scheduler scheduler) {
|
||||
public static Task of(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return new SimpleTask(closure, scheduler);
|
||||
}
|
||||
|
||||
public static Task of(Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return new SimpleTask(i -> closure.run(), scheduler);
|
||||
}
|
||||
|
||||
public static <V> TaskResult<V> ofResult(String id, Callable<V> callable) {
|
||||
return new TaskCallable<>(id, callable);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -38,6 +39,10 @@ public final class AutoTypingMap<K> {
|
||||
return (V) impl.get(key);
|
||||
}
|
||||
|
||||
public <V> Optional<V> getOptional(K key) {
|
||||
return Optional.ofNullable(get(key));
|
||||
}
|
||||
|
||||
public void set(K key, Object value) {
|
||||
if (value != null)
|
||||
impl.put(key, value);
|
||||
|
||||
@@ -71,12 +71,12 @@ public final class Lang {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, R, E extends Exception> Function<T, R> hideException(ExceptionalFunction<T, R, E> function) {
|
||||
public static <T, R, E extends Exception> Function<T, R> hideFunction(ExceptionalFunction<T, R, E> function) {
|
||||
return r -> invoke(function, r);
|
||||
}
|
||||
|
||||
public static <T, R, E extends Exception> Function<T, R> liftException(ExceptionalFunction<T, R, E> function) throws E {
|
||||
return hideException(function);
|
||||
public static <T, R, E extends Exception> Function<T, R> liftFunction(ExceptionalFunction<T, R, E> function) throws E {
|
||||
return hideFunction(function);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +107,33 @@ public final class Lang {
|
||||
return hideException(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call a method without checked exceptions
|
||||
* by treating the compiler.
|
||||
*
|
||||
* If this method throws a checked exception,
|
||||
* it will still abort the application because of the exception.
|
||||
*
|
||||
* @param <T> type of result.
|
||||
* @param consumer your method.
|
||||
* @return the result of the method to invoke.
|
||||
*/
|
||||
public static <T, E extends Exception> void invokeConsumer(ExceptionalConsumer<T, E> consumer, T t) {
|
||||
try {
|
||||
consumer.accept(t);
|
||||
} catch (Exception e) {
|
||||
throwable(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, E extends Exception> Consumer<T> hideConsumer(ExceptionalConsumer<T, E> consumer) {
|
||||
return it -> invokeConsumer(consumer, it);
|
||||
}
|
||||
|
||||
public static <T, E extends Exception> Consumer<T> liftConsumer(ExceptionalConsumer<T, E> consumer) throws E {
|
||||
return hideConsumer(consumer);
|
||||
}
|
||||
|
||||
public static <E extends Exception> boolean test(ExceptionalSupplier<Boolean, E> r) {
|
||||
try {
|
||||
return r.get();
|
||||
@@ -248,17 +275,25 @@ public final class Lang {
|
||||
return () -> asIterator(enumeration);
|
||||
}
|
||||
|
||||
public static int parseInt(String string, int defaultValue) {
|
||||
public static int parseInt(Object string, int defaultValue) {
|
||||
try {
|
||||
return Integer.parseInt(string);
|
||||
return Integer.parseInt(string.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer toIntOrNull(String string) {
|
||||
public static Integer toIntOrNull(Object string) {
|
||||
try {
|
||||
return Integer.parseInt(string);
|
||||
return Integer.parseInt(string.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Double toDoubleOrNull(Object string) {
|
||||
try {
|
||||
return Double.parseDouble(string.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -268,4 +303,9 @@ public final class Lang {
|
||||
for (T a : t) if (a != null) return a;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T apply(T t, Consumer<T> consumer) {
|
||||
consumer.accept(t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,152 +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
|
||||
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher
|
||||
import org.jackhuang.hmcl.game.LaunchOptions
|
||||
import org.jackhuang.hmcl.game.minecraftVersion
|
||||
import org.jackhuang.hmcl.launch.ProcessListener
|
||||
import org.jackhuang.hmcl.util.makeCommand
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskListener
|
||||
import org.jackhuang.hmcl.util.Log4jLevel
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
|
||||
class Test {
|
||||
val ss = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", 1080))
|
||||
val repository = DefaultGameRepository(File(".minecraft").absoluteFile)
|
||||
val dependency = DefaultDependencyManager(
|
||||
repository = repository,
|
||||
downloadProvider = MojangDownloadProvider,
|
||||
proxy = ss)
|
||||
|
||||
init {
|
||||
repository.refreshVersions()
|
||||
}
|
||||
|
||||
fun launch() {
|
||||
val launcher = DefaultLauncher(
|
||||
repository = repository,
|
||||
versionId = "test",
|
||||
account = OfflineAccount.fromUsername("player007").logIn(),
|
||||
options = LaunchOptions(gameDir = repository.baseDirectory),
|
||||
listener = object : ProcessListener {
|
||||
override fun onLog(log: String, level: Log4jLevel) {
|
||||
println(log)
|
||||
}
|
||||
|
||||
override fun onExit(exitCode: Int, exitType: ProcessListener.ExitType) {
|
||||
println("Process exited then exit code $exitCode")
|
||||
}
|
||||
|
||||
},
|
||||
isDaemon = false
|
||||
)
|
||||
println(makeCommand(launcher.rawCommandLine))
|
||||
launcher.launch()
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
} catch (e: InterruptedException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadNewVersion() {
|
||||
val thread = Thread.currentThread()
|
||||
dependency.gameBuilder()
|
||||
.name("test")
|
||||
.gameVersion("1.12")
|
||||
.version("forge", "14.21.1.2426")
|
||||
.version("liteloader", "1.12-SNAPSHOT-4")
|
||||
.version("optifine", "HD_U_C4")
|
||||
.buildAsync().executor().apply {
|
||||
taskListener = taskListener(thread)
|
||||
}.start()
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
} catch (e: InterruptedException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun completeGame() {
|
||||
val thread = Thread.currentThread()
|
||||
val version = repository.getVersion("test").resolve(repository)
|
||||
dependency.checkGameCompletionAsync(version).executor().apply {
|
||||
taskListener = taskListener(thread)
|
||||
}.start()
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
} catch (e: InterruptedException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun installForge() {
|
||||
val thread = Thread.currentThread()
|
||||
val version = repository.getVersion("test").resolve(repository)
|
||||
val minecraftVersion = minecraftVersion(repository.getVersionJar(version)) ?: ""
|
||||
// optifine HD_U_C4
|
||||
// forge 14.21.1.2426
|
||||
// liteloader 1.12-SNAPSHOT-4
|
||||
dependency.installLibraryAsync(minecraftVersion, version, "liteloader", "1.12-SNAPSHOT-4").executor().apply {
|
||||
taskListener = taskListener(thread)
|
||||
}.start()
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
} catch (e: InterruptedException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshAsync() {
|
||||
val thread = Thread.currentThread()
|
||||
LiteLoaderVersionList.refreshAsync(BMCLAPIDownloadProvider).executor().apply {
|
||||
taskListener = taskListener(thread)
|
||||
}.start()
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
} catch (e: InterruptedException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun taskListener(thread: Thread) = object : TaskListener {
|
||||
override fun onReady(task: Task) {
|
||||
}
|
||||
|
||||
override fun onFinished(task: Task) {
|
||||
}
|
||||
|
||||
override fun onFailed(task: Task, throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
thread.interrupt()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
108
README.md
108
README.md
@@ -7,50 +7,76 @@ HMCL is a Minecraft launcher which supports Mod management, game customizing, au
|
||||
|
||||
## Contribution
|
||||
If you want to submit a pull request, there're some requirements:
|
||||
* IDE: Netbeans 8.1
|
||||
* Compiler: Java 1.8 and libraries only supports Java 1.7(because of retrolambda).
|
||||
* IDE: Intellij IDEA.
|
||||
* Compiler: Java 1.8.
|
||||
* Do NOT modify `gradle` files.
|
||||
|
||||
## Code
|
||||
* package `HMCLCore/org.jackhuang.hmcl.util`: HMCL development utilities.
|
||||
* package `HMCL/org.jackhuang.hmcl`: HMCL UI core.
|
||||
* package `HMCLCore/org.jackhuang.hmcl.core`: HMCL game launcher core.
|
||||
* package `HMCLAPI(HMCL)/org.jackhuang.hmcl.api`: HMCL API, see API section.
|
||||
* Folder `HMCLCore/src/main/resources/org/jackhuang/hmcl/lang` contains language files.
|
||||
## HMCLCore
|
||||
Now HMCLCore is independent and you can use HMCLCore as a library to launch your game.
|
||||
|
||||
## Pay Attention
|
||||
* When you do decide to modify this app, please and you MUST delete `HMCL/org.jackhuang.hmcl.util.CrashReporter`, or errors your code cause will be sent to my server.
|
||||
* package `org.jackhuang.hmcl.util.logging`: repackaged Apache Log4j, Apache License 2.0.
|
||||
* package `com.google.gson`: Apache License 2.0
|
||||
* package `org.jackhuang.hmcl.laf.ui`: contains some NimbusLAF's code belonging to Sun Microsystems under LGPL.
|
||||
### GameRepository
|
||||
Create a game repository `repository` to manage a minecraft installation. Like this.
|
||||
```java
|
||||
DefaultGameRepository repository = new DefaultGameRepository(new File(".minecraft").getAbsoluteFile());
|
||||
```
|
||||
|
||||
## API
|
||||
HMCLAPI is based on Event bus. There are all events below.
|
||||
* org.jackhuang.hmcl.api.event
|
||||
- OutOfDateEvent - you can cancel checking new versions and upgrading by this event.
|
||||
* org.jackhuang.hmcl.api.event.config
|
||||
- AuthenticatorChangedEvent
|
||||
- DownloadTypeChangedEvent
|
||||
- ThemeChangedEvent
|
||||
* org.jackhuang.hmcl.api.event.launch
|
||||
- LaunchEvent
|
||||
- LaunchSucceededEvent
|
||||
- LaunchingStateChangedEvent
|
||||
- ProcessingLaunchOptionsEvent
|
||||
- ProcessingLoginResultEvent
|
||||
* org.jackhuang.hmcl.api.event.process
|
||||
- JVMLaunchFailedEvent
|
||||
- JavaProcessExitedAbnormallyEvent
|
||||
- JavaProcessStartingEvent
|
||||
- JavaProcessStoppedEvent
|
||||
* org.jackhuang.hmcl.api.event.version
|
||||
- LoadedOneVersionEvent
|
||||
- RefreshedVersionsEvent
|
||||
- RefreshingVersionsEvent
|
||||
You should put where your minecraft installation is to the only argument of the constructor of `DefaultGameRepository`.
|
||||
|
||||
You can also add tabs to root window or add authenticators through IPlugin.
|
||||
### Launching
|
||||
Now you can launch game by constructing a `DefaultLauncher`.
|
||||
```java
|
||||
DefaultLauncher launcher = new DefaultLauncher(
|
||||
repository, // GameRepository
|
||||
"test", // Your minecraft version name
|
||||
OfflineAccountFactory.INSTANCE.fromUsername("player007").logIn(MultiCharacterSelector.DEFAULT), // account
|
||||
// or YggdrasilAccountFactory.INSTANCE.fromUsername(username, password).logIn
|
||||
new LaunchOptions.Builder()
|
||||
.setGameDir(repository.getBaseDirectory())
|
||||
.setMaxMemory(...)
|
||||
.setJava(...)
|
||||
.setJavaArgs(...)
|
||||
.setMinecraftArgs(...)
|
||||
.setHeight(...)
|
||||
.setWidth(...)
|
||||
...
|
||||
.create(),
|
||||
new ProcessListener() { // listening the process state.
|
||||
@Override
|
||||
public void onLog(String log, Log4jLevel level) { // new console log
|
||||
System.out.println(log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExit(int exitCode, ExitType exitType) { // process exited
|
||||
System.out.println("Process exited then exit code " + exitCode);
|
||||
}
|
||||
},
|
||||
false // true if launcher process exits, listening thread exit too.
|
||||
);
|
||||
```
|
||||
Now you can simply call `launcher.launch()` to launch the game.
|
||||
If you want the command line, just call `launcher.getRawCommandLine`. Also, `StringUtils.makeCommand` might be useful.
|
||||
|
||||
### Remember
|
||||
* A valid plugin will have a main class that implements `org.jackhuang.hmcl.api.IPlugin`. HMCL will search all jar files in `plugins` folder and load classes that implements IPlugin.
|
||||
* If you want to debug, use option: `--plugin=<Your IPlugin Class Name>` and add your jar to classpath.
|
||||
* You'd better only access `org.jackhuang.hmcl.api.*`, and other classes may change in different versions.
|
||||
### Downloading
|
||||
HMCLCore just owns a simple way to download a new game.
|
||||
```java
|
||||
DefaultDependencyManager dependency = new DefaultDependencyManager(repository, MojangDownloadProvider.INSTANCE, proxy);
|
||||
```
|
||||
`repository` is your `GameRepository`. `MojangDownloadProvider.INSTANCE` means that we download files from mojang servers. If you want BMCLAPI, `BMCLAPIDownloadProvider.INSTANCE` is just for you. `proxy` is `java.net.Proxy`, if you have a proxy, put it here, or `Proxy.NO_PROXY`.
|
||||
|
||||
Now `GameBuilder` can build a game.
|
||||
```
|
||||
Task gameBuildingTask = dependency.gameBuilder()
|
||||
.name("test")
|
||||
.gameVersion("1.12") // Minecraft version
|
||||
.version("forge", "14.21.1.2426") // Forge version
|
||||
.version("liteloader", "1.12-SNAPSHOT-4") // LiteLoader version
|
||||
.version("optifine", "HD_U_C4") // OptiFine version
|
||||
.buildAsync()
|
||||
```
|
||||
|
||||
Nowadays HMCLCore only supports Forge, LiteLoader and OptiFine auto-installing.
|
||||
`buildAsync` will return a `Task`, you can call `Task.executor()::start` or simply `Task::start` to start this task. If you want to monitor the execution of tasks, you should see `TaskExecutor` and `Task::executor`.
|
||||
|
||||
## HMCL
|
||||
JavaFX version of HMCL does not support old APIs.
|
||||
10
build.gradle
10
build.gradle
@@ -26,7 +26,6 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'net.sf.proguard:proguard-gradle:5.3.3'
|
||||
}
|
||||
}
|
||||
@@ -50,16 +49,7 @@ allprojects {
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
compile "com.google.code.gson:gson:2.8.1"
|
||||
compile "org.apache.commons:commons-compress:1.8.1"
|
||||
compile "org.tukaani:xz:1.6"
|
||||
|
||||
Reference in New Issue
Block a user