Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
129f6bb5cc | ||
|
|
545c17d052 | ||
|
|
add00ddba1 | ||
|
|
d9aeb0cc2b | ||
|
|
49e70e7195 | ||
|
|
f8abebf110 | ||
|
|
8e2dfc7341 | ||
|
|
e650ffffce | ||
|
|
d549a300ab | ||
|
|
82a62e73c1 | ||
|
|
45c7e0c231 | ||
|
|
b98a4b11a3 | ||
|
|
484a103f27 | ||
|
|
e31120be21 | ||
|
|
247f02d423 | ||
|
|
3da0c1d8e3 | ||
|
|
c830bbc703 | ||
|
|
7b619fef1d | ||
|
|
bac265a077 | ||
|
|
39df0f8d43 | ||
|
|
6af7f916ee | ||
|
|
ae64097edf | ||
|
|
cb6d8a88f1 | ||
|
|
12838994ad | ||
|
|
d30758a2a8 | ||
|
|
b820aad358 | ||
|
|
170189c344 | ||
|
|
f9e9c9d38b | ||
|
|
088af981df | ||
|
|
357fef8672 | ||
|
|
c38848ea0e | ||
|
|
be8cd13b08 | ||
|
|
487816ab74 | ||
|
|
bcd9f8240c | ||
|
|
1b62972f06 | ||
|
|
1117c3bfab | ||
|
|
748d195db5 | ||
|
|
3a9017adb8 | ||
|
|
eff3186ab5 | ||
|
|
e5f5fe878e | ||
|
|
a47f0f6ecf | ||
|
|
9896165b68 | ||
|
|
5e3adcee93 | ||
|
|
b252266a46 | ||
|
|
1d5adea114 | ||
|
|
a64a51c7df | ||
|
|
ec41c13b45 | ||
|
|
486ca434ff | ||
|
|
fe87a899c8 | ||
|
|
06806bd383 | ||
|
|
2f6c7e1ce2 | ||
|
|
141cda13f1 | ||
|
|
3e8c8afcd1 | ||
|
|
8236f613e0 | ||
|
|
ea6108dca7 | ||
|
|
985a4f07e0 | ||
|
|
5642c579a4 | ||
|
|
8e0559fb3b | ||
|
|
a6a1755fee | ||
|
|
846f383f7b | ||
|
|
bbbe3726f2 | ||
|
|
6d83fd9903 | ||
|
|
fac62a8d24 | ||
|
|
b0d71a5511 | ||
|
|
124ba34d48 | ||
|
|
75270b8ad6 | ||
|
|
5cf6ef88a6 | ||
|
|
b5f72af513 | ||
|
|
1532893c5f | ||
|
|
a3fbc9595a | ||
|
|
f789343b6e | ||
|
|
ff74417352 | ||
|
|
a0ff843d0c | ||
|
|
da93cb0dbb | ||
|
|
0de446c608 | ||
|
|
03bccf16d4 | ||
|
|
635ae8c11e | ||
|
|
dfd5a82112 | ||
|
|
7dad8d452f | ||
|
|
07f8294876 | ||
|
|
7185cc410b | ||
|
|
2d985af60d | ||
|
|
bee2e64482 | ||
|
|
d5dd43c0c1 | ||
|
|
09470eeb8a | ||
|
|
094249a6fa | ||
|
|
6b5302907a | ||
|
|
adc5b08765 | ||
|
|
0150e2a8d7 | ||
|
|
45eaa0c60f | ||
|
|
66ed683196 | ||
|
|
aa4239721e | ||
|
|
5b0b926cbd | ||
|
|
929ce398eb | ||
|
|
b3fdbe021d | ||
|
|
423bd68ce8 | ||
|
|
9bffc68c70 | ||
|
|
b7a5a60091 | ||
|
|
333b11c19f | ||
|
|
fbd4d1b772 | ||
|
|
06a58514b3 | ||
|
|
80815fc3a0 | ||
|
|
b97c277a3f | ||
|
|
310d693e2b | ||
|
|
c1596f6a11 | ||
|
|
76a04343d9 | ||
|
|
25261c99f1 | ||
|
|
d0b994fa9d | ||
|
|
f259601397 | ||
|
|
87904109ca | ||
|
|
785790ccc4 | ||
|
|
759921d00e | ||
|
|
e9db183d54 |
No files matched your search
Binary file not shown.
+25
-67
@@ -1,5 +1,4 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import proguard.gradle.ProGuardTask
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.util.jar.JarFile
|
||||
@@ -54,11 +53,6 @@ jar {
|
||||
}
|
||||
|
||||
doLast {
|
||||
//new File("build/signed").mkdirs()
|
||||
//ant.signjar(signedjar: archivePath, jar: archivePath,
|
||||
// keystore: "HMCL.keystore", storepass: "123456",
|
||||
// alias: "HMCL")
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
archivePath.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead)
|
||||
@@ -76,6 +70,29 @@ processResources {
|
||||
}
|
||||
}
|
||||
|
||||
task makePackGZ(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
makeExecutableinjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
makeExecutableoutjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
}
|
||||
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz")
|
||||
def os = new GZIPOutputStream(new FileOutputStream(loc))
|
||||
Pack200.newPacker().pack new JarFile(makeExecutableinjar), os
|
||||
os.close()
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
loc.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead)
|
||||
}
|
||||
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
|
||||
def fileEx = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz.sha1")
|
||||
if (!fileEx.exists()) fileEx.createNewFile()
|
||||
fileEx.append sha1Hex
|
||||
}
|
||||
|
||||
task makeExecutable(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
@@ -100,64 +117,5 @@ task makeExecutable(dependsOn: jar) doLast {
|
||||
|
||||
}
|
||||
|
||||
task makePackGZ(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
makeExecutableinjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
makeExecutableoutjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
}
|
||||
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz")
|
||||
def os = new GZIPOutputStream(new FileOutputStream(loc))
|
||||
Pack200.newPacker().pack new JarFile(makeExecutableinjar), os
|
||||
os.close()
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
loc.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead)
|
||||
}
|
||||
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
|
||||
def fileEx = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz.sha1")
|
||||
if (!fileEx.exists()) fileEx.createNewFile()
|
||||
fileEx.append sha1Hex
|
||||
}
|
||||
|
||||
task proguard(type: ProGuardTask, dependsOn: jar) {
|
||||
ext {
|
||||
def re = jar.classifier
|
||||
injar = jar.archivePath
|
||||
jar.classifier = 'optimized'
|
||||
outjar = jar.archivePath
|
||||
jar.classifier = re
|
||||
}
|
||||
|
||||
injars(/*["filter": "!META-INF/**,**"], */injar)
|
||||
String javaHome = System.getProperty("java.home")
|
||||
if (!javaHome.endsWith("jre"))
|
||||
javaHome += File.separator + "jre"
|
||||
libraryjars javaHome + "/lib/rt.jar"
|
||||
libraryjars javaHome + "/lib/jce.jar"
|
||||
libraryjars javaHome + "/lib/jsse.jar"
|
||||
libraryjars javaHome + "/lib/ext/jfxrt.jar"
|
||||
outjars outjar
|
||||
|
||||
configuration 'proguard.pro'
|
||||
}
|
||||
|
||||
task combineMETA_INF(type: Jar) {
|
||||
ext {
|
||||
def re = jar.classifier
|
||||
injar = jar.archivePath
|
||||
jar.classifier = 'optimized'
|
||||
outjar = jar.archivePath
|
||||
jar.classifier = re
|
||||
}
|
||||
from zipTree(outjar)
|
||||
from zipTree(injar).matching {
|
||||
include "META-INF/"
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn makeExecutable
|
||||
build.dependsOn makePackGZ
|
||||
build.dependsOn makePackGZ
|
||||
build.dependsOn makeExecutable
|
||||
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
-target 1.8
|
||||
-dontoptimize
|
||||
-dontobfuscate
|
||||
|
||||
# JFoenix
|
||||
-keep class com.jfoenix.** {
|
||||
<fields>;
|
||||
<methods>;
|
||||
}
|
||||
|
||||
# HMCL
|
||||
-keep class org.jackhuang.** {
|
||||
<fields>;
|
||||
<methods>;
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
@@ -34,6 +36,7 @@ import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -79,11 +82,13 @@ public final class Launcher extends Application {
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
|
||||
|
||||
Logging.LOG.info("*** " + TITLE + " ***");
|
||||
Logging.LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
Logging.LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
Logging.LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
Logging.LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("*** " + TITLE + " ***");
|
||||
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("Current Directory: " + Paths.get("").toAbsolutePath());
|
||||
LOG.info("HMCL Directory: " + HMCL_DIRECTORY);
|
||||
|
||||
launch(args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
@@ -92,7 +97,7 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
public static void stopApplication() {
|
||||
Logging.LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
@@ -106,7 +111,7 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
public static void stopWithoutPlatform() {
|
||||
Logging.LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
|
||||
@@ -21,6 +21,8 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -32,12 +34,17 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
checkDSTRootCAX3();
|
||||
checkConfigPermission();
|
||||
|
||||
ConfigHolder.init();
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
@@ -80,6 +87,22 @@ public final class Main {
|
||||
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
|
||||
}
|
||||
|
||||
private static void checkConfigPermission() {
|
||||
Path config = ConfigHolder.CONFIG_PATH;
|
||||
if (Files.exists(config)) {
|
||||
if (Files.isReadable(config) && Files.isWritable(config)) {
|
||||
// we are able to read & write the existent config
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Files.isWritable(config.getParent())) {
|
||||
// we are able to create a new config
|
||||
return;
|
||||
}
|
||||
}
|
||||
showErrorAndExit(i18n("fatal.config_access_denied", config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* This event gets fired when added accounts.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class AccountAddedEvent extends Event {
|
||||
|
||||
private final Account account;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public AccountAddedEvent(Object source, Account account) {
|
||||
super(source);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("account", account)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This event gets fired when loading accounts.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class AccountLoadingEvent extends Event {
|
||||
|
||||
private final Collection<Account> accounts;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public AccountLoadingEvent(Object source, Collection<Account> accounts) {
|
||||
super(source);
|
||||
this.accounts = Collections.unmodifiableCollection(accounts);
|
||||
}
|
||||
|
||||
|
||||
public Collection<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("accounts", accounts)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Scheduler;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -33,7 +33,6 @@ import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.*;
|
||||
|
||||
public final class AccountHelper {
|
||||
@@ -43,31 +42,19 @@ public final class AccountHelper {
|
||||
public static final File SKIN_DIR = new File(Launcher.HMCL_DIRECTORY, "skins");
|
||||
|
||||
public static void loadSkins() {
|
||||
loadSkins(Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public static void loadSkins(Proxy proxy) {
|
||||
for (Account account : Settings.INSTANCE.getAccounts()) {
|
||||
for (Account account : Accounts.getAccounts()) {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
new SkinLoadTask((YggdrasilAccount) account, proxy, false).start();
|
||||
new SkinLoadTask((YggdrasilAccount) account, false).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Task loadSkinAsync(YggdrasilAccount account) {
|
||||
return loadSkinAsync(account, Settings.INSTANCE.getProxy());
|
||||
}
|
||||
|
||||
public static Task loadSkinAsync(YggdrasilAccount account, Proxy proxy) {
|
||||
return new SkinLoadTask(account, proxy, false);
|
||||
return new SkinLoadTask(account, false);
|
||||
}
|
||||
|
||||
public static Task refreshSkinAsync(YggdrasilAccount account) {
|
||||
return refreshSkinAsync(account, Settings.INSTANCE.getProxy());
|
||||
}
|
||||
|
||||
public static Task refreshSkinAsync(YggdrasilAccount account, Proxy proxy) {
|
||||
return new SkinLoadTask(account, proxy, true);
|
||||
return new SkinLoadTask(account, true);
|
||||
}
|
||||
|
||||
private static File getSkinFile(UUID uuid) {
|
||||
@@ -94,9 +81,9 @@ public final class AccountHelper {
|
||||
return getDefaultSkin(uuid, scaleRatio);
|
||||
}
|
||||
|
||||
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio, Proxy proxy) throws Exception {
|
||||
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio) throws Exception {
|
||||
File file = getSkinFile(profile.getId());
|
||||
downloadSkin(account, profile, true, proxy);
|
||||
downloadSkin(account, profile, true);
|
||||
if (!file.exists())
|
||||
return getDefaultSkin(profile.getId(), scaleRatio);
|
||||
|
||||
@@ -111,17 +98,15 @@ public final class AccountHelper {
|
||||
|
||||
private static class SkinLoadTask extends Task {
|
||||
private final YggdrasilAccount account;
|
||||
private final Proxy proxy;
|
||||
private final boolean refresh;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
public SkinLoadTask(YggdrasilAccount account, Proxy proxy) {
|
||||
this(account, proxy, false);
|
||||
public SkinLoadTask(YggdrasilAccount account) {
|
||||
this(account, false);
|
||||
}
|
||||
|
||||
public SkinLoadTask(YggdrasilAccount account, Proxy proxy, boolean refresh) {
|
||||
public SkinLoadTask(YggdrasilAccount account, boolean refresh) {
|
||||
this.account = account;
|
||||
this.proxy = proxy;
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
@@ -140,11 +125,11 @@ public final class AccountHelper {
|
||||
if (!account.isLoggedIn() && (account.getCharacter() == null || refresh))
|
||||
DialogController.logIn(account);
|
||||
|
||||
downloadSkin(account, refresh, proxy);
|
||||
downloadSkin(account, refresh);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh, Proxy proxy) throws Exception {
|
||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh) throws Exception {
|
||||
account.clearCache();
|
||||
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
@@ -153,10 +138,10 @@ public final class AccountHelper {
|
||||
File file = getSkinFile(profile.getId());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
private static void downloadSkin(YggdrasilAccount account, boolean refresh, Proxy proxy) throws Exception {
|
||||
private static void downloadSkin(YggdrasilAccount account, boolean refresh) throws Exception {
|
||||
account.clearCache();
|
||||
|
||||
if (account.getCharacter() == null) return;
|
||||
@@ -166,7 +151,7 @@ public final class AccountHelper {
|
||||
File file = getSkinFile(account.getUUID());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
public static Image scale(String url, double scaleRatio) {
|
||||
|
||||
@@ -22,8 +22,6 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -31,11 +29,7 @@ public class HMCLDependencyManager extends DefaultDependencyManager {
|
||||
private final Profile profile;
|
||||
|
||||
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider) {
|
||||
this(profile, downloadProvider, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider, Proxy proxy) {
|
||||
super(profile.getRepository(), downloadProvider, proxy);
|
||||
super(profile.getRepository(), downloadProvider);
|
||||
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -58,7 +61,11 @@ public class HMCLGameDownloadTask extends Task {
|
||||
public void execute() {
|
||||
File jar = profile.getRepository().getVersionJar(version);
|
||||
|
||||
File cache = new File(Settings.INSTANCE.getCommonPath(), "jars/" + gameVersion + ".jar");
|
||||
// Force using common directory will not affect the behaviour that repository acts
|
||||
// Since we always copy the downloaded jar to .minecraft/versions/<version>/
|
||||
File cache = new File(Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(Settings.getDefaultCommonDirectory()),
|
||||
"jars/" + gameVersion + ".jar");
|
||||
if (cache.exists())
|
||||
try {
|
||||
FileUtils.copyFile(cache, jar);
|
||||
@@ -70,7 +77,6 @@ public class HMCLGameDownloadTask extends Task {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
cache,
|
||||
profile.getDependency().getProxy(),
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.util.CommandBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -42,10 +44,10 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendJvmArgs(List<String> result) {
|
||||
super.appendJvmArgs(result);
|
||||
|
||||
result.add("-Dminecraft.launcher.version=" + Launcher.VERSION);
|
||||
result.add("-Dminecraft.launcher.brand=" + Launcher.NAME);
|
||||
protected Map<String, String> getConfigurations() {
|
||||
Map<String, String> res = super.getConfigurations();
|
||||
res.put("${launcher_name}", Launcher.NAME);
|
||||
res.put("${launcher_version}", Launcher.VERSION);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
||||
@@ -50,17 +52,16 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return profile;
|
||||
}
|
||||
|
||||
private boolean useSelf(String version, String assetId) {
|
||||
VersionSetting vs = profile.getVersionSetting(version);
|
||||
return new File(getBaseDirectory(), "assets/indexes/" + assetId + ".json").exists() || vs.isNoCommon();
|
||||
private boolean useSelf(String assetId) {
|
||||
return new File(getBaseDirectory(), "assets/indexes/" + assetId + ".json").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAssetDirectory(String version, String assetId) {
|
||||
if (useSelf(version, assetId))
|
||||
if (Settings.instance().isCommonDirectoryDisabled() || useSelf(assetId))
|
||||
return super.getAssetDirectory(version, assetId);
|
||||
else
|
||||
return new File(Settings.INSTANCE.getCommonPath(), "assets");
|
||||
return new File(Settings.instance().getCommonDirectory(), "assets");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,10 +83,10 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public File getLibraryFile(Version version, Library lib) {
|
||||
VersionSetting vs = profile.getVersionSetting(version.getId());
|
||||
File self = super.getLibraryFile(version, lib);
|
||||
if (self.exists() || vs.isNoCommon())
|
||||
if (Settings.instance().isCommonDirectoryDisabled() || self.exists())
|
||||
return self;
|
||||
else
|
||||
return new File(Settings.INSTANCE.getCommonPath(), "libraries/" + lib.getPath());
|
||||
return new File(Settings.instance().getCommonDirectory(), "libraries/" + lib.getPath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
@@ -37,6 +38,7 @@ import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
@@ -148,11 +150,11 @@ public final class LauncherHelper {
|
||||
else
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
process.stop();
|
||||
Controllers.closeDialog(it);
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
} else
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
@@ -177,7 +179,7 @@ public final class LauncherHelper {
|
||||
// Check if the application has stopped
|
||||
// because onStop will be invoked if tasks fail when the executor service shut down.
|
||||
if (!Controllers.isStopped()) {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Exception ex = executor.getLastException();
|
||||
if (ex != null) {
|
||||
String message;
|
||||
@@ -189,6 +191,8 @@ public final class LauncherHelper {
|
||||
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
|
||||
} else if (ex instanceof NotDecompressingNativesException) {
|
||||
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
|
||||
} else if (ex instanceof LibraryDownloadException) {
|
||||
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
} else {
|
||||
message = StringUtils.getStackTrace(ex);
|
||||
}
|
||||
@@ -267,8 +271,9 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
if (state == LoadingState.DONE) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
@@ -373,7 +378,7 @@ public final class LauncherHelper {
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.INSTANCE.getLogLines())
|
||||
if (logs.size() > Settings.instance().getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
if (setting.isShowLogs()) {
|
||||
@@ -432,7 +437,7 @@ public final class LauncherHelper {
|
||||
|
||||
logWindow.show();
|
||||
logWindow.onDone.register(() -> {
|
||||
logWindow.logLine("Commands: " + StringUtils.makeCommand(process.getCommands()), Log4jLevel.INFO);
|
||||
logWindow.logLine("Command: " + new CommandBuilder().addAll(process.getCommands()).toString(), Log4jLevel.INFO);
|
||||
for (Map.Entry<String, Log4jLevel> entry : logs)
|
||||
logWindow.logLine(entry.getKey(), entry.getValue());
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
*
|
||||
* 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
|
||||
@@ -29,13 +29,27 @@ import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyListProperty;
|
||||
import javafx.beans.property.ReadOnlyListWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -43,35 +57,162 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
public final class Accounts {
|
||||
private Accounts() {}
|
||||
|
||||
public static final String OFFLINE_ACCOUNT_KEY = "offline";
|
||||
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
|
||||
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
|
||||
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
|
||||
public static final YggdrasilAccountFactory FACTORY_YGGDRASIL = new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE);
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.instance().getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer);
|
||||
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.INSTANCE.getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer))
|
||||
);
|
||||
private static final String TYPE_OFFLINE = "offline";
|
||||
private static final String TYPE_YGGDRASIL_ACCOUNT = "yggdrasil";
|
||||
private static final String TYPE_AUTHLIB_INJECTOR = "authlibInjector";
|
||||
|
||||
public static String getAccountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY;
|
||||
else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY;
|
||||
else if (account instanceof YggdrasilAccount) return YGGDRASIL_ACCOUNT_KEY;
|
||||
else return YGGDRASIL_ACCOUNT_KEY;
|
||||
private static Map<String, AccountFactory<?>> type2factory = mapOf(
|
||||
pair(TYPE_OFFLINE, FACTORY_OFFLINE),
|
||||
pair(TYPE_YGGDRASIL_ACCOUNT, FACTORY_YGGDRASIL),
|
||||
pair(TYPE_AUTHLIB_INJECTOR, FACTORY_AUTHLIB_INJECTOR));
|
||||
|
||||
private static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return TYPE_OFFLINE;
|
||||
else if (account instanceof AuthlibInjectorAccount)
|
||||
return TYPE_AUTHLIB_INJECTOR;
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return TYPE_YGGDRASIL_ACCOUNT;
|
||||
else
|
||||
throw new IllegalArgumentException("Failed to determine account type: " + account);
|
||||
}
|
||||
|
||||
static String getAccountId(Account account) {
|
||||
return getAccountId(account.getUsername(), account.getCharacter());
|
||||
public static AccountFactory<?> getAccountFactory(Account account) {
|
||||
return type2factory.get(accountType(account));
|
||||
}
|
||||
|
||||
static String getAccountId(String username, String character) {
|
||||
return username + ":" + character;
|
||||
private static String accountId(Account account) {
|
||||
return account.getUsername() + ":" + account.getCharacter();
|
||||
}
|
||||
|
||||
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static ReadOnlyListProperty<Account> accountsWrapper = new ReadOnlyListWrapper<>(accounts);
|
||||
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
accounts.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
// this methods first checks whether the current selection is valid
|
||||
// if it's valid, the underlying storage will be updated
|
||||
// otherwise, the first account will be selected as an alternative(or null if accounts is empty)
|
||||
Account selected = get();
|
||||
if (accounts.isEmpty()) {
|
||||
if (selected == null) {
|
||||
// valid
|
||||
} else {
|
||||
// the previously selected account is gone, we can only set it to null here
|
||||
set(null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (accounts.contains(selected)) {
|
||||
// valid
|
||||
} else {
|
||||
// the previously selected account is gone
|
||||
set(accounts.get(0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// selection is valid, store it
|
||||
if (!initialized)
|
||||
return;
|
||||
config().setSelectedAccount(selected == null ? "" : accountId(selected));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* True if {@link #init()} hasn't been called.
|
||||
*/
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
accounts.addListener(onInvalidating(Accounts::updateAccountStorages));
|
||||
}
|
||||
|
||||
private static void updateAccountStorages() {
|
||||
// don't update the underlying storage before data loading is completed
|
||||
// otherwise it might cause data loss
|
||||
if (!initialized)
|
||||
return;
|
||||
// update storage
|
||||
config().getAccountStorages().setAll(
|
||||
accounts.stream()
|
||||
.map(account -> {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", accountType(account));
|
||||
return storage;
|
||||
})
|
||||
.collect(toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it's ready to load accounts from {@link ConfigHolder#config()}.
|
||||
*/
|
||||
static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
// load accounts
|
||||
config().getAccountStorages().forEach(storage -> {
|
||||
AccountFactory<?> factory = type2factory.get(storage.get("type"));
|
||||
if (factory == null) {
|
||||
LOG.warning("Unrecognized account type: " + storage);
|
||||
return;
|
||||
}
|
||||
Account account;
|
||||
try {
|
||||
account = factory.fromStorage(storage);
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Failed to load account: " + storage, e);
|
||||
return;
|
||||
}
|
||||
accounts.add(account);
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
|
||||
config().getAuthlibInjectorServers().addListener(onInvalidating(Accounts::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
// load selected account
|
||||
selectedAccount.set(
|
||||
accounts.stream()
|
||||
.filter(it -> accountId(it).equals(config().getSelectedAccount()))
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
public static ObservableList<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public static ReadOnlyListProperty<Account> accountsProperty() {
|
||||
return accountsWrapper;
|
||||
}
|
||||
|
||||
public static Account getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public static void setSelectedAccount(Account selectedAccount) {
|
||||
Accounts.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
public static ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
// ==== authlib-injector ====
|
||||
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
||||
return Settings.SETTINGS.authlibInjectorServers.stream()
|
||||
return config().getAuthlibInjectorServers().stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
@@ -85,8 +226,38 @@ public final class Accounts {
|
||||
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
|
||||
}
|
||||
|
||||
Settings.SETTINGS.authlibInjectorServers.add(server);
|
||||
config().getAuthlibInjectorServers().add(server);
|
||||
return server;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
*/
|
||||
private static void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.map(AuthlibInjectorAccount.class::cast)
|
||||
.filter(it -> !config().getAuthlibInjectorServers().contains(it.getServer()))
|
||||
.collect(toList())
|
||||
.forEach(accounts::remove);
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Login type name i18n ===
|
||||
private static Map<AccountFactory<?>, String> loginType2name = mapOf(
|
||||
pair(Accounts.FACTORY_OFFLINE, i18n("account.methods.offline")),
|
||||
pair(Accounts.FACTORY_YGGDRASIL, i18n("account.methods.yggdrasil")),
|
||||
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, i18n("account.methods.authlib_injector")));
|
||||
|
||||
public static String getAccountTypeName(AccountFactory<?> factory) {
|
||||
return Optional.ofNullable(loginType2name.get(factory))
|
||||
.orElseThrow(() -> new IllegalArgumentException("No corresponding login type name"));
|
||||
}
|
||||
|
||||
public static String getAccountTypeName(Account account) {
|
||||
return getAccountTypeName(getAccountFactory(account));
|
||||
}
|
||||
// ====
|
||||
}
|
||||
@@ -17,95 +17,422 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.util.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
|
||||
public final class Config implements Cloneable {
|
||||
public final class Config implements Cloneable, Observable {
|
||||
|
||||
private static final Gson CONFIG_GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
|
||||
.registerTypeAdapter(Theme.class, new Theme.TypeAdapter())
|
||||
.registerTypeAdapter(SupportedLocale.class, new SupportedLocale.TypeAdapter())
|
||||
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
|
||||
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static Config fromJson(String json) throws JsonParseException {
|
||||
Config instance = CONFIG_GSON.fromJson(json, Config.class);
|
||||
// Gson will replace the property fields (even they are final!)
|
||||
// So we have to add the listeners again after deserialization
|
||||
instance.addListenerToProperties();
|
||||
return instance;
|
||||
}
|
||||
|
||||
@SerializedName("last")
|
||||
public final StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
private StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("backgroundType")
|
||||
public final IntegerProperty backgroundImageType = new SimpleIntegerProperty(0);
|
||||
private ObjectProperty<EnumBackgroundImage> backgroundImageType = new SimpleObjectProperty<>(EnumBackgroundImage.DEFAULT);
|
||||
|
||||
@SerializedName("bgpath")
|
||||
public final StringProperty backgroundImage = new SimpleStringProperty();
|
||||
private StringProperty backgroundImage = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("commonDirType")
|
||||
private ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
@SerializedName("commonpath")
|
||||
public final StringProperty commonDirectory = new SimpleStringProperty(Launcher.MINECRAFT_DIRECTORY.getAbsolutePath());
|
||||
private StringProperty commonDirectory = new SimpleStringProperty(Launcher.MINECRAFT_DIRECTORY.getAbsolutePath());
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
public final BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
private BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("hasProxyAuth")
|
||||
public final BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
private BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
public final IntegerProperty proxyType = new SimpleIntegerProperty();
|
||||
private ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.DIRECT);
|
||||
|
||||
@SerializedName("proxyHost")
|
||||
public final StringProperty proxyHost = new SimpleStringProperty();
|
||||
private StringProperty proxyHost = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPort")
|
||||
public final StringProperty proxyPort = new SimpleStringProperty();
|
||||
private StringProperty proxyPort = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyUserName")
|
||||
public final StringProperty proxyUser = new SimpleStringProperty();
|
||||
private StringProperty proxyUser = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPassword")
|
||||
public final StringProperty proxyPass = new SimpleStringProperty();
|
||||
private StringProperty proxyPass = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("theme")
|
||||
public final StringProperty theme = new SimpleStringProperty();
|
||||
private ObjectProperty<Theme> theme = new SimpleObjectProperty<>(Theme.BLUE);
|
||||
|
||||
@SerializedName("localization")
|
||||
public final StringProperty localization = new SimpleStringProperty();
|
||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
public final IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
private IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
|
||||
@SerializedName("configurations")
|
||||
public final ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
|
||||
@SerializedName("accounts")
|
||||
public final ObservableList<Map<Object, Object>> accounts = FXCollections.observableArrayList();
|
||||
private ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("selectedAccount")
|
||||
public final StringProperty selectedAccount = new SimpleStringProperty("");
|
||||
private StringProperty selectedAccount = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("fontFamily")
|
||||
public final StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
private StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
|
||||
@SerializedName("fontSize")
|
||||
public final DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
private DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
|
||||
@SerializedName("logLines")
|
||||
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
private IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("firstLaunch")
|
||||
public final BooleanProperty firstLaunch = new SimpleBooleanProperty(true);
|
||||
@SerializedName("authlibInjectorServers")
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
public Config() {
|
||||
addListenerToProperties();
|
||||
}
|
||||
|
||||
private void addListenerToProperties() {
|
||||
Stream.of(getClass().getDeclaredFields())
|
||||
.filter(it -> {
|
||||
int modifiers = it.getModifiers();
|
||||
return !Modifier.isTransient(modifiers) && !Modifier.isStatic(modifiers);
|
||||
})
|
||||
.filter(it -> Observable.class.isAssignableFrom(it.getType()))
|
||||
.map(it -> {
|
||||
try {
|
||||
return (Observable) it.get(this);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Failed to get my own properties");
|
||||
}
|
||||
})
|
||||
.forEach(helper::receiveUpdatesFrom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
// Getters & Setters & Properties
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
}
|
||||
|
||||
public void setSelectedProfile(String selectedProfile) {
|
||||
this.selectedProfile.set(selectedProfile);
|
||||
}
|
||||
|
||||
public StringProperty selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public EnumBackgroundImage getBackgroundImageType() {
|
||||
return backgroundImageType.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
|
||||
this.backgroundImageType.set(backgroundImageType);
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
public StringProperty backgroundImageProperty() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public EnumCommonDirectory getCommonDirType() {
|
||||
return commonDirType.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumCommonDirectory> commonDirTypeProperty() {
|
||||
return commonDirType;
|
||||
}
|
||||
|
||||
public void setCommonDirType(EnumCommonDirectory commonDirType) {
|
||||
this.commonDirType.set(commonDirType);
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
return commonDirectory.get();
|
||||
}
|
||||
|
||||
public void setCommonDirectory(String commonDirectory) {
|
||||
this.commonDirectory.set(commonDirectory);
|
||||
}
|
||||
|
||||
public StringProperty commonDirectoryProperty() {
|
||||
return commonDirectory;
|
||||
}
|
||||
|
||||
public boolean hasProxy() {
|
||||
return hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
this.hasProxy.set(hasProxy);
|
||||
}
|
||||
|
||||
public BooleanProperty hasProxyProperty() {
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() {
|
||||
return hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
this.hasProxyAuth.set(hasProxyAuth);
|
||||
}
|
||||
|
||||
public BooleanProperty hasProxyAuthProperty() {
|
||||
return hasProxyAuth;
|
||||
}
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType.get();
|
||||
}
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType.set(proxyType);
|
||||
}
|
||||
|
||||
public ObjectProperty<Proxy.Type> proxyTypeProperty() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost.set(proxyHost);
|
||||
}
|
||||
|
||||
public StringProperty proxyHostProperty() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
this.proxyPort.set(proxyPort);
|
||||
}
|
||||
|
||||
public StringProperty proxyPortProperty() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
this.proxyUser.set(proxyUser);
|
||||
}
|
||||
|
||||
public StringProperty proxyUserProperty() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
this.proxyPass.set(proxyPass);
|
||||
}
|
||||
|
||||
public StringProperty proxyPassProperty() {
|
||||
return proxyPass;
|
||||
}
|
||||
|
||||
public Theme getTheme() {
|
||||
return theme.get();
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme.set(theme);
|
||||
}
|
||||
|
||||
public ObjectProperty<Theme> themeProperty() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public SupportedLocale getLocalization() {
|
||||
return localization.get();
|
||||
}
|
||||
|
||||
public void setLocalization(SupportedLocale localization) {
|
||||
this.localization.set(localization);
|
||||
}
|
||||
|
||||
public ObjectProperty<SupportedLocale> localizationProperty() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
return downloadType.get();
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
this.downloadType.set(downloadType);
|
||||
}
|
||||
|
||||
public IntegerProperty downloadTypeProperty() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
public ObservableMap<String, Profile> getConfigurations() {
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public ObservableList<Map<Object, Object>> getAccountStorages() {
|
||||
return accountStorages;
|
||||
}
|
||||
|
||||
public String getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public void setSelectedAccount(String selectedAccount) {
|
||||
this.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
public StringProperty selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily.get();
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily.set(fontFamily);
|
||||
}
|
||||
|
||||
public StringProperty fontFamilyProperty() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return fontSize.get();
|
||||
}
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize.set(fontSize);
|
||||
}
|
||||
|
||||
public DoubleProperty fontSizeProperty() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return logLines.get();
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
this.logLines.set(logLines);
|
||||
}
|
||||
|
||||
public IntegerProperty logLinesProperty() {
|
||||
return logLines;
|
||||
}
|
||||
|
||||
public ObservableList<AuthlibInjectorServer> getAuthlibInjectorServers() {
|
||||
return authlibInjectorServers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.setting;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
public final class ConfigHolder {
|
||||
|
||||
private ConfigHolder() {}
|
||||
|
||||
public static final String CONFIG_FILENAME = "hmcl.json";
|
||||
public static final Path CONFIG_PATH = Paths.get(CONFIG_FILENAME).toAbsolutePath();
|
||||
|
||||
private static Config configInstance;
|
||||
private static boolean newlyCreated;
|
||||
|
||||
public static Config config() {
|
||||
if (configInstance == null) {
|
||||
throw new IllegalStateException("Configuration hasn't been loaded");
|
||||
}
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
public static boolean isNewlyCreated() {
|
||||
if (configInstance == null) {
|
||||
throw new IllegalStateException("Configuration hasn't been loaded");
|
||||
}
|
||||
return newlyCreated;
|
||||
}
|
||||
|
||||
public synchronized static void init() {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("Configuration is already loaded");
|
||||
}
|
||||
|
||||
configInstance = loadConfig();
|
||||
configInstance.addListener(source -> saveConfig());
|
||||
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private static Config loadConfig() {
|
||||
if (Files.exists(CONFIG_PATH)) {
|
||||
try {
|
||||
String content = new String(Files.readAllBytes(CONFIG_PATH), UTF_8);
|
||||
Config deserialized = Config.fromJson(content);
|
||||
if (deserialized == null) {
|
||||
LOG.info("Config is empty");
|
||||
} else {
|
||||
Map<?, ?> raw = new Gson().fromJson(content, Map.class);
|
||||
return upgradeConfig(deserialized, raw);
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Something went wrong when loading config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Creating an empty config");
|
||||
newlyCreated = true;
|
||||
return new Config();
|
||||
}
|
||||
|
||||
static void saveConfig() {
|
||||
LOG.info("Saving config");
|
||||
try {
|
||||
Files.write(CONFIG_PATH, configInstance.toJson().getBytes(UTF_8));
|
||||
} catch (IOException ex) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Config upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
return deserialized;
|
||||
}
|
||||
}
|
||||
+15
-18
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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
|
||||
@@ -17,20 +17,17 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Proxies {
|
||||
private Proxies() {}
|
||||
|
||||
public static final List<Proxy.Type> PROXIES = Lang.immutableListOf(Proxy.Type.DIRECT, Proxy.Type.HTTP, Proxy.Type.SOCKS);
|
||||
|
||||
public static Proxy.Type getProxyType(int index) {
|
||||
return Lang.get(PROXIES, index).orElse(null);
|
||||
}
|
||||
public enum EnumCommonDirectory {
|
||||
/**
|
||||
* same to game directory
|
||||
*/
|
||||
DISABLED,
|
||||
/**
|
||||
* %appdata%/.minecraft or ~/.minecraft
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* user customized common directory.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import org.jackhuang.hmcl.game.HMCLDependencyManager;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -83,8 +86,18 @@ public final class Profile {
|
||||
nameProperty.set(name);
|
||||
}
|
||||
|
||||
public Profile() {
|
||||
this("Default");
|
||||
private BooleanProperty useRelativePathProperty = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
|
||||
public BooleanProperty useRelativePathProperty() {
|
||||
return useRelativePathProperty();
|
||||
}
|
||||
|
||||
public boolean isUseRelativePath() {
|
||||
return useRelativePathProperty.get();
|
||||
}
|
||||
|
||||
public void setUseRelativePath(boolean useRelativePath) {
|
||||
useRelativePathProperty.set(useRelativePath);
|
||||
}
|
||||
|
||||
public Profile(String name) {
|
||||
@@ -109,7 +122,7 @@ public final class Profile {
|
||||
}
|
||||
|
||||
public HMCLDependencyManager getDependency() {
|
||||
return new HMCLDependencyManager(this, Settings.INSTANCE.getDownloadProvider(), Settings.INSTANCE.getProxy());
|
||||
return new HMCLDependencyManager(this, Settings.instance().getDownloadProvider());
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
@@ -153,6 +166,7 @@ public final class Profile {
|
||||
return new ToStringBuilder(this)
|
||||
.append("gameDir", getGameDir())
|
||||
.append("name", getName())
|
||||
.append("useRelativePath", isUseRelativePath())
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -160,6 +174,7 @@ public final class Profile {
|
||||
nameProperty.addListener(listener);
|
||||
globalProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
useRelativePathProperty.addListener(listener);
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
@@ -176,6 +191,7 @@ public final class Profile {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("global", context.serialize(src.getGlobal()));
|
||||
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
||||
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
@@ -188,6 +204,8 @@ public final class Profile {
|
||||
|
||||
Profile profile = new Profile("Default", new File(gameDir));
|
||||
profile.setGlobal(context.deserialize(obj.get("global"), VersionSetting.class));
|
||||
|
||||
profile.setUseRelativePath(Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
|
||||
public final class ProxyManager {
|
||||
private ProxyManager() {
|
||||
}
|
||||
|
||||
private static ObjectBinding<Proxy> proxyProperty = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
String host = config().getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(config().getProxyPort());
|
||||
if (!config().hasProxy() || StringUtils.isBlank(host) || port == null || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
} else {
|
||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||
}
|
||||
},
|
||||
config().proxyTypeProperty(),
|
||||
config().proxyHostProperty(),
|
||||
config().proxyPortProperty(),
|
||||
config().hasProxyProperty());
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxyProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<Proxy> proxyProperty() {
|
||||
return proxyProperty;
|
||||
}
|
||||
|
||||
static void init() {
|
||||
proxyProperty.addListener(observable -> updateSystemProxy());
|
||||
|
||||
updateSystemProxy();
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
if (username != null && password != null) {
|
||||
return new PasswordAuthentication(username, password.toCharArray());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void updateSystemProxy() {
|
||||
Proxy proxy = proxyProperty.get();
|
||||
if (proxy.type() == Proxy.Type.DIRECT) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
} else {
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
String host = address.getHostString();
|
||||
String port = String.valueOf(address.getPort());
|
||||
if (proxy.type() == Type.HTTP) {
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
System.setProperty("http.proxyHost", host);
|
||||
System.setProperty("http.proxyPort", port);
|
||||
System.setProperty("https.proxyHost", host);
|
||||
System.setProperty("https.proxyPort", port);
|
||||
} else if (proxy.type() == Type.SOCKS) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.setProperty("socksProxyHost", host);
|
||||
System.setProperty("socksProxyPort", port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,318 +17,89 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class Settings {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static final String SETTINGS_FILE_NAME = "hmcl.json";
|
||||
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
|
||||
private static Settings instance;
|
||||
|
||||
public static final Config SETTINGS = initSettings();
|
||||
public static Settings instance() {
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("Settings hasn't been initialized");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
|
||||
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
|
||||
|
||||
private final boolean firstLaunch;
|
||||
/**
|
||||
* Should be called from {@link ConfigHolder#init()}.
|
||||
*/
|
||||
static void init() {
|
||||
instance = new Settings();
|
||||
}
|
||||
|
||||
private Settings() {
|
||||
firstLaunch = SETTINGS.firstLaunch.get();
|
||||
|
||||
loadProxy();
|
||||
|
||||
for (Iterator<Map<Object, Object>> iterator = SETTINGS.accounts.iterator(); iterator.hasNext();) {
|
||||
Map<Object, Object> settings = iterator.next();
|
||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
|
||||
if (factory == null) {
|
||||
LOG.warning("Unrecognized account type, removing: " + settings);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
Account account;
|
||||
try {
|
||||
account = factory.fromStorage(settings, getProxy());
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Malformed account storage, removing: " + settings, e);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
|
||||
checkProfileMap();
|
||||
|
||||
save();
|
||||
|
||||
for (Map.Entry<String, Profile> entry2 : getProfileMap().entrySet()) {
|
||||
entry2.getValue().setName(entry2.getKey());
|
||||
entry2.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
entry2.getValue().addPropertyChangedListener(e -> save());
|
||||
for (Map.Entry<String, Profile> profileEntry : getProfileMap().entrySet()) {
|
||||
profileEntry.getValue().setName(profileEntry.getKey());
|
||||
profileEntry.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
profileEntry.getValue().addPropertyChangedListener(e -> ConfigHolder.saveConfig());
|
||||
}
|
||||
|
||||
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
||||
}
|
||||
|
||||
private static Config initSettings() {
|
||||
Config c = new Config();
|
||||
if (SETTINGS_FILE.exists())
|
||||
try {
|
||||
String str = FileUtils.readText(SETTINGS_FILE);
|
||||
if (StringUtils.isBlank(str))
|
||||
Logging.LOG.finer("Settings file is empty, use the default settings.");
|
||||
else {
|
||||
Config d = GSON.fromJson(str, Config.class);
|
||||
if (d != null)
|
||||
c = d;
|
||||
}
|
||||
Logging.LOG.finest("Initialized settings.");
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
SETTINGS.accounts.clear();
|
||||
SETTINGS.firstLaunch.set(false);
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
SETTINGS.accounts.add(storage);
|
||||
}
|
||||
|
||||
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to save config", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFirstLaunch() {
|
||||
return firstLaunch;
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.commonDirectory.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.commonDirectory.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public String getCommonPath() {
|
||||
return commonPath.get();
|
||||
}
|
||||
|
||||
public StringProperty commonPathProperty() {
|
||||
return commonPath;
|
||||
}
|
||||
|
||||
public void setCommonPath(String commonPath) {
|
||||
this.commonPath.set(commonPath);
|
||||
}
|
||||
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.localization.get());
|
||||
|
||||
public Locales.SupportedLocale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(Locales.SupportedLocale locale) {
|
||||
this.locale = locale;
|
||||
SETTINGS.localization.set(Locales.getNameByLocale(locale));
|
||||
save();
|
||||
}
|
||||
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.proxyType.get());
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
SETTINGS.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
|
||||
save();
|
||||
loadProxy();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return SETTINGS.proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
SETTINGS.proxyHost.set(proxyHost);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return SETTINGS.proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
SETTINGS.proxyPort.set(proxyPort);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return SETTINGS.proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
SETTINGS.proxyUser.set(proxyUser);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return SETTINGS.proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
SETTINGS.proxyPass.set(proxyPass);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxy() {
|
||||
return SETTINGS.hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
SETTINGS.hasProxy.set(hasProxy);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() {
|
||||
return SETTINGS.hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
SETTINGS.hasProxyAuth.set(hasProxyAuth);
|
||||
save();
|
||||
}
|
||||
|
||||
private void loadProxy() {
|
||||
String host = getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(getProxyPort());
|
||||
if (!hasProxy() || StringUtils.isBlank(host) || port == null || getProxyType() == Proxy.Type.DIRECT)
|
||||
proxy = Proxy.NO_PROXY;
|
||||
else {
|
||||
System.setProperty("http.proxyHost", getProxyHost());
|
||||
System.setProperty("http.proxyPort", getProxyPort());
|
||||
proxy = new Proxy(proxyType, new InetSocketAddress(host, port));
|
||||
|
||||
String user = getProxyUser();
|
||||
String pass = getProxyPass();
|
||||
if (hasProxyAuth() && StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass)) {
|
||||
System.setProperty("http.proxyUser", user);
|
||||
System.setProperty("http.proxyPassword", pass);
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(user, pass.toCharArray());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
config().addListener(source -> ConfigHolder.saveConfig());
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return Font.font(SETTINGS.fontFamily.get(), SETTINGS.fontSize.get());
|
||||
return Font.font(config().getFontFamily(), config().getFontSize());
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
SETTINGS.fontFamily.set(font.getFamily());
|
||||
SETTINGS.fontSize.set(font.getSize());
|
||||
save();
|
||||
config().setFontFamily(font.getFamily());
|
||||
config().setFontSize(font.getSize());
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(SETTINGS.logLines.get(), 100);
|
||||
return Math.max(config().getLogLines(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
SETTINGS.logLines.set(logLines);
|
||||
save();
|
||||
config().setLogLines(logLines);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* AUTHLIB INJECTORS *
|
||||
****************************************/
|
||||
public boolean isCommonDirectoryDisabled() {
|
||||
return config().getCommonDirType() == EnumCommonDirectory.DISABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
* Don't call this before {@link #migrateAuthlibInjectorServers()} is called, otherwise old data would be lost.
|
||||
*/
|
||||
private void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.values().stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
|
||||
.collect(toList())
|
||||
.forEach(this::deleteAccount);
|
||||
public static String getDefaultCommonDirectory() {
|
||||
return Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
switch (config().getCommonDirType()) {
|
||||
case DISABLED:
|
||||
return null;
|
||||
case DEFAULT:
|
||||
return getDefaultCommonDirectory();
|
||||
case CUSTOM:
|
||||
return config().getCommonDirectory();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -336,161 +107,14 @@ public class Settings {
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return DownloadProviders.getDownloadProvider(SETTINGS.downloadType.get());
|
||||
return DownloadProviders.getDownloadProvider(config().getDownloadType());
|
||||
}
|
||||
|
||||
public void setDownloadProvider(DownloadProvider downloadProvider) {
|
||||
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
|
||||
if (index == -1)
|
||||
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
|
||||
SETTINGS.downloadType.set(index);
|
||||
save();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* ACCOUNTS *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.selectedAccount.get())) {
|
||||
@Override
|
||||
public Account get() {
|
||||
Account a = super.get();
|
||||
if (a == null || !accounts.containsKey(Accounts.getAccountId(a))) {
|
||||
Account acc = accounts.values().stream().findAny().orElse(null);
|
||||
set(acc);
|
||||
return acc;
|
||||
} else return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Account newValue) {
|
||||
if (newValue == null || accounts.containsKey(Accounts.getAccountId(newValue))) {
|
||||
super.set(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public Account getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public void setSelectedAccount(Account selectedAccount) {
|
||||
this.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
onAccountLoading();
|
||||
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountAddedEvent(this, account));
|
||||
}
|
||||
|
||||
public Account getAccount(String name, String character) {
|
||||
return accounts.get(Accounts.getAccountId(name, character));
|
||||
}
|
||||
|
||||
public Collection<Account> getAccounts() {
|
||||
return Collections.unmodifiableCollection(accounts.values());
|
||||
}
|
||||
|
||||
public void deleteAccount(String name, String character) {
|
||||
accounts.remove(Accounts.getAccountId(name, character));
|
||||
|
||||
onAccountLoading();
|
||||
selectedAccount.get();
|
||||
}
|
||||
|
||||
public void deleteAccount(Account account) {
|
||||
accounts.remove(Accounts.getAccountId(account));
|
||||
|
||||
onAccountLoading();
|
||||
selectedAccount.get();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* BACKGROUND *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.backgroundImage.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.backgroundImage.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage.get();
|
||||
}
|
||||
|
||||
public ImmediateStringProperty backgroundImageProperty() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.backgroundImageType.get())) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.backgroundImageType.set(get().ordinal());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public EnumBackgroundImage getBackgroundImageType() {
|
||||
return backgroundImageType.get();
|
||||
}
|
||||
|
||||
public ImmediateObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
|
||||
this.backgroundImageType.set(backgroundImageType);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* THEME *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.theme.get()).orElse(Theme.BLUE)) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.theme.set(get().getName().toLowerCase());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public Theme getTheme() {
|
||||
return theme.get();
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme.set(theme);
|
||||
}
|
||||
|
||||
public ImmediateObjectProperty<Theme> themeProperty() {
|
||||
return theme;
|
||||
config().setDownloadType(index);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -500,20 +124,18 @@ public class Settings {
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(SETTINGS.selectedProfile.get())) {
|
||||
if (!hasProfile(config().getSelectedProfile())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
|
||||
SETTINGS.selectedProfile.set(selectedProfile);
|
||||
save();
|
||||
config().setSelectedProfile(selectedProfile);
|
||||
});
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
return getProfile(SETTINGS.selectedProfile.get());
|
||||
return getProfile(config().getSelectedProfile());
|
||||
}
|
||||
|
||||
public void setSelectedProfile(Profile selectedProfile) {
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.selectedProfile.get())) {
|
||||
SETTINGS.selectedProfile.set(selectedProfile.getName());
|
||||
save();
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), config().getSelectedProfile())) {
|
||||
config().setSelectedProfile(selectedProfile.getName());
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
}
|
||||
@@ -530,7 +152,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return SETTINGS.configurations;
|
||||
return config().getConfigurations();
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
@@ -545,8 +167,6 @@ public class Settings {
|
||||
Schedulers.computation().schedule(this::onProfileLoading);
|
||||
|
||||
ver.nameProperty().setChangedListener(this::profileNameChanged);
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
public void deleteProfile(Profile profile) {
|
||||
@@ -561,8 +181,12 @@ public class Settings {
|
||||
|
||||
private void checkProfileMap() {
|
||||
if (getProfileMap().isEmpty()) {
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE));
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE);
|
||||
current.setUseRelativePath(true);
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, current);
|
||||
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY);
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, home);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,12 +207,4 @@ public class Settings {
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(this, getProfiles()));
|
||||
onProfileChanged();
|
||||
}
|
||||
|
||||
public void onAccountLoading() {
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(this, getAccounts()));
|
||||
}
|
||||
|
||||
public Config getRawConfig() {
|
||||
return SETTINGS.clone();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
@@ -31,13 +36,15 @@ import java.util.logging.Level;
|
||||
|
||||
public class Theme {
|
||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||
public static final Theme DARK_BLUE = new Theme("dark_blue", "#283593");
|
||||
public static final Theme GREEN = new Theme("green", "#43A047");
|
||||
public static final Theme ORANGE = new Theme("orange", "#E67E22");
|
||||
public static final Theme PURPLE = new Theme("purple", "#9C27B0");
|
||||
public static final Theme RED = new Theme("red", "#F44336");
|
||||
|
||||
public static final Theme[] VALUES = new Theme[]{BLUE, DARK_BLUE, GREEN, ORANGE, PURPLE, RED};
|
||||
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
||||
Color.web("#5C6BC0"), // blue
|
||||
Color.web("#283593"), // dark blue
|
||||
Color.web("#43A047"), // green
|
||||
Color.web("#E67E22"), // orange
|
||||
Color.web("#9C27B0"), // purple
|
||||
Color.web("#F44336") // red
|
||||
};
|
||||
|
||||
private final String color;
|
||||
private final String name;
|
||||
@@ -68,8 +75,7 @@ public class Theme {
|
||||
}
|
||||
|
||||
public String[] getStylesheets() {
|
||||
String name = isCustom() ? BLUE.getName() : this.name;
|
||||
String css = Theme.class.getResource("/assets/css/" + name + ".css").toExternalForm();
|
||||
String css;
|
||||
try {
|
||||
File temp = File.createTempFile("hmcl", ".css");
|
||||
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
|
||||
@@ -77,7 +83,8 @@ public class Theme {
|
||||
.replace("%font-color%", getColorDisplayName(getForegroundColor())));
|
||||
css = temp.toURI().toString();
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet", e);
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
|
||||
css = Theme.class.getResource("/assets/css/blue.css").toExternalForm();
|
||||
}
|
||||
|
||||
return new String[]{
|
||||
@@ -98,10 +105,6 @@ public class Theme {
|
||||
if (name == null)
|
||||
return Optional.empty();
|
||||
|
||||
for (Theme theme : VALUES)
|
||||
if (theme.name.equalsIgnoreCase(name))
|
||||
return Optional.of(theme);
|
||||
|
||||
if (name.startsWith("#"))
|
||||
try {
|
||||
Color.web(name);
|
||||
@@ -117,7 +120,7 @@ public class Theme {
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> foregroundFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> Settings.INSTANCE.getTheme().getForegroundColor(), Settings.INSTANCE.themeProperty());
|
||||
return Bindings.createObjectBinding(() -> config().getTheme().getForegroundColor(), config().themeProperty());
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> blackFillBinding() {
|
||||
@@ -127,4 +130,16 @@ public class Theme {
|
||||
public static ObjectBinding<Color> whiteFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> Color.WHITE);
|
||||
}
|
||||
|
||||
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {
|
||||
@Override
|
||||
public void write(JsonWriter out, Theme value) throws IOException {
|
||||
out.value(value.getName().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Theme read(JsonReader in) throws IOException {
|
||||
return getTheme(in.nextString()).orElse(Theme.BLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -79,7 +81,7 @@ public final class VersionSetting {
|
||||
}
|
||||
|
||||
/**
|
||||
* Java version or null if user customizes java directory.
|
||||
* Java version or "Custom" if user customizes java directory, "Default" if the jvm that this app relies on.
|
||||
*/
|
||||
public String getJava() {
|
||||
return javaProperty.get();
|
||||
@@ -89,6 +91,15 @@ public final class VersionSetting {
|
||||
javaProperty.set(java);
|
||||
}
|
||||
|
||||
public boolean isUsesCustomJavaDir() {
|
||||
return "Custom".equals(getJava());
|
||||
}
|
||||
|
||||
public void setUsesCustomJavaDir() {
|
||||
setJava("Custom");
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
@@ -276,23 +287,6 @@ public final class VersionSetting {
|
||||
notCheckGameProperty.set(notCheckGame);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty noCommonProperty = new ImmediateBooleanProperty(this, "noCommon", false);
|
||||
|
||||
public ImmediateBooleanProperty noCommonProperty() {
|
||||
return noCommonProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if HMCL does not find/download libraries in/to common path.
|
||||
*/
|
||||
public boolean isNoCommon() {
|
||||
return noCommonProperty.get();
|
||||
}
|
||||
|
||||
public void setNoCommon(boolean noCommon) {
|
||||
noCommonProperty.set(noCommon);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty showLogsProperty = new ImmediateBooleanProperty(this, "showLogs", false);
|
||||
|
||||
public ImmediateBooleanProperty showLogsProperty() {
|
||||
@@ -453,7 +447,7 @@ public final class VersionSetting {
|
||||
if (StringUtils.isBlank(getJava()))
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
|
||||
if ("Default".equals(getJava())) return JavaVersion.fromCurrentEnvironment();
|
||||
else if ("Custom".equals(getJava())) {
|
||||
else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(new File(getJavaDir()));
|
||||
} catch (IOException e) {
|
||||
@@ -475,6 +469,11 @@ public final class VersionSetting {
|
||||
} else throw new Error();
|
||||
}
|
||||
|
||||
public void setJavaVersion(JavaVersion java) {
|
||||
setJava(java.getVersion());
|
||||
setDefaultJavaPath(java.getBinary().toString());
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
usesGlobalProperty.addListener(listener);
|
||||
javaProperty.addListener(listener);
|
||||
@@ -488,7 +487,6 @@ public final class VersionSetting {
|
||||
minecraftArgsProperty.addListener(listener);
|
||||
noJVMArgsProperty.addListener(listener);
|
||||
notCheckGameProperty.addListener(listener);
|
||||
noCommonProperty.addListener(listener);
|
||||
showLogsProperty.addListener(listener);
|
||||
serverIpProperty.addListener(listener);
|
||||
fullscreenProperty.addListener(listener);
|
||||
@@ -517,10 +515,10 @@ public final class VersionSetting {
|
||||
.setFullscreen(isFullscreen())
|
||||
.setServerIp(getServerIp())
|
||||
.setWrapper(getWrapper())
|
||||
.setProxyHost(Settings.INSTANCE.getProxyHost())
|
||||
.setProxyPort(Settings.INSTANCE.getProxyPort())
|
||||
.setProxyUser(Settings.INSTANCE.getProxyUser())
|
||||
.setProxyPass(Settings.INSTANCE.getProxyPass())
|
||||
.setProxyHost(config().getProxyHost())
|
||||
.setProxyPort(config().getProxyPort())
|
||||
.setProxyUser(config().getProxyUser())
|
||||
.setProxyPass(config().getProxyPass())
|
||||
.setPrecalledCommand(getPreLaunchCommand())
|
||||
.setNoGeneratedJVMArgs(isNoJVMArgs())
|
||||
.create();
|
||||
@@ -553,7 +551,6 @@ public final class VersionSetting {
|
||||
obj.addProperty("fullscreen", src.isFullscreen());
|
||||
obj.addProperty("noJVMArgs", src.isNoJVMArgs());
|
||||
obj.addProperty("notCheckGame", src.isNotCheckGame());
|
||||
obj.addProperty("noCommon", src.isNoCommon());
|
||||
obj.addProperty("showLogs", src.isShowLogs());
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
@@ -591,7 +588,6 @@ public final class VersionSetting {
|
||||
vs.setFullscreen(Optional.ofNullable(obj.get("fullscreen")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoCommon(Optional.ofNullable(obj.get("noCommon")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
|
||||
vs.setGameDirType(EnumGameDirectory.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
|
||||
@@ -21,13 +21,13 @@ import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -35,7 +35,6 @@ public class AccountLoginPane extends StackPane {
|
||||
private final Account oldAccount;
|
||||
private final Consumer<AuthInfo> success;
|
||||
private final Runnable failed;
|
||||
private final Consumer<Region> closeConsumer;
|
||||
|
||||
@FXML
|
||||
private Label lblUsername;
|
||||
@@ -43,11 +42,10 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
|
||||
public AccountLoginPane(Account oldAccount, Consumer<Region> closeConsumer, Consumer<AuthInfo> success, Runnable failed) {
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
this.success = success;
|
||||
this.failed = failed;
|
||||
this.closeConsumer = closeConsumer;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
|
||||
|
||||
@@ -70,9 +68,9 @@ public class AccountLoginPane extends StackPane {
|
||||
Object account = variable.get("login");
|
||||
if (account instanceof AuthInfo) {
|
||||
success.accept(((AuthInfo) account));
|
||||
closeConsumer.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (account instanceof NoSelectedCharacterException) {
|
||||
closeConsumer.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
|
||||
}
|
||||
@@ -84,6 +82,6 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML
|
||||
private void onCancel() {
|
||||
failed.run();
|
||||
closeConsumer.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
}
|
||||
@@ -31,14 +31,17 @@ import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.installTooltip;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -78,25 +81,23 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
|
||||
|
||||
FXUtils.setLimitWidth(this, 300);
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
lblServer.setText(((AuthlibInjectorAccount) account).getServer().getName());
|
||||
FXUtils.setLimitHeight(this, 182);
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
|
||||
lblServer.setText(server.getName());
|
||||
installTooltip(lblServer, server.getUrl());
|
||||
} else {
|
||||
componentList.removeChildren(paneServer);
|
||||
|
||||
if (account instanceof OfflineAccount) {
|
||||
componentList.removeChildren(paneEmail);
|
||||
FXUtils.setLimitHeight(this, 110);
|
||||
} else
|
||||
FXUtils.setLimitHeight(this, 145);
|
||||
}
|
||||
}
|
||||
|
||||
btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15));
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
lblCharacter.setText(account.getCharacter());
|
||||
lblType.setText(AddAccountPane.accountType(account));
|
||||
lblType.setText(Accounts.getAccountTypeName(account));
|
||||
lblEmail.setText(account.getUsername());
|
||||
|
||||
btnRefresh.setVisible(account instanceof YggdrasilAccount);
|
||||
@@ -104,7 +105,7 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
Settings.INSTANCE.deleteAccount(account);
|
||||
Accounts.getAccounts().remove(account);
|
||||
Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,166 +19,172 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
|
||||
@FXML private JFXTextField txtUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblPassword;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXComboBox<AccountFactory<?>> cboType;
|
||||
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
|
||||
@FXML private Label lblInjectorServer;
|
||||
@FXML private Hyperlink linkManageInjectorServers;
|
||||
@FXML private JFXDialogLayout layout;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
private final Consumer<Region> finalization;
|
||||
|
||||
public AddAccountPane(Consumer<Region> finalization) {
|
||||
this.finalization = finalization;
|
||||
|
||||
public AddAccountPane() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
|
||||
|
||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
|
||||
cboServers.setPromptText(i18n("general.prompt.empty"));
|
||||
Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());
|
||||
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
|
||||
selectDefaultServer();
|
||||
|
||||
// workaround: otherwise the combox will be black
|
||||
if (!cboServers.getItems().isEmpty())
|
||||
cboServers.getSelectionModel().select(0);
|
||||
|
||||
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
|
||||
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
|
||||
txtPassword.setVisible(newValue.intValue() != 0);
|
||||
lblPassword.setVisible(newValue.intValue() != 0);
|
||||
cboServers.setVisible(newValue.intValue() == 2);
|
||||
linkManageInjectorServers.setVisible(newValue.intValue() == 2);
|
||||
lblInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
validateAcceptButton();
|
||||
});
|
||||
cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_YGGDRASIL, Accounts.FACTORY_AUTHLIB_INJECTOR);
|
||||
cboType.setConverter(stringConverter(Accounts::getAccountTypeName));
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
txtPassword.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.setOnAction(e -> onCreationAccept());
|
||||
ReadOnlyObjectProperty<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty();
|
||||
|
||||
txtPassword.visibleProperty().bind(loginType.isNotEqualTo(Accounts.FACTORY_OFFLINE));
|
||||
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
|
||||
|
||||
cboServers.visibleProperty().bind(loginType.isEqualTo(Accounts.FACTORY_AUTHLIB_INJECTOR));
|
||||
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
linkManageInjectorServers.visibleProperty().bind(cboServers.visibleProperty());
|
||||
|
||||
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
|
||||
txtUsername.textProperty().addListener(it -> validateAcceptButton());
|
||||
txtPassword.textProperty().addListener(it -> validateAcceptButton());
|
||||
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !( // consider the opposite situation: input is valid
|
||||
txtUsername.validate() &&
|
||||
// invisible means the field is not needed, neither should it be validated
|
||||
(!txtPassword.isVisible() || txtPassword.validate()) &&
|
||||
(!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null)
|
||||
),
|
||||
txtUsername.textProperty(),
|
||||
txtPassword.textProperty(), txtPassword.visibleProperty(),
|
||||
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
|
||||
}
|
||||
|
||||
private void validateAcceptButton() {
|
||||
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
|
||||
/**
|
||||
* Selects the first server if no server is selected.
|
||||
*/
|
||||
private void selectDefaultServer() {
|
||||
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
|
||||
cboServers.getSelectionModel().select(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}.
|
||||
*/
|
||||
private Object getAuthAdditionalData() {
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) {
|
||||
return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
Object addtionalData;
|
||||
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
AccountFactory<?> factory;
|
||||
switch (type) {
|
||||
case 0:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 1:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 2:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY);
|
||||
Optional<AuthlibInjectorServer> server = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem());
|
||||
if (server.isPresent()) {
|
||||
addtionalData = server.get();
|
||||
} else {
|
||||
lblCreationWarning.setText(i18n("account.failed.no_selected_server"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
if (btnAccept.isDisabled())
|
||||
return;
|
||||
|
||||
acceptPane.showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
setDisable(true);
|
||||
|
||||
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy()))
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
Object additionalData = getAuthAdditionalData();
|
||||
|
||||
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, additionalData))
|
||||
.finalized(Schedulers.javafx(), variables -> {
|
||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||
|
||||
Account account = variables.get("create_account");
|
||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||
if (oldIndex == -1) {
|
||||
Accounts.getAccounts().add(account);
|
||||
} else {
|
||||
// adding an already-added account
|
||||
// instead of discarding the new account, we first remove the existing one then add the new one
|
||||
Accounts.getAccounts().remove(oldIndex);
|
||||
Accounts.getAccounts().add(oldIndex, account);
|
||||
}
|
||||
|
||||
// select the new account
|
||||
Accounts.setSelectedAccount(account);
|
||||
|
||||
acceptPane.hideSpinner();
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
setDisable(false);
|
||||
acceptPane.hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationCancel() {
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onManageInjecterServers() {
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
|
||||
private void showSelector(Node node) {
|
||||
getChildren().setAll(node);
|
||||
}
|
||||
|
||||
private void closeSelector() {
|
||||
getChildren().setAll(layout);
|
||||
}
|
||||
|
||||
private class Selector extends BorderPane implements CharacterSelector {
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXButton cancel = new JFXButton();
|
||||
@@ -212,7 +218,7 @@ public class AddAccountPane extends StackPane {
|
||||
for (GameProfile profile : names) {
|
||||
Image image;
|
||||
try {
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
|
||||
image = null;
|
||||
@@ -235,7 +241,7 @@ public class AddAccountPane extends StackPane {
|
||||
listBox.add(accountItem);
|
||||
}
|
||||
|
||||
JFXUtilities.runInFX(() -> showSelector(this));
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(this));
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
@@ -243,11 +249,11 @@ public class AddAccountPane extends StackPane {
|
||||
if (selectedProfile == null)
|
||||
throw new NoSelectedCharacterException(account);
|
||||
|
||||
JFXUtilities.runInFX(AddAccountPane.this::closeSelector);
|
||||
|
||||
return selectedProfile;
|
||||
} catch (InterruptedException ignore) {
|
||||
throw new NoSelectedCharacterException(account);
|
||||
} finally {
|
||||
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,6 +263,8 @@ public class AddAccountPane extends StackPane {
|
||||
return i18n("account.failed.no_character");
|
||||
} else if (exception instanceof ServerDisconnectException) {
|
||||
return i18n("account.failed.connect_authentication_server");
|
||||
} else if (exception instanceof ServerResponseMalformedException) {
|
||||
return i18n("account.failed.server_response_malformed");
|
||||
} else if (exception instanceof RemoteAuthenticationException) {
|
||||
RemoteAuthenticationException remoteException = (RemoteAuthenticationException) exception;
|
||||
String remoteMessage = remoteException.getRemoteMessage();
|
||||
@@ -270,14 +278,7 @@ public class AddAccountPane extends StackPane {
|
||||
}
|
||||
return exception.getMessage();
|
||||
} else {
|
||||
return exception.getClass() + ": " + exception.getLocalizedMessage();
|
||||
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
|
||||
else if (account instanceof AuthlibInjectorAccount) return i18n("account.methods.authlib_injector");
|
||||
else if (account instanceof YggdrasilAccount) return i18n("account.methods.yggdrasil");
|
||||
else throw new Error(i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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 static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerUrl;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblServerWarning;
|
||||
@FXML private JFXTextField txtServerUrl;
|
||||
@FXML private JFXDialogLayout addServerPane;
|
||||
@FXML private JFXDialogLayout confirmServerPane;
|
||||
@FXML private SpinnerPane nextPane;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private TransitionHandler transitionHandler;
|
||||
|
||||
private AuthlibInjectorServer serverBeingAdded;
|
||||
|
||||
public AddAuthlibInjectorServerPane(String url) {
|
||||
this();
|
||||
txtServerUrl.setText(url);
|
||||
onAddNext();
|
||||
}
|
||||
|
||||
public AddAuthlibInjectorServerPane() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
|
||||
btnAddNext.disableProperty().bind(
|
||||
Bindings.createBooleanBinding(txtServerUrl::validate, txtServerUrl.textProperty()).not());
|
||||
nextPane.hideSpinner();
|
||||
}
|
||||
|
||||
private String fixInputUrl(String url) {
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private String resolveFetchExceptionMessage(Throwable exception) {
|
||||
if (exception instanceof IOException) {
|
||||
return i18n("account.failed.connect_injector_server");
|
||||
} else {
|
||||
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddCancel() {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddNext() {
|
||||
if (btnAddNext.isDisabled())
|
||||
return;
|
||||
|
||||
lblCreationWarning.setText("");
|
||||
|
||||
String url = fixInputUrl(txtServerUrl.getText());
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else {
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
|
||||
config().getAuthlibInjectorServers().add(serverBeingAdded);
|
||||
}
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,134 +1,66 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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 static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.server"));
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.manage.title"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerUrl;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblServerWarning;
|
||||
@FXML private VBox listPane;
|
||||
@FXML private JFXTextField txtServerUrl;
|
||||
@FXML private JFXDialogLayout addServerPane;
|
||||
@FXML private JFXDialogLayout confirmServerPane;
|
||||
@FXML private JFXDialog dialog;
|
||||
@FXML private StackPane contentPane;
|
||||
@FXML private SpinnerPane nextPane;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private final TransitionHandler transitionHandler;
|
||||
private ObservableList<AuthlibInjectorServerItem> serverItems;
|
||||
|
||||
private AuthlibInjectorServer serverBeingAdded;
|
||||
public AuthlibInjectorServersPage() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
smoothScrolling(scrollPane);
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
|
||||
getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
txtServerUrl.textProperty().addListener((a, b, newValue) ->
|
||||
btnAddNext.setDisable(!txtServerUrl.validate()));
|
||||
|
||||
reload();
|
||||
serverItems = MappedObservableList.create(config().getAuthlibInjectorServers(), this::createServerItem);
|
||||
Bindings.bindContent(listPane.getChildren(), serverItems);
|
||||
}
|
||||
|
||||
private void removeServer(AuthlibInjectorServerItem item) {
|
||||
Settings.SETTINGS.authlibInjectorServers.remove(item.getServer());
|
||||
reload();
|
||||
}
|
||||
|
||||
private void reload() {
|
||||
listPane.getChildren().setAll(
|
||||
Settings.SETTINGS.authlibInjectorServers.stream()
|
||||
.map(server -> new AuthlibInjectorServerItem(server, this::removeServer))
|
||||
.collect(toList()));
|
||||
if (Settings.SETTINGS.authlibInjectorServers.isEmpty()) {
|
||||
onAdd();
|
||||
}
|
||||
private AuthlibInjectorServerItem createServerItem(AuthlibInjectorServer server) {
|
||||
return new AuthlibInjectorServerItem(server,
|
||||
item -> config().getAuthlibInjectorServers().remove(item.getServer()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
txtServerUrl.setText("");
|
||||
txtServerUrl.resetValidation();
|
||||
lblCreationWarning.setText("");
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddCancel() {
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddNext() {
|
||||
String url = fixInputUrl(txtServerUrl.getText());
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
nextPane.hideSpinner();
|
||||
addServerPane.setDisable(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else {
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) {
|
||||
Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded);
|
||||
}
|
||||
reload();
|
||||
dialog.close();
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
@@ -143,19 +75,4 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
private String fixInputUrl(String url) {
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private String resolveFetchExceptionMessage(Throwable exception) {
|
||||
if (exception instanceof IOException) {
|
||||
return i18n("account.failed.connect_injector_server");
|
||||
} else {
|
||||
return exception.getClass() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,16 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class Controllers {
|
||||
@@ -98,13 +102,13 @@ public final class Controllers {
|
||||
decorator.showPage(null);
|
||||
leftPaneController = new LeftPaneController(decorator.getLeftPane());
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
Settings.instance().onProfileLoading();
|
||||
Task.of(JavaVersion::initialize).start();
|
||||
|
||||
decorator.setCustomMaximize(false);
|
||||
|
||||
scene = new Scene(decorator, 804, 521);
|
||||
scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||
stage.setMinWidth(804);
|
||||
stage.setMaxWidth(804);
|
||||
stage.setMinHeight(521);
|
||||
@@ -132,15 +136,17 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, type, onAccept));
|
||||
dialog(new MessageDialogPane(text, title, type, onAccept));
|
||||
}
|
||||
|
||||
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, onAccept, onCancel));
|
||||
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public static void inputDialog(String text, Consumer<String> onResult) {
|
||||
dialog(new InputDialogPane(text, Controllers::closeDialog, onResult));
|
||||
public static InputDialogPane inputDialog(String text, FutureCallback<String> onResult) {
|
||||
InputDialogPane pane = new InputDialogPane(text, onResult);
|
||||
dialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
@@ -156,6 +162,9 @@ public final class Controllers {
|
||||
return pane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link DialogCloseEvent}
|
||||
*/
|
||||
public static void closeDialog(Region content) {
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
|
||||
@@ -32,6 +32,7 @@ import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.BoundingBox;
|
||||
import javafx.geometry.Bounds;
|
||||
@@ -43,6 +44,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -52,24 +54,34 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
@@ -81,6 +93,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
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 static final String PROPERTY_DIALOG_CLOSE_HANDLER = Decorator.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction;
|
||||
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
|
||||
|
||||
@@ -89,13 +103,14 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private final boolean max, min;
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
private final JFXDialog dialog;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging, maximized;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
private final TransitionHandler animationHandler;
|
||||
private final StackContainerPane dialogPane = new StackContainerPane();
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
|
||||
@FXML
|
||||
private StackPane contentPlaceHolder;
|
||||
@@ -174,12 +189,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
btnMax.fire();
|
||||
});
|
||||
|
||||
dialog = new JFXDialog();
|
||||
dialog.setOverlayClose(false);
|
||||
drawerWrapper.getChildren().add(0, dialog);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setContent(dialogPane);
|
||||
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
@@ -190,7 +199,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
);
|
||||
nowAnimation.play();
|
||||
});
|
||||
if (!Settings.INSTANCE.isFirstLaunch() || Settings.INSTANCE.getLocale().getLocale() != Locale.CHINA)
|
||||
if (!ConfigHolder.isNewlyCreated() || config().getLocalization().getLocale() != Locale.CHINA)
|
||||
drawerWrapper.getChildren().remove(welcomeView);
|
||||
|
||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
||||
@@ -208,79 +217,87 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
loadBackground();
|
||||
setupBackground();
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
private void loadBackground() {
|
||||
// ==== Background ====
|
||||
private void setupBackground() {
|
||||
drawerWrapper.backgroundProperty().bind(
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
Image image = null;
|
||||
if (config().getBackgroundImageType() == EnumBackgroundImage.CUSTOM && config().getBackgroundImage() != null) {
|
||||
image = tryLoadImage(Paths.get(config().getBackgroundImage()))
|
||||
.orElse(null);
|
||||
}
|
||||
if (image == null) {
|
||||
image = loadDefaultBackgroundImage();
|
||||
}
|
||||
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
|
||||
},
|
||||
config().backgroundImageTypeProperty(),
|
||||
config().backgroundImageProperty()));
|
||||
}
|
||||
|
||||
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
||||
|
||||
/**
|
||||
* Load background image from bg/, background.png, background.jpg
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.png"));
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.jpg"));
|
||||
}
|
||||
return image.orElse(defaultBackground);
|
||||
}
|
||||
|
||||
private Optional<Image> randomImageIn(Path imageDir) {
|
||||
if (!Files.isDirectory(imageDir)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
try {
|
||||
Image background;
|
||||
|
||||
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
|
||||
else
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
|
||||
|
||||
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
candidates = Files.list(imageDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(it -> {
|
||||
String filename = it.getFileName().toString();
|
||||
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
while (candidates.size() > 0) {
|
||||
int selected = rnd.nextInt(candidates.size());
|
||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded.isPresent()) {
|
||||
return Optional.of(loaded.get());
|
||||
} else {
|
||||
candidates.remove(selected);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Image searchBackgroundImage(Image def, String customPath) {
|
||||
Random random = new Random();
|
||||
boolean loaded = false;
|
||||
Image background = def;
|
||||
|
||||
// custom path
|
||||
if (StringUtils.isNotBlank(customPath)) {
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
background = new Image("file:" + customPath);
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// images in ./bg
|
||||
if (!loaded) {
|
||||
File backgroundImageFile = new File("bg");
|
||||
if (backgroundImageFile.isDirectory()) {
|
||||
File[] backgroundPath = backgroundImageFile.listFiles(file -> StringUtils.containsOne(FileUtils.getExtension(file), "png", "jpg"));
|
||||
if (backgroundPath != null && backgroundPath.length > 0) {
|
||||
int index = random.nextInt(backgroundPath.length);
|
||||
try {
|
||||
background = new Image("file:" + backgroundPath[index].getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// background.png
|
||||
if (!loaded) {
|
||||
File backgroundImageFile = new File("background.png");
|
||||
if (backgroundImageFile.exists()) {
|
||||
try {
|
||||
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// background.jpg
|
||||
if (!loaded) {
|
||||
File backgroundImageFile = new File("background.jpg");
|
||||
if (backgroundImageFile.exists()) {
|
||||
try {
|
||||
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return background;
|
||||
return Optional.empty();
|
||||
}
|
||||
// ====
|
||||
|
||||
@FXML
|
||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
||||
@@ -563,20 +580,37 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialogPane.isEmpty())
|
||||
if (dialog == null) {
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
dialogPane.push(node);
|
||||
|
||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
dialogPane.pop(node);
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
Platform.runLater(() -> {
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
@@ -670,4 +704,10 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
public AdvancedListBox getLeftPane() {
|
||||
return leftPane;
|
||||
}
|
||||
|
||||
private void setupAuthlibInjectorDnD() {
|
||||
addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
|
||||
addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
|
||||
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public final class DialogController {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
JFXUtilities.runInFX(() -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, Controllers::closeDialog, it -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
}, latch::countDown);
|
||||
|
||||
@@ -21,6 +21,12 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -45,24 +51,43 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.ClassTitle;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static javafx.collections.FXCollections.singletonObservableList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LeftPaneController {
|
||||
private final AdvancedListBox leftPane;
|
||||
private final VBox profilePane = new VBox();
|
||||
private final VBox accountPane = new VBox();
|
||||
private final IconedItem launcherSettingsItem;
|
||||
private final VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
private final HashMap<Account, VersionListItem> items = new HashMap<>();
|
||||
|
||||
private ListProperty<RipplerContainer> accountItems = new SimpleListProperty<>();
|
||||
private ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
accountItems.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account selected = get();
|
||||
accountItems.forEach(item -> item.setSelected(
|
||||
getAccountFromItem(item)
|
||||
.map(it -> it == selected)
|
||||
.orElse(false)));
|
||||
}
|
||||
};
|
||||
|
||||
public LeftPaneController(AdvancedListBox leftPane) {
|
||||
this.leftPane = leftPane;
|
||||
@@ -89,30 +114,91 @@ public final class LeftPaneController {
|
||||
})))
|
||||
.add(profilePane);
|
||||
|
||||
EventBus.EVENT_BUS.channel(AccountAddedEvent.class).register(this::onAccountAdd);
|
||||
EventBus.EVENT_BUS.channel(AccountLoadingEvent.class).register(this::onAccountsLoading);
|
||||
// ==== Accounts ====
|
||||
// Missing account item
|
||||
VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
RipplerContainer missingAccountRippler = new RipplerContainer(missingAccountItem);
|
||||
missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount());
|
||||
missingAccountRippler.setOnMouseClicked(e -> addNewAccount());
|
||||
|
||||
accountItems.bind(
|
||||
new When(Accounts.accountsProperty().emptyProperty())
|
||||
.then(singletonObservableList(missingAccountRippler))
|
||||
.otherwise(MappedObservableList.create(Accounts.getAccounts(), this::createAccountItem)));
|
||||
Bindings.bindContent(accountPane.getChildren(), accountItems);
|
||||
|
||||
selectedAccount.bindBidirectional(Accounts.selectedAccountProperty());
|
||||
// ====
|
||||
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(this::onRefreshedVersions);
|
||||
}
|
||||
|
||||
FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), this::onSelectedAccountChanged);
|
||||
onAccountsLoading();
|
||||
// ==== Accounts ====
|
||||
private Optional<Account> getAccountFromItem(RipplerContainer accountItem) {
|
||||
return Optional.ofNullable(accountItem.getProperties().get("account"))
|
||||
.map(Account.class::cast);
|
||||
}
|
||||
|
||||
private static String accountSubtitle(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return account.getUsername();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
private RipplerContainer createAccountItem(Account account) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountSubtitle(account));
|
||||
RipplerContainer rippler = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
JFXPopup popup = new JFXPopup(accountPage);
|
||||
accountPage.setOnDelete(popup::hide);
|
||||
popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
});
|
||||
rippler.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
selectedAccount.set(account);
|
||||
}
|
||||
});
|
||||
rippler.getProperties().put("account", account);
|
||||
rippler.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else {
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
}
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(rippler, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
// update skin
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
return rippler;
|
||||
}
|
||||
|
||||
public void checkAccount() {
|
||||
if (Accounts.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
}
|
||||
|
||||
private void addNewAccount() {
|
||||
Controllers.dialog(new AddAccountPane(Controllers::closeDialog));
|
||||
}
|
||||
|
||||
private void onSelectedAccountChanged(Account newAccount) {
|
||||
Platform.runLater(() -> {
|
||||
for (Node node : accountPane.getChildren()) {
|
||||
if (node instanceof RipplerContainer && node.getProperties().get("account") instanceof Account) {
|
||||
boolean current = Objects.equals(node.getProperties().get("account"), newAccount);
|
||||
((RipplerContainer) node).setSelected(current);
|
||||
}
|
||||
}
|
||||
});
|
||||
Controllers.dialog(new AddAccountPane());
|
||||
}
|
||||
// ====
|
||||
|
||||
private void onProfileChanged(ProfileChangedEvent event) {
|
||||
Profile profile = event.getProfile();
|
||||
@@ -130,11 +216,11 @@ public final class LeftPaneController {
|
||||
|
||||
private void onProfilesLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
for (Profile profile : Settings.INSTANCE.getProfiles()) {
|
||||
for (Profile profile : Settings.instance().getProfiles()) {
|
||||
VersionListItem item = new VersionListItem(Profiles.getProfileDisplayName(profile));
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.INSTANCE.setSelectedProfile(profile));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.instance().setSelectedProfile(profile));
|
||||
ripplerContainer.getProperties().put("profile", profile.getName());
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
list.add(ripplerContainer);
|
||||
@@ -142,70 +228,6 @@ public final class LeftPaneController {
|
||||
Platform.runLater(() -> profilePane.getChildren().setAll(list));
|
||||
}
|
||||
|
||||
private static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount) return account.getUsername();
|
||||
else throw new Error(i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
|
||||
private void onAccountAdd(AccountAddedEvent event) {
|
||||
Account account = event.getAccount();
|
||||
VersionListItem item = items.get(account);
|
||||
if (account instanceof YggdrasilAccount)
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
private void onAccountsLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
items.clear();
|
||||
Account selectedAccount = Settings.INSTANCE.getSelectedAccount();
|
||||
for (Account account : Settings.INSTANCE.getAccounts()) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountType(account));
|
||||
items.put(account, item);
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
JFXPopup popup = new JFXPopup(accountPage);
|
||||
accountPage.setOnDelete(popup::hide);
|
||||
popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
});
|
||||
ripplerContainer.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY)
|
||||
Settings.INSTANCE.setSelectedAccount(account);
|
||||
});
|
||||
ripplerContainer.getProperties().put("account", account);
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
if (selectedAccount == account)
|
||||
ripplerContainer.setSelected(true);
|
||||
|
||||
list.add(ripplerContainer);
|
||||
}
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty()) {
|
||||
RipplerContainer container = new RipplerContainer(missingAccountItem);
|
||||
missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount());
|
||||
container.setOnMouseClicked(e -> addNewAccount());
|
||||
list.add(container);
|
||||
}
|
||||
|
||||
Platform.runLater(() -> accountPane.getChildren().setAll(list));
|
||||
}
|
||||
|
||||
public void showUpdate() {
|
||||
launcherSettingsItem.setText(i18n("update.found"));
|
||||
launcherSettingsItem.setTextFill(Color.RED);
|
||||
@@ -228,7 +250,7 @@ public final class LeftPaneController {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), () -> {
|
||||
Controllers.closeDialog(region.get());
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
checkAccount();
|
||||
})).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.installing"), ""));
|
||||
@@ -246,9 +268,4 @@ public final class LeftPaneController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void checkAccount() {
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -64,7 +65,7 @@ public final class LogWindow extends Stage {
|
||||
|
||||
public LogWindow() {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
@@ -176,12 +177,12 @@ public final class LogWindow extends Stage {
|
||||
|
||||
engine = webView.getEngine();
|
||||
engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(getClass().getResourceAsStream("/assets/log-window-content.html")))
|
||||
.replace("${FONT}", Settings.INSTANCE.getFont().getSize() + "px \"" + Settings.INSTANCE.getFont().getFamily() + "\""));
|
||||
.replace("${FONT}", Settings.instance().getFont().getSize() + "px \"" + Settings.instance().getFont().getFamily() + "\""));
|
||||
engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
document = engine.getDocument();
|
||||
body = document.getElementsByTagName("body").item(0);
|
||||
engine.executeScript("limitedLogs=" + Settings.INSTANCE.getLogLines());
|
||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
||||
latch.countDown();
|
||||
onDone.fireEvent(new Event(LogWindow.this));
|
||||
}
|
||||
@@ -189,14 +190,14 @@ public final class LogWindow extends Stage {
|
||||
|
||||
boolean flag = false;
|
||||
for (String i : cboLines.getItems())
|
||||
if (Integer.toString(Settings.INSTANCE.getLogLines()).equals(i)) {
|
||||
if (Integer.toString(Settings.instance().getLogLines()).equals(i)) {
|
||||
cboLines.getSelectionModel().select(i);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
Settings.INSTANCE.setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + Settings.INSTANCE.getLogLines());
|
||||
Settings.instance().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
|
||||
@@ -37,16 +37,20 @@ import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -97,7 +101,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), i18n("install")));
|
||||
FXUtils.installTooltip(btnAdd, i18n("install"));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.instance().getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
|
||||
}
|
||||
|
||||
@@ -131,13 +135,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
item.setVersionName(id);
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null);
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
});
|
||||
item.setOnScriptButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
@@ -149,7 +153,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, file);
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, file);
|
||||
}
|
||||
});
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
@@ -165,17 +169,17 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), () -> Controllers.closeDialog(region.get()))).executor();
|
||||
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
@@ -213,10 +217,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null);
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
}
|
||||
});
|
||||
File iconFile = repository.getVersionIcon(id);
|
||||
@@ -232,6 +236,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -40,12 +41,11 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty location;
|
||||
private final Profile profile;
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProfileName;
|
||||
@FXML
|
||||
private FileItem gameDir;
|
||||
@FXML private JFXTextField txtProfileName;
|
||||
@FXML private FileItem gameDir;
|
||||
@FXML private JFXButton btnSave;
|
||||
@FXML private JFXButton btnDelete;
|
||||
@FXML private JFXCheckBox toggleUseRelativePath;
|
||||
|
||||
/**
|
||||
* @param profile null if creating a new profile.
|
||||
@@ -65,19 +65,22 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
gameDir.setProperty(location);
|
||||
gameDir.pathProperty().bindBidirectional(location);
|
||||
FXUtils.onChangeAndOperate(location, it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
|
||||
if (profile == null)
|
||||
gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty());
|
||||
if (profile == null) {
|
||||
btnDelete.setVisible(false);
|
||||
} else {
|
||||
toggleUseRelativePath.setSelected(profile.isUseRelativePath());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
if (profile != null) {
|
||||
Settings.INSTANCE.deleteProfile(profile);
|
||||
Settings.instance().deleteProfile(profile);
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
@@ -86,16 +89,20 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
private void onSave() {
|
||||
if (profile != null) {
|
||||
profile.setName(txtProfileName.getText());
|
||||
if (StringUtils.isNotBlank(getLocation()))
|
||||
profile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
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())));
|
||||
Profile newProfile = new Profile(txtProfileName.getText(), new File(getLocation()));
|
||||
newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
Settings.instance().putProfile(newProfile);
|
||||
}
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
Settings.instance().onProfileLoading();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -27,7 +31,6 @@ import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
@@ -36,7 +39,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.FontComboBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
@@ -44,12 +46,14 @@ import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("settings.launcher"));
|
||||
@@ -71,7 +75,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private FontComboBox cboFont;
|
||||
@FXML
|
||||
private FileItem fileCommonLocation;
|
||||
private MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
@FXML
|
||||
private Label lblDisplay;
|
||||
@FXML
|
||||
@@ -83,13 +87,11 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private ScrollPane scroll;
|
||||
@FXML
|
||||
private MultiFileItem backgroundItem;
|
||||
private MultiFileItem<EnumBackgroundImage> backgroundItem;
|
||||
@FXML
|
||||
private StackPane themeColorPickerContainer;
|
||||
@FXML
|
||||
private JFXRadioButton chkNoProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkManualProxy;
|
||||
private JFXCheckBox chkEnableProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxyHttp;
|
||||
@FXML
|
||||
@@ -101,128 +103,125 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private Pane proxyPane;
|
||||
|
||||
{
|
||||
private ObjectProperty<Proxy.Type> selectedProxyType;
|
||||
|
||||
public SettingsPage() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/setting.fxml");
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
txtProxyHost.setText(Settings.INSTANCE.getProxyHost());
|
||||
txtProxyHost.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyHost(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())));
|
||||
|
||||
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.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);
|
||||
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.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);
|
||||
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() + "\";");
|
||||
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())));
|
||||
list.add(new Label(locale.getName(config().getLocalization().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())));
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(config().getLocalization()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> config().setLocalization(Locales.getLocale(newValue.intValue())));
|
||||
|
||||
// ==== Proxy ====
|
||||
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
||||
txtProxyPort.textProperty().bindBidirectional(config().proxyPortProperty());
|
||||
txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty());
|
||||
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
|
||||
|
||||
proxyPane.disableProperty().bind(chkEnableProxy.selectedProperty().not());
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
chkEnableProxy.selectedProperty().bindBidirectional(config().hasProxyProperty());
|
||||
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
|
||||
|
||||
selectedProxyType = new SimpleObjectProperty<Proxy.Type>(Proxy.Type.HTTP) {
|
||||
{
|
||||
invalidated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Proxy.Type type = Objects.requireNonNull(get());
|
||||
if (type == Proxy.Type.DIRECT) {
|
||||
set(Proxy.Type.HTTP); // HTTP by default
|
||||
} else {
|
||||
chkProxyHttp.setSelected(type == Proxy.Type.HTTP);
|
||||
chkProxySocks.setSelected(type == Proxy.Type.SOCKS);
|
||||
}
|
||||
}
|
||||
};
|
||||
selectedProxyType.bindBidirectional(config().proxyTypeProperty());
|
||||
|
||||
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
|
||||
chkProxyHttp.setUserData(Proxy.Type.HTTP);
|
||||
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
|
||||
chkProxySocks.setUserData(Proxy.Type.SOCKS);
|
||||
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
|
||||
proxyConfigurationGroup.getToggles().forEach(
|
||||
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
selectedProxyType.set((Proxy.Type) toggle.getUserData());
|
||||
}
|
||||
}));
|
||||
// ====
|
||||
|
||||
for (Toggle toggle : proxyConfigurationGroup.getToggles())
|
||||
if (toggle.getUserData() == Settings.INSTANCE.getProxyType())
|
||||
toggle.setSelected(true);
|
||||
|
||||
ToggleGroup hasProxyGroup = new ToggleGroup();
|
||||
chkNoProxy.setToggleGroup(hasProxyGroup);
|
||||
chkManualProxy.setToggleGroup(hasProxyGroup);
|
||||
if (!Settings.INSTANCE.hasProxy())
|
||||
chkNoProxy.setSelected(true);
|
||||
else
|
||||
chkManualProxy.setSelected(true);
|
||||
proxyPane.disableProperty().bind(chkNoProxy.selectedProperty());
|
||||
|
||||
hasProxyGroup.selectedToggleProperty().addListener((a, b, newValue) ->
|
||||
Settings.INSTANCE.setHasProxy(newValue != chkNoProxy));
|
||||
|
||||
proxyConfigurationGroup.selectedToggleProperty().addListener((a, b, newValue) ->
|
||||
Settings.INSTANCE.setProxyType((Proxy.Type) newValue.getUserData()));
|
||||
|
||||
chkProxyAuthentication.setSelected(Settings.INSTANCE.hasProxyAuth());
|
||||
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
fileCommonLocation.createChildren(i18n("launcher.common_directory.disabled"), EnumCommonDirectory.DISABLED),
|
||||
fileCommonLocation.createChildren(i18n("launcher.common_directory.default"), EnumCommonDirectory.DEFAULT)
|
||||
), EnumCommonDirectory.CUSTOM);
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
fileCommonLocation.customTextProperty().bindBidirectional(config().commonDirectoryProperty());
|
||||
fileCommonLocation.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.common_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
checkUpdate();
|
||||
|
||||
// background
|
||||
// ==== Background ====
|
||||
backgroundItem.loadChildren(Collections.singletonList(
|
||||
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
));
|
||||
), EnumBackgroundImage.CUSTOM);
|
||||
backgroundItem.customTextProperty().bindBidirectional(config().backgroundImageProperty());
|
||||
backgroundItem.selectedDataProperty().bindBidirectional(config().backgroundImageTypeProperty());
|
||||
backgroundItem.subtitleProperty().bind(
|
||||
new When(backgroundItem.selectedDataProperty().isEqualTo(EnumBackgroundImage.DEFAULT))
|
||||
.then(i18n("launcher.background.default"))
|
||||
.otherwise(config().backgroundImageProperty()));
|
||||
// ====
|
||||
|
||||
FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty());
|
||||
|
||||
backgroundItem.setCustomUserData(EnumBackgroundImage.CUSTOM);
|
||||
backgroundItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Settings.INSTANCE.getBackgroundImageType()).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
|
||||
Settings.INSTANCE.backgroundImageProperty().setChangedListener(it -> initBackgroundItemSubtitle());
|
||||
Settings.INSTANCE.backgroundImageTypeProperty().setChangedListener(it -> initBackgroundItemSubtitle());
|
||||
initBackgroundItemSubtitle();
|
||||
|
||||
backgroundItem.setToggleSelectedListener(newValue ->
|
||||
Settings.INSTANCE.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData()));
|
||||
|
||||
// theme
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null);
|
||||
// ==== Theme ====
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(config().getTheme().getColor()), null);
|
||||
picker.setCustomColorText(i18n("color.custom"));
|
||||
picker.setRecentColorsText(i18n("color.recent"));
|
||||
picker.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList()));
|
||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
Settings.INSTANCE.setTheme(theme);
|
||||
config().setTheme(theme);
|
||||
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets());
|
||||
});
|
||||
themeColorPickerContainer.getChildren().setAll(picker);
|
||||
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
||||
}
|
||||
|
||||
private void initBackgroundItemSubtitle() {
|
||||
switch (Settings.INSTANCE.getBackgroundImageType()) {
|
||||
case DEFAULT:
|
||||
backgroundItem.setSubtitle(i18n("launcher.background.default"));
|
||||
break;
|
||||
case CUSTOM:
|
||||
backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage());
|
||||
break;
|
||||
}
|
||||
// ====
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.beans.property.SimpleStringProperty;
|
||||
|
||||
@@ -71,7 +71,7 @@ public final class VersionItem extends StackPane {
|
||||
private void initializeComponents() {
|
||||
setPickOnBounds(false);
|
||||
FXUtils.setLimitWidth(this, 160);
|
||||
FXUtils.setLimitHeight(this, 156);
|
||||
FXUtils.setLimitHeight(this, 149);
|
||||
|
||||
content = new VBox();
|
||||
{
|
||||
|
||||
@@ -195,12 +195,15 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void renameVersion(Profile profile, String version) {
|
||||
Controllers.inputDialog(i18n("version.manage.rename.message"), res -> {
|
||||
Controllers.inputDialog(i18n("version.manage.rename.message"), (res, resolve, reject) -> {
|
||||
if (profile.getRepository().renameVersion(version, res)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
resolve.run();
|
||||
} else {
|
||||
reject.accept(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
});
|
||||
}).setInitialText(version);
|
||||
}
|
||||
|
||||
public static void exportVersion(Profile profile, String version) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.controls.JFXToggleButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
@@ -43,9 +44,9 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -53,6 +54,7 @@ public final class VersionSettingsController {
|
||||
private VersionSetting lastVersionSetting = null;
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private boolean javaItemsLoaded;
|
||||
|
||||
@FXML private VBox rootPane;
|
||||
@FXML private ScrollPane scroll;
|
||||
@@ -70,11 +72,10 @@ public final class VersionSettingsController {
|
||||
@FXML private JFXCheckBox chkFullscreen;
|
||||
@FXML private Label lblPhysicalMemory;
|
||||
@FXML private JFXToggleButton chkNoJVMArgs;
|
||||
@FXML private JFXToggleButton chkNoCommon;
|
||||
@FXML private JFXToggleButton chkNoGameCheck;
|
||||
@FXML private MultiFileItem globalItem;
|
||||
@FXML private MultiFileItem javaItem;
|
||||
@FXML private MultiFileItem gameDirItem;
|
||||
@FXML private MultiFileItem<Boolean> globalItem;
|
||||
@FXML private MultiFileItem<JavaVersion> javaItem;
|
||||
@FXML private MultiFileItem<EnumGameDirectory> gameDirItem;
|
||||
@FXML private JFXToggleButton chkShowLogs;
|
||||
@FXML private ImagePickerItem iconPickerItem;
|
||||
|
||||
@@ -90,12 +91,16 @@ public final class VersionSettingsController {
|
||||
(variables.<List<JavaVersion>>get("list")).stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
javaItemsLoaded = true;
|
||||
initializeSelectedJava();
|
||||
});
|
||||
|
||||
javaItem.setSelectedData(null);
|
||||
javaItem.setFallbackData(JavaVersion.fromCurrentEnvironment());
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe"));
|
||||
|
||||
gameDirItem.setCustomUserData(EnumGameDirectory.CUSTOM);
|
||||
gameDirItem.loadChildren(Arrays.asList(
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
|
||||
@@ -116,6 +121,7 @@ public final class VersionSettingsController {
|
||||
gameDirItem.setDisable(profile.getRepository().isModpack(versionId));
|
||||
globalItem.setDisable(profile.getRepository().isModpack(versionId));
|
||||
|
||||
// unbind data fields
|
||||
if (lastVersionSetting != null) {
|
||||
FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty());
|
||||
FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty());
|
||||
@@ -130,11 +136,21 @@ public final class VersionSettingsController {
|
||||
FXUtils.unbindString(txtServerIP, lastVersionSetting.serverIpProperty());
|
||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||
FXUtils.unbindBoolean(chkNoGameCheck, lastVersionSetting.notCheckGameProperty());
|
||||
FXUtils.unbindBoolean(chkNoCommon, lastVersionSetting.noCommonProperty());
|
||||
FXUtils.unbindBoolean(chkNoJVMArgs, lastVersionSetting.noJVMArgsProperty());
|
||||
FXUtils.unbindBoolean(chkShowLogs, lastVersionSetting.showLogsProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
|
||||
globalItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.usesGlobalProperty());
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().unbind();
|
||||
}
|
||||
|
||||
// unbind data fields
|
||||
globalItem.setToggleSelectedListener(null);
|
||||
javaItem.setToggleSelectedListener(null);
|
||||
|
||||
// bind new data fields
|
||||
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
|
||||
FXUtils.bindInt(txtHeight, versionSetting.heightProperty());
|
||||
FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty());
|
||||
@@ -148,18 +164,15 @@ public final class VersionSettingsController {
|
||||
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
|
||||
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
|
||||
FXUtils.bindBoolean(chkNoGameCheck, versionSetting.notCheckGameProperty());
|
||||
FXUtils.bindBoolean(chkNoCommon, versionSetting.noCommonProperty());
|
||||
FXUtils.bindBoolean(chkNoJVMArgs, versionSetting.noJVMArgsProperty());
|
||||
FXUtils.bindBoolean(chkShowLogs, versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
|
||||
javaItem.setToggleSelectedListener(newValue -> {
|
||||
if (javaItem.isCustomToggle(newValue)) {
|
||||
versionSetting.setJava("Custom");
|
||||
versionSetting.setDefaultJavaPath(null);
|
||||
versionSetting.setUsesCustomJavaDir();
|
||||
} else {
|
||||
JavaVersion java = (JavaVersion) newValue.getUserData();
|
||||
versionSetting.setJava(java.getVersion());
|
||||
versionSetting.setDefaultJavaPath(java.getBinary().toString());
|
||||
versionSetting.setJavaVersion((JavaVersion) newValue.getUserData());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -167,13 +180,13 @@ public final class VersionSettingsController {
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
|
||||
if (versionSetting.isUsesGlobal())
|
||||
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.TRUE).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
else
|
||||
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.FALSE).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
globalItem.selectedDataProperty().bindBidirectional(versionSetting.usesGlobalProperty());
|
||||
globalItem.subtitleProperty().bind(Bindings.createStringBinding(() -> i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special"),
|
||||
versionSetting.usesGlobalProperty()));
|
||||
globalItem.setToggleSelectedListener(newValue -> {
|
||||
// do not call versionSettings.setUsesGlobal(true/false)
|
||||
// because versionSettings can be the global one.
|
||||
// global versionSettings.usesGlobal is always true.
|
||||
if ((Boolean) newValue.getUserData())
|
||||
profile.globalizeVersionSetting(versionId);
|
||||
else
|
||||
@@ -182,19 +195,9 @@ public final class VersionSettingsController {
|
||||
Platform.runLater(() -> loadVersionSetting(profile, versionId));
|
||||
});
|
||||
|
||||
versionSetting.usesGlobalProperty().setChangedListenerAndOperate(it ->
|
||||
globalItem.setSubtitle(i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
|
||||
|
||||
gameDirItem.getGroup().getToggles().stream()
|
||||
.filter(it -> it.getUserData() == versionSetting.getGameDirType())
|
||||
.findFirst().ifPresent(toggle -> toggle.setSelected(true));
|
||||
|
||||
gameDirItem.setCustomUserData(EnumGameDirectory.CUSTOM);
|
||||
gameDirItem.setToggleSelectedListener(newValue -> versionSetting.setGameDirType((EnumGameDirectory) newValue.getUserData()));
|
||||
|
||||
versionSetting.gameDirProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
|
||||
versionSetting.gameDirTypeProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
|
||||
initGameDirSubtitle(versionSetting);
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(),
|
||||
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
@@ -205,24 +208,19 @@ public final class VersionSettingsController {
|
||||
|
||||
private void initializeSelectedJava() {
|
||||
if (lastVersionSetting == null
|
||||
|| !JavaVersion.getJREsImmediately().isPresent() /* JREs are still being loaded */) {
|
||||
|| !javaItemsLoaded /* JREs are still being loaded */) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean flag = false;
|
||||
Toggle defaultToggle = null;
|
||||
for (Toggle toggle : javaItem.getGroup().getToggles()) {
|
||||
if (toggle.getUserData() == Lang.invoke(lastVersionSetting::getJavaVersion)) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle;
|
||||
if (lastVersionSetting.isUsesCustomJavaDir()) {
|
||||
javaItem.setSelectedData(null);
|
||||
} else {
|
||||
try {
|
||||
javaItem.setSelectedData(lastVersionSetting.getJavaVersion());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
}
|
||||
}
|
||||
|
||||
private void initJavaSubtitle(VersionSetting versionSetting) {
|
||||
@@ -232,10 +230,6 @@ public final class VersionSettingsController {
|
||||
.map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory"))));
|
||||
}
|
||||
|
||||
private void initGameDirSubtitle(VersionSetting versionSetting) {
|
||||
gameDirItem.setSubtitle(profile.getRepository().getRunDirectory(versionId).getAbsolutePath());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onExploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
|
||||
@@ -21,14 +21,15 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class WebStage extends Stage {
|
||||
private final WebView webView = new WebView();
|
||||
|
||||
public WebStage() {
|
||||
setScene(new Scene(webView, 800, 480));
|
||||
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AnimationProducer {
|
||||
void init(AnimationHandler handler);
|
||||
|
||||
List<KeyFrame> animate(AnimationHandler handler);
|
||||
}
|
||||
@@ -20,17 +20,43 @@ package org.jackhuang.hmcl.ui.animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.scene.Node;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public enum ContainerAnimations {
|
||||
NONE(c -> Collections.emptyList()),
|
||||
NONE(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c -> Collections.emptyList()),
|
||||
/**
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
FADE(c ->
|
||||
FADE(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
@@ -40,7 +66,12 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_IN(c ->
|
||||
ZOOM_IN(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
@@ -52,7 +83,12 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_OUT(c ->
|
||||
ZOOM_OUT(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
(Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
@@ -64,7 +100,14 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_LEFT(c ->
|
||||
SWIPE_LEFT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
@@ -75,7 +118,14 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_RIGHT(c ->
|
||||
SWIPE_RIGHT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
@@ -85,8 +135,18 @@ public enum ContainerAnimations {
|
||||
|
||||
private final AnimationProducer animationProducer;
|
||||
|
||||
ContainerAnimations(AnimationProducer animationProducer) {
|
||||
this.animationProducer = animationProducer;
|
||||
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
|
||||
this.animationProducer = new AnimationProducer() {
|
||||
@Override
|
||||
public void init(AnimationHandler handler) {
|
||||
init.accept(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler handler) {
|
||||
return animationProducer.apply(handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public AnimationProducer getAnimationProducer() {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -78,35 +79,38 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
|
||||
updateContent(newView);
|
||||
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
previousNode.setMouseTransparent((Boolean) previousNode.getProperties().get(MOUSE_TRANSPARENT));
|
||||
view.getChildren().remove(previousNode);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
transition.init(this);
|
||||
|
||||
// runLater or "init" will not work
|
||||
Platform.runLater(() -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
view.setMouseTransparent(false);
|
||||
view.getChildren().remove(previousNode);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateContent(Node newView) {
|
||||
if (view.getWidth() > 0 && view.getHeight() > 0) {
|
||||
previousNode = currentNode;
|
||||
if (previousNode == null)
|
||||
previousNode = NULL;
|
||||
previousNode = EMPTY_PANE;
|
||||
} else
|
||||
previousNode = NULL;
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
if (previousNode == newView)
|
||||
previousNode = NULL;
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
previousNode.getProperties().put(MOUSE_TRANSPARENT, previousNode.isMouseTransparent());
|
||||
previousNode.setMouseTransparent(true);
|
||||
view.setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
view.getChildren().setAll(previousNode, currentNode);
|
||||
}
|
||||
|
||||
private static final String MOUSE_TRANSPARENT = "TransitionHandler.MOUSE_TRANSPARENT";
|
||||
private static final StackPane NULL = new StackPane();
|
||||
private final StackPane EMPTY_PANE = new StackPane();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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 org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventTarget;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
/**
|
||||
* Indicates a close operation on the dialog.
|
||||
*
|
||||
* @author yushijinhun
|
||||
* @see Controllers#dialog(Region)
|
||||
*/
|
||||
public class DialogCloseEvent extends Event {
|
||||
|
||||
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("CLOSE");
|
||||
|
||||
public DialogCloseEvent() {
|
||||
super(CLOSE);
|
||||
}
|
||||
|
||||
public DialogCloseEvent(Object source, EventTarget target) {
|
||||
super(source, target, CLOSE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.Property;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -31,24 +33,30 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class FileItem extends BorderPane {
|
||||
private Property<String> property;
|
||||
private final Label x = new Label();
|
||||
private final Label lblPath = new Label();
|
||||
|
||||
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
|
||||
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private final SimpleStringProperty path = new SimpleStringProperty(this, "path");
|
||||
private final SimpleBooleanProperty convertToRelativePath = new SimpleBooleanProperty(this, "convertToRelativePath");
|
||||
|
||||
public FileItem() {
|
||||
VBox left = new VBox();
|
||||
Label name = new Label();
|
||||
name.textProperty().bind(nameProperty());
|
||||
x.getStyleClass().addAll("subtitle-label");
|
||||
left.getChildren().addAll(name, x);
|
||||
lblPath.getStyleClass().addAll("subtitle-label");
|
||||
lblPath.textProperty().bind(path);
|
||||
left.getChildren().addAll(name, lblPath);
|
||||
setLeft(left);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
@@ -61,12 +69,29 @@ public class FileItem extends BorderPane {
|
||||
Tooltip tip = new Tooltip();
|
||||
tip.textProperty().bind(tooltipProperty());
|
||||
Tooltip.install(this, tip);
|
||||
|
||||
convertToRelativePath.addListener(onInvalidating(() -> path.set(processPath(path.get()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given path to absolute/relative(if possible) path according to {@link #convertToRelativePathProperty()}.
|
||||
*/
|
||||
private String processPath(String path) {
|
||||
Path given = Paths.get(path).toAbsolutePath();
|
||||
if (isConvertToRelativePath()) {
|
||||
try {
|
||||
return Paths.get(".").normalize().toAbsolutePath().relativize(given).normalize().toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// the given path can't be relativized against current path
|
||||
}
|
||||
}
|
||||
return given.normalize().toString();
|
||||
}
|
||||
|
||||
public void onExplore() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
if (property.getValue() != null) {
|
||||
File file = new File(property.getValue());
|
||||
if (path.get() != null) {
|
||||
File file = new File(path.get());
|
||||
if (file.exists()) {
|
||||
if (file.isFile())
|
||||
file = file.getAbsoluteFile().getParentFile();
|
||||
@@ -77,16 +102,12 @@ public class FileItem extends BorderPane {
|
||||
}
|
||||
chooser.titleProperty().bind(titleProperty());
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
property.setValue(selectedDir.getAbsolutePath());
|
||||
if (selectedDir != null) {
|
||||
path.set(processPath(selectedDir.toString()));
|
||||
}
|
||||
chooser.titleProperty().unbind();
|
||||
}
|
||||
|
||||
public void setProperty(Property<String> property) {
|
||||
this.property = property;
|
||||
x.textProperty().bind(property);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
@@ -122,4 +143,28 @@ public class FileItem extends BorderPane {
|
||||
public void setTooltip(String tooltip) {
|
||||
this.tooltip.set(tooltip);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path.get();
|
||||
}
|
||||
|
||||
public StringProperty pathProperty() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path.set(path);
|
||||
}
|
||||
|
||||
public boolean isConvertToRelativePath() {
|
||||
return convertToRelativePath.get();
|
||||
}
|
||||
|
||||
public BooleanProperty convertToRelativePathProperty() {
|
||||
return convertToRelativePath;
|
||||
}
|
||||
|
||||
public void setConvertToRelativePath(boolean convertToRelativePath) {
|
||||
this.convertToRelativePath.set(convertToRelativePath);
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,12 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
public class InputDialogPane extends StackPane {
|
||||
|
||||
@@ -37,14 +36,28 @@ public class InputDialogPane extends StackPane {
|
||||
private JFXTextField textField;
|
||||
@FXML
|
||||
private Label content;
|
||||
@FXML
|
||||
private Label lblCreationWarning;
|
||||
|
||||
public InputDialogPane(String text, Consumer<Region> closeConsumer, Consumer<String> onResult) {
|
||||
public InputDialogPane(String text, FutureCallback<String> onResult) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||
content.setText(text);
|
||||
cancelButton.setOnMouseClicked(e -> closeConsumer.accept(this));
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
onResult.accept(textField.getText());
|
||||
closeConsumer.accept(this);
|
||||
onResult.call(textField.getText(), () -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, msg -> {
|
||||
lblCreationWarning.setText(msg);
|
||||
});
|
||||
});
|
||||
|
||||
acceptButton.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !textField.validate(),
|
||||
textField.textProperty()
|
||||
));
|
||||
}
|
||||
|
||||
public void setInitialText(String text) {
|
||||
textField.setText(text);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -30,10 +29,8 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
private boolean closingDialog = true;
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@@ -48,7 +45,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
@FXML
|
||||
private HBox actions;
|
||||
|
||||
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, int type, Runnable onAccept) {
|
||||
public MessageDialogPane(String text, String title, int type, Runnable onAccept) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
if (title != null)
|
||||
@@ -56,7 +53,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
|
||||
content.setText(text);
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
closeConsumer.accept(MessageDialogPane.this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
@@ -83,12 +80,12 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, closeConsumer, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
|
||||
cancelButton.setVisible(true);
|
||||
cancelButton.setOnMouseClicked(e -> {
|
||||
closeConsumer.accept(MessageDialogPane.this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
@@ -97,8 +94,4 @@ public final class MessageDialogPane extends StackPane {
|
||||
|
||||
actions.getChildren().add(cancelButton);
|
||||
}
|
||||
|
||||
public void disableClosingDialog() {
|
||||
closingDialog = false;
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -46,12 +43,15 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MultiFileItem extends ComponentList {
|
||||
public class MultiFileItem<T> extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
|
||||
private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false);
|
||||
private final ObjectProperty<T> selectedData = new SimpleObjectProperty<>(this, "selectedData");
|
||||
private final ObjectProperty<T> fallbackData = new SimpleObjectProperty<>(this, "fallbackData");
|
||||
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
@@ -64,6 +64,7 @@ public class MultiFileItem extends ComponentList {
|
||||
|
||||
private Consumer<Toggle> toggleSelectedListener;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public MultiFileItem(@NamedArg(value = "hasCustom", defaultValue = "true") boolean hasCustom) {
|
||||
this.hasCustom = hasCustom;
|
||||
|
||||
@@ -110,6 +111,20 @@ public class MultiFileItem extends ComponentList {
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
if (toggleSelectedListener != null)
|
||||
toggleSelectedListener.accept(newValue);
|
||||
|
||||
selectedData.set((T) newValue.getUserData());
|
||||
});
|
||||
selectedData.addListener((a, b, newValue) -> {
|
||||
Optional<Toggle> selecting = group.getToggles().stream()
|
||||
.filter(it -> it.getUserData() == newValue)
|
||||
.findFirst();
|
||||
if (!selecting.isPresent()) {
|
||||
selecting = group.getToggles().stream()
|
||||
.filter(it -> it.getUserData() == getFallbackData())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
selecting.ifPresent(toggle -> toggle.setSelected(true));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,11 +132,11 @@ public class MultiFileItem extends ComponentList {
|
||||
return createChildren(title, null);
|
||||
}
|
||||
|
||||
public Node createChildren(String title, Object userData) {
|
||||
public Node createChildren(String title, T userData) {
|
||||
return createChildren(title, "", userData);
|
||||
}
|
||||
|
||||
public Node createChildren(String title, String subtitle, Object userData) {
|
||||
public Node createChildren(String title, String subtitle, T userData) {
|
||||
BorderPane pane = new BorderPane();
|
||||
pane.setStyle("-fx-padding: 3;");
|
||||
FXUtils.setLimitHeight(pane, 20);
|
||||
@@ -146,12 +161,9 @@ public class MultiFileItem extends ComponentList {
|
||||
pane.getChildren().add(custom);
|
||||
}
|
||||
|
||||
public void onExploreJavaDir() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(i18n(getChooserTitle()));
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
txtCustom.setText(selectedDir.getAbsolutePath());
|
||||
public void loadChildren(Collection<Node> list, T customUserData) {
|
||||
loadChildren(list);
|
||||
setCustomUserData(customUserData);
|
||||
}
|
||||
|
||||
public ToggleGroup getGroup() {
|
||||
@@ -182,7 +194,7 @@ public class MultiFileItem extends ComponentList {
|
||||
this.chooserTitle.set(chooserTitle);
|
||||
}
|
||||
|
||||
public void setCustomUserData(Object userData) {
|
||||
public void setCustomUserData(T userData) {
|
||||
radioCustom.setUserData(userData);
|
||||
}
|
||||
|
||||
@@ -225,4 +237,28 @@ public class MultiFileItem extends ComponentList {
|
||||
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
|
||||
return extensionFilters;
|
||||
}
|
||||
|
||||
public T getSelectedData() {
|
||||
return selectedData.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<T> selectedDataProperty() {
|
||||
return selectedData;
|
||||
}
|
||||
|
||||
public void setSelectedData(T selectedData) {
|
||||
this.selectedData.set(selectedData);
|
||||
}
|
||||
|
||||
public T getFallbackData() {
|
||||
return fallbackData.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<T> fallbackDataProperty() {
|
||||
return fallbackData;
|
||||
}
|
||||
|
||||
public void setFallbackData(T fallbackData) {
|
||||
this.fallbackData.set(fallbackData);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -35,7 +35,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
|
||||
Controllers.closeDialog(it);
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(null);
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
Controllers.closeDialog(pane);
|
||||
pane.fireEvent(new DialogCloseEvent());
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
profile = Settings.INSTANCE.getSelectedProfile();
|
||||
profile = Settings.instance().getSelectedProfile();
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.download;
|
||||
import javafx.scene.Node;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -100,9 +101,10 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new AdditionalInstallersPage(this, controller, profile.getRepository(), BMCLAPIDownloadProvider.INSTANCE);
|
||||
return new AdditionalInstallersPage(this, controller, profile.getRepository(), provider);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
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;
|
||||
@@ -37,6 +37,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public final class ExportWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
@@ -80,20 +82,13 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
boolean flag = true;
|
||||
|
||||
try (ZipEngine zip = new ZipEngine(modpackFile)) {
|
||||
Config config = Settings.INSTANCE.getRawConfig();
|
||||
|
||||
config.hasProxy.set(false);
|
||||
config.selectedProfile.set("");
|
||||
config.commonDirectory.set(null);
|
||||
config.fontFamily.set("Consolas");
|
||||
config.fontSize.set(12);
|
||||
config.localization.set(null);
|
||||
config.accounts.clear();
|
||||
config.selectedAccount.set("");
|
||||
config.logLines.set(100);
|
||||
config.configurations.clear();
|
||||
|
||||
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
|
||||
Config exported = new Config();
|
||||
exported.setBackgroundImageType(config().getBackgroundImageType());
|
||||
exported.setBackgroundImage(config().getBackgroundImage());
|
||||
exported.setTheme(config().getTheme());
|
||||
exported.setDownloadType(config().getDownloadType());
|
||||
exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());
|
||||
zip.putTextFile(exported.toJson(), ConfigHolder.CONFIG_FILENAME);
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
|
||||
@@ -28,7 +28,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -67,7 +67,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
txtModpackName.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackVersion.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.setText(Optional.ofNullable(Settings.INSTANCE.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
txtModpackAuthor.setText(Optional.ofNullable(Accounts.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
lblVersionName.setText(version);
|
||||
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -120,7 +121,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
@@ -138,7 +139,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
@@ -184,7 +185,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, new IntegrityCheck("SHA-1", hash)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,7 +235,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, new IntegrityCheck("SHA-1", hash)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
import java.io.File;
|
||||
@@ -72,7 +73,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
|
||||
private static String getRealPath() {
|
||||
|
||||
@@ -51,8 +51,6 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
put("netscape.javascript.JSException", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.IncompatibleClassChangeError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.ClassFormatError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!");
|
||||
put("Trampoline", i18n("launcher.update_java"));
|
||||
put("com.sun.javafx.css.StyleManager.findMatchingStyles", i18n("launcher.update_java"));
|
||||
put("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?");
|
||||
}
|
||||
|
||||
@@ -23,24 +23,25 @@ import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
public final class I18n {
|
||||
|
||||
private I18n() {}
|
||||
|
||||
private static ResourceBundle RESOURCE_BUNDLE = null;
|
||||
|
||||
static {
|
||||
private static SupportedLocale getCurrentLocale() {
|
||||
try {
|
||||
RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Settings cannot be initialized", e);
|
||||
return ConfigHolder.config().getLocalization();
|
||||
} catch (IllegalStateException e) {
|
||||
// e is thrown by ConfigHolder.config(), indicating the config hasn't been loaded
|
||||
// fallback to use default locale
|
||||
return Locales.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceBundle getResourceBundle() {
|
||||
return RESOURCE_BUNDLE == null ? Locales.DEFAULT.getResourceBundle() : RESOURCE_BUNDLE;
|
||||
return getCurrentLocale().getResourceBundle();
|
||||
}
|
||||
|
||||
public static String i18n(String key, Object... formatArgs) {
|
||||
|
||||
@@ -19,6 +19,10 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -107,5 +111,17 @@ public final class Locales {
|
||||
if (name == null) return resourceBundle.getString("lang");
|
||||
else return newResourceBundle.getString(name);
|
||||
}
|
||||
|
||||
public static class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
||||
@Override
|
||||
public void write(JsonWriter out, SupportedLocale value) throws IOException {
|
||||
out.value(getNameByLocale(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportedLocale read(JsonReader in) throws IOException {
|
||||
return getLocaleByName(in.nextString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #283593;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: white;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #43A047;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: black;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #E67E22;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: black;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #9C27B0;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: white;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #F44336;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: black;
|
||||
}
|
||||
@@ -569,7 +569,6 @@
|
||||
.list-cell:selected, .jfx-list-cell:selected {
|
||||
-fx-background-insets: 0.0;
|
||||
-fx-text-fill: BLACK;
|
||||
-fx-background-color: black;
|
||||
}
|
||||
|
||||
.jfx-list-cell:filled:hover,
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<Label fx:id="lblInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
|
||||
GridPane.columnIndex="0" GridPane.rowIndex="1"/>
|
||||
|
||||
<JFXComboBox fx:id="cboServers" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
<JFXComboBox fx:id="cboServers" promptText="%general.prompt.empty" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
|
||||
<Hyperlink fx:id="linkManageInjectorServers" text="%account.injector.manage"
|
||||
onMouseClicked="#onManageInjecterServers" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
|
||||
@@ -37,7 +37,7 @@
|
||||
<Label text="%account.username" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
|
||||
|
||||
<JFXTextField fx:id="txtUsername" GridPane.columnIndex="1" GridPane.rowIndex="2" GridPane.columnSpan="2"
|
||||
FXUtils.validateWhileTextChanged="true">
|
||||
FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
@@ -47,7 +47,7 @@
|
||||
<Label fx:id="lblPassword" text="%account.password" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
|
||||
|
||||
<JFXPasswordField fx:id="txtPassword" GridPane.columnIndex="1" GridPane.rowIndex="3"
|
||||
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true">
|
||||
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
||||
@@ -20,6 +21,9 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblType" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
@@ -33,6 +37,9 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblCharacter" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
@@ -46,6 +53,9 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblEmail" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
@@ -59,24 +69,26 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblServer" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</ComponentList>
|
||||
|
||||
<BorderPane pickOnBounds="false" style="-fx-padding: 4;">
|
||||
<left>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnRefresh" onMouseClicked="#onRefresh"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</VBox>
|
||||
<BorderPane pickOnBounds="false" style="-fx-padding: 4;">
|
||||
<left>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnRefresh" onMouseClicked="#onRefresh"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" visible="false" />
|
||||
</fx:root>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<StackPane fx:id="addServerContainer">
|
||||
<JFXDialogLayout fx:id="addServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url" onAction="#onAddNext">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
<protocols>
|
||||
<String fx:value="http" />
|
||||
<String fx:value="https" />
|
||||
</protocols>
|
||||
</URLValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</SpinnerPane>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
<JFXDialogLayout fx:id="confirmServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;"
|
||||
GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
|
||||
</GridPane>
|
||||
</body>
|
||||
<actions>
|
||||
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import java.lang.String?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -23,61 +21,4 @@
|
||||
</JFXButton>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
|
||||
<JFXDialog fx:id="dialog">
|
||||
<StackPane>
|
||||
<StackPane fx:id="addServerContainer">
|
||||
<JFXDialogLayout fx:id="addServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
<protocols>
|
||||
<String fx:value="http" />
|
||||
<String fx:value="https" />
|
||||
</protocols>
|
||||
</URLValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</SpinnerPane>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
<JFXDialogLayout fx:id="confirmServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
|
||||
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
|
||||
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
|
||||
</GridPane>
|
||||
</body>
|
||||
<actions>
|
||||
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
</StackPane>
|
||||
</JFXDialog>
|
||||
</fx:root>
|
||||
@@ -5,6 +5,7 @@
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -16,8 +17,11 @@
|
||||
<JFXTextField fx:id="textField" />
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning"/>
|
||||
<SpinnerPane fx:id="acceptPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="acceptButton" text="%button.ok" styleClass="dialog-accept"/>
|
||||
</SpinnerPane>
|
||||
<JFXButton fx:id="cancelButton" styleClass="dialog-cancel" text="%button.cancel" />
|
||||
<JFXButton fx:id="acceptButton" styleClass="dialog-accept" text="%button.ok" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</fx:root>
|
||||
@@ -13,7 +13,7 @@
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="153">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXCheckBox?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import com.jfoenix.validation.RequiredFieldValidator?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.FileItem?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
@@ -32,6 +34,12 @@
|
||||
</BorderPane>
|
||||
|
||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
|
||||
|
||||
<JFXCheckBox fx:id="toggleUseRelativePath" text="%profile.use_relative_path" StackPane.alignment="CENTER_LEFT">
|
||||
<StackPane.margin>
|
||||
<Insets left="-10"/>
|
||||
</StackPane.margin>
|
||||
</JFXCheckBox>
|
||||
</ComponentList>
|
||||
|
||||
</VBox>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
<FileItem fx:id="fileCommonLocation" name="%launcher.common_directory" title="%launcher.common_directory.choose" tooltip="%settings.launcher.common_path.tooltip" />
|
||||
<MultiFileItem fx:id="fileCommonLocation" title="%launcher.common_directory" directory="true" chooserTitle="%launcher.common_directory.choose" hasSubtitle="true" customText="%settings.custom" />
|
||||
|
||||
<MultiFileItem fx:id="backgroundItem" title="%launcher.background" chooserTitle="%launcher.background.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" />
|
||||
@@ -55,8 +55,7 @@
|
||||
|
||||
<ComponentList title="%settings.launcher.proxy"> <!-- proxy -->
|
||||
<VBox spacing="10">
|
||||
<JFXRadioButton fx:id="chkNoProxy" text="%settings.launcher.proxy.no_proxy" />
|
||||
<JFXRadioButton fx:id="chkManualProxy" text="%settings.launcher.proxy.has_proxy" />
|
||||
<JFXCheckBox fx:id="chkEnableProxy" text="%settings.launcher.proxy.enable" />
|
||||
<VBox fx:id="proxyPane" style="-fx-padding: 0 0 0 30;">
|
||||
<HBox>
|
||||
<JFXRadioButton fx:id="chkProxyHttp" text="%settings.launcher.proxy.http" />
|
||||
|
||||
@@ -139,14 +139,6 @@
|
||||
<JFXToggleButton fx:id="chkNoJVMArgs" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<Label text="%settings.advanced.no_common"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXToggleButton fx:id="chkNoCommon" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<Label text="%settings.advanced.dont_check_game_completeness"/>
|
||||
|
||||
@@ -35,24 +35,24 @@ account.choose=Choose a character
|
||||
account.create=Create a new account
|
||||
account.email=Email
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your network.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.failed.invalid_credentials=Incorrect password, or you are forbidden to login temporarily.
|
||||
account.failed.invalid_password=Invalid password
|
||||
account.failed.invalid_token=Please log out and re-input your password to login.
|
||||
account.failed.no_character=No character in this account.
|
||||
account.failed.no_selected_server=No authentication server is selected.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.injector.add=Add authentication server
|
||||
account.failed.server_response_malformed=Invalid server response. Is this a valid authentication server?
|
||||
account.injector.add=Add an authentication server
|
||||
account.injector.manage=Manage authentication servers
|
||||
account.injector.manage.title=Authentication servers
|
||||
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
|
||||
account.injector.server=Auth Server
|
||||
account.injector.server_url=Server URL
|
||||
account.injector.server_name=Server Name
|
||||
account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.no_method=No login method
|
||||
account.methods.offline=Offline
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.missing=Missing account
|
||||
account.missing=None
|
||||
account.password=Password
|
||||
account.username=Name
|
||||
|
||||
@@ -93,6 +93,7 @@ extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
fatal.config_access_denied=The configuration at "%s" is not readable or writable.\nPlease grant HMCL read and write access to this file and its parent directory.
|
||||
|
||||
folder.config=Configs
|
||||
folder.coremod=Core Mod
|
||||
@@ -119,7 +120,7 @@ install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=%s not Installed
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s Version
|
||||
install.modpack=Install a modpack (CurseForge supported)
|
||||
install.modpack=Install a modpack
|
||||
install.new_game=Install a New Game
|
||||
install.new_game.already_exists=This version has already been existing.
|
||||
install.new_game.current_game_version=Current Game Version
|
||||
@@ -127,7 +128,7 @@ install.select=Select an operation
|
||||
install.success=Installed successfully
|
||||
|
||||
lang=English
|
||||
lang.default=Belong to OS language.
|
||||
lang.default=Use system language
|
||||
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
@@ -139,6 +140,7 @@ launch.failed=Unable to launch
|
||||
launch.failed.cannot_create_jvm=Java virtual machine cannot be created. Java arguments may have problems. You can enable the no args mode in the settings.
|
||||
launch.failed.creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
|
||||
launch.failed.decompressing_natives=Unable to decompress native libraries.
|
||||
launch.failed.download_library=Unable to download library %s.
|
||||
launch.failed.executable_permission=Unable to add permission to the launch script
|
||||
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
|
||||
launch.state.dependencies=Dependencies
|
||||
@@ -154,6 +156,8 @@ launcher.background.choose=Choose background path.
|
||||
launcher.background.default=Default
|
||||
launcher.common_directory=Common Directory
|
||||
launcher.common_directory.choose=Choose common directory.
|
||||
launcher.common_directory.default=Default
|
||||
launcher.common_directory.disabled=Disabled
|
||||
launcher.contact=Contact Us
|
||||
launcher.crash=Hello Minecraft! Launcher has crashed!
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
@@ -239,6 +243,7 @@ profile.instance_directory.choose=Choose Game Directory
|
||||
profile.new=New Config
|
||||
profile.title=Game Directories
|
||||
profile.selected=Selected
|
||||
profile.use_relative_path=Use relative path for game directory if possible
|
||||
|
||||
selector.choose=Choose
|
||||
selector.choose_file=Select a file
|
||||
@@ -286,8 +291,7 @@ settings.launcher.language=Language
|
||||
settings.launcher.log_font=Log Font
|
||||
settings.launcher.proxy=Proxy
|
||||
settings.launcher.proxy.authentication=Proxy Authentication
|
||||
settings.launcher.proxy.no_proxy=No proxy
|
||||
settings.launcher.proxy.has_proxy=Proxy configuration
|
||||
settings.launcher.proxy.enable=Enable Proxy
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=Password
|
||||
@@ -306,8 +310,8 @@ settings.type.special=Specialized version settings(will not affect other version
|
||||
|
||||
update=Update
|
||||
update.failed=Failed to check for updates.
|
||||
update.found=Found Update!
|
||||
update.newest_version=Newest version: %s
|
||||
update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.latest=This is latest Version.
|
||||
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
|
||||
update.tooltip=Update
|
||||
@@ -323,11 +327,12 @@ version.launch_script.failed=Unable to make launch script.
|
||||
version.launch_script.save=Save the launch script
|
||||
version.launch_script.success=Finished script creation, %s.
|
||||
version.manage.redownload_assets_index=Redownload Assets Index
|
||||
version.manage.remove=Delete this version
|
||||
version.manage.remove=Delete this game
|
||||
version.manage.remove.confirm=Sure to remove version %s?
|
||||
version.manage.remove_libraries=Delete library files
|
||||
version.manage.rename=Rename this version
|
||||
version.manage.rename=Rename this game
|
||||
version.manage.rename.message=Please enter the new name
|
||||
version.manage.rename.fail=Failed to rename this game.
|
||||
version.settings=Settings
|
||||
version.update=Update
|
||||
|
||||
|
||||
@@ -35,21 +35,21 @@ account.choose=選擇一個角色
|
||||
account.create=新建賬戶
|
||||
account.email=郵箱
|
||||
account.failed.connect_authentication_server=無法連接認證服務器,可能是網絡問題
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.failed.invalid_credentials=您的用戶名或密碼錯誤,或者登錄次數過多被暫時禁止登錄,請稍後再試
|
||||
account.failed.invalid_password=無效的密碼
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登錄
|
||||
account.failed.no_character=該帳號沒有角色
|
||||
account.failed.no_selected_server=未選擇認證服務器
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.failed.server_response_malformed=這個服務器不是合法的登錄驗證服務器
|
||||
account.injector.add=添加認證服務器
|
||||
account.injector.manage=管理認證服務器
|
||||
account.injector.manage.title=認證服務器
|
||||
account.injector.http=警告:此服務器使用不安全的 HTTP 協議,您的密碼在登錄時會被明文傳輸。
|
||||
account.injector.server=認證服務器
|
||||
account.injector.server_url=服務器地址
|
||||
account.injector.server_name=服務器名稱
|
||||
account.methods=登錄方式
|
||||
account.methods.authlib_injector=authlib-injector 登錄
|
||||
account.methods.no_method=沒有登入方式
|
||||
account.methods.offline=離線模式
|
||||
account.methods.yggdrasil=正版登錄
|
||||
account.missing=沒有賬戶
|
||||
@@ -93,6 +93,7 @@ extension.sh=Bash 腳本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=當前 Java 平台缺少 DST Root CA X3 證書。\n您依然可以使用 HMCL,但將無法連接到部分站點(如使用 Let's Encrypt 證書的站點),這可能會使 HMCL 無法正常工作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_access_denied=HMCL 無法讀取或寫入位於 "%s" 的配置文件。\n請授予 HMCL 對該文件及其父目錄的讀寫權限。
|
||||
|
||||
folder.config=配置文件夾
|
||||
folder.coremod=核心MOD文件夾
|
||||
@@ -139,6 +140,7 @@ launch.failed=啓動失敗
|
||||
launch.failed.cannot_create_jvm=截獲到無法創建 Java 虛擬機,可能是 Java 參數有問題,可以在設置中開啓無參數模式啓動。
|
||||
launch.failed.creating_process=啓動失敗,在創建新進程時發生錯誤,可能是 Java 路徑錯誤。
|
||||
launch.failed.decompressing_natives=未能解壓遊戲本地庫。
|
||||
launch.failed.download_library=未能下载游戏依赖 %s.
|
||||
launch.failed.executable_permission=未能爲啓動文件添加執行權限。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看日志文件,或聯繫他人尋求幫助。
|
||||
launch.state.dependencies=正在處理遊戲依賴
|
||||
@@ -154,6 +156,8 @@ launcher.background.choose=選擇背景路徑
|
||||
launcher.background.default=默認(自動檢索啓動器同目錄下的 background.png/jpg 及 bg 文件夾內的圖片)
|
||||
launcher.common_directory=公共文件夾
|
||||
launcher.common_directory.choose=選擇公共文件夾
|
||||
launcher.common_directory.default=默認
|
||||
launcher.common_directory.disabled=禁用
|
||||
launcher.contact=聯繫我們
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並通過 MCBBS、貼吧、GitHub 或 Minecraft Forum 反饋 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已檢測到您的啓動器不是最新版本,請更新後再試!
|
||||
@@ -239,6 +243,7 @@ profile.instance_directory.choose=選擇遊戲路徑
|
||||
profile.new=新建配置
|
||||
profile.title=遊戲目錄
|
||||
profile.selected=已選中
|
||||
profile.use_relative_path=若可能,遊戲目錄使用相對路徑
|
||||
|
||||
selector.choose=選擇
|
||||
selector.choose_file=選擇文件
|
||||
@@ -285,9 +290,8 @@ settings.launcher.download_source=下載源
|
||||
settings.launcher.language=語言
|
||||
settings.launcher.log_font=日誌字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=代理賬戶
|
||||
settings.launcher.proxy.no_proxy=直連
|
||||
settings.launcher.proxy.has_proxy=啓用驗證
|
||||
settings.launcher.proxy.authentication=身份驗證
|
||||
settings.launcher.proxy.enable=启用代理
|
||||
settings.launcher.proxy.host=主機
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密碼
|
||||
@@ -328,6 +332,7 @@ version.manage.remove.confirm=真的要刪除版本 %s 嗎?
|
||||
version.manage.remove_libraries=刪除所有庫文件
|
||||
version.manage.rename=重命名該版本
|
||||
version.manage.rename.message=請輸入要改成的名字
|
||||
version.manage.rename.fail=重命名版本失敗,可能文件被佔用或者名字有特殊字符
|
||||
version.settings=遊戲設置
|
||||
version.update=更新
|
||||
|
||||
|
||||
@@ -35,21 +35,21 @@ account.choose=选择一个角色
|
||||
account.create=新建账户
|
||||
account.email=邮箱
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
|
||||
account.failed.invalid_credentials=您的用户名或密码错误,或者登录次数过多被暂时禁止登录,请稍后再试
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.no_character=该帐号没有角色
|
||||
account.failed.no_selected_server=未选择认证服务器
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
|
||||
account.failed.server_response_malformed=这个服务器不是合法的登录验证服务器
|
||||
account.injector.add=添加认证服务器
|
||||
account.injector.manage=管理认证服务器
|
||||
account.injector.manage.title=认证服务器
|
||||
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_url=服务器地址
|
||||
account.injector.server_name=服务器名称
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=authlib-injector 登录
|
||||
account.methods.no_method=没有登入方式
|
||||
account.methods.offline=离线模式
|
||||
account.methods.yggdrasil=正版登录
|
||||
account.missing=没有账户
|
||||
@@ -93,6 +93,7 @@ extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_access_denied=HMCL 无法读取或写入位于 "%s" 的配置文件。\n请授予 HMCL 对该文件及其父目录的读写权限。
|
||||
|
||||
folder.config=配置文件夹
|
||||
folder.coremod=核心MOD文件夹
|
||||
@@ -139,6 +140,7 @@ launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
launch.failed.decompressing_natives=未能解压游戏本地库。
|
||||
launch.failed.download_library=未能下载游戏依赖 %s.
|
||||
launch.failed.executable_permission=未能为启动文件添加执行权限。
|
||||
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
||||
launch.state.dependencies=正在处理游戏依赖
|
||||
@@ -152,8 +154,10 @@ launcher=启动器
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=选择背景路径
|
||||
launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg 及 bg 文件夹内的图片)
|
||||
launcher.common_directory=公共文件夹
|
||||
launcher.common_directory=公共文件夹(统一存放游戏资源文件,减少游戏体积)
|
||||
launcher.common_directory.choose=选择公共文件夹
|
||||
launcher.common_directory.default=默认(%AppData%/.minecraft 或者 ~/.minecraft)
|
||||
launcher.common_directory.disabled=禁用(总是使用游戏路径)
|
||||
launcher.contact=联系我们
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub 或 Minecraft Forum 反馈 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
@@ -239,6 +243,7 @@ profile.instance_directory.choose=选择游戏路径
|
||||
profile.new=新建配置
|
||||
profile.title=游戏目录
|
||||
profile.selected=已选中
|
||||
profile.use_relative_path=若可能,游戏目录使用相对路径
|
||||
|
||||
selector.choose=选择
|
||||
selector.choose_file=选择文件
|
||||
@@ -259,7 +264,7 @@ settings.advanced.launcher_visibility.hide_reopen=隐藏启动器并在游戏结
|
||||
settings.advanced.launcher_visibility.keep=保持启动器可见
|
||||
settings.advanced.launcher_visible=启动器可见性
|
||||
settings.advanced.minecraft_arguments=Minecraft 额外参数(不必填写)
|
||||
settings.advanced.no_common=不使用公共路径
|
||||
settings.advanced.no_common=不使用公共路径(游戏依赖文件存放在公共路径)
|
||||
settings.advanced.no_jvm_args=不添加默认的 JVM 参数(使用 Java 9 时必勾)
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器 IP 地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
@@ -285,9 +290,8 @@ settings.launcher.download_source=下载源
|
||||
settings.launcher.language=语言
|
||||
settings.launcher.log_font=日志字体
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=代理账户
|
||||
settings.launcher.proxy.no_proxy=直连
|
||||
settings.launcher.proxy.has_proxy=启用验证
|
||||
settings.launcher.proxy.authentication=身份验证
|
||||
settings.launcher.proxy.enable=启用代理
|
||||
settings.launcher.proxy.host=主机
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密码
|
||||
@@ -328,6 +332,7 @@ version.manage.remove.confirm=真的要删除版本 %s 吗?
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
version.manage.rename=重命名该版本
|
||||
version.manage.rename.message=请输入要改成的名字
|
||||
version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符
|
||||
version.settings=游戏设置
|
||||
version.update=更新
|
||||
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -27,7 +32,7 @@ import java.util.UUID;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class Account {
|
||||
public abstract class Account implements Observable {
|
||||
|
||||
/**
|
||||
* @return the name of the account who owns the character
|
||||
@@ -66,6 +71,26 @@ public abstract class Account {
|
||||
|
||||
public abstract void clearCache();
|
||||
|
||||
private ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the account has changed.
|
||||
* This method can be called from any thread.
|
||||
*/
|
||||
protected void invalidate() {
|
||||
Platform.runLater(helper::invalidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class AccountBuilder<T extends Account> {
|
||||
@@ -25,7 +24,6 @@ public final class AccountBuilder<T extends Account> {
|
||||
private String username;
|
||||
private String password = null;
|
||||
private Object additionalData = null;
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
|
||||
public AccountBuilder() {
|
||||
}
|
||||
@@ -50,12 +48,7 @@ public final class AccountBuilder<T extends Account> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountBuilder setProxy(Proxy proxy) {
|
||||
this.proxy = Objects.requireNonNull(proxy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T create(AccountFactory<T> factory) throws AuthenticationException {
|
||||
return factory.create(selector, Objects.requireNonNull(username), password, additionalData, proxy);
|
||||
return factory.create(selector, Objects.requireNonNull(username), password, additionalData);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -26,7 +25,7 @@ import java.util.Map;
|
||||
*/
|
||||
public abstract class AccountFactory<T extends Account> {
|
||||
|
||||
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException;
|
||||
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException;
|
||||
|
||||
public abstract T fromStorage(Map<Object, Object> storage, Proxy proxy);
|
||||
public abstract T fromStorage(Map<Object, Object> storage);
|
||||
}
|
||||
+13
@@ -31,6 +31,7 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
@@ -110,4 +111,16 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), server.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof AuthlibInjectorAccount))
|
||||
return false;
|
||||
AuthlibInjectorAccount another = (AuthlibInjectorAccount) obj;
|
||||
return super.equals(another) && server.equals(another.server);
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
@@ -45,24 +44,22 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl())),
|
||||
server, authlibInjectorDownloader, username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage) {
|
||||
Objects.requireNonNull(storage);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilSession session = YggdrasilSession.fromStorage(storage);
|
||||
|
||||
@@ -73,7 +70,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
|
||||
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
||||
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl())),
|
||||
server, authlibInjectorDownloader, username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.auth.authlibinjector;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.TransferMode;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
* @see https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF
|
||||
*/
|
||||
public final class AuthlibInjectorDnD {
|
||||
|
||||
private static final String SCHEME = "authlib-injector";
|
||||
private static final String PATH_YGGDRASIL_SERVER = "yggdrasil-server";
|
||||
|
||||
private AuthlibInjectorDnD() {}
|
||||
|
||||
public static Optional<String> parseUrlFromDragboard(Dragboard dragboard) {
|
||||
String uri = dragboard.getString();
|
||||
if (uri == null) return Optional.empty();
|
||||
|
||||
String[] uriElements = uri.split(":");
|
||||
if (uriElements.length == 3 && SCHEME.equals(uriElements[0]) && PATH_YGGDRASIL_SERVER.equals(uriElements[1])) {
|
||||
try {
|
||||
return Optional.of(URLDecoder.decode(uriElements[2], "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static EventHandler<DragEvent> dragOverHandler() {
|
||||
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
|
||||
event.acceptTransferModes(TransferMode.COPY);
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
public static EventHandler<DragEvent> dragDroppedHandler(Consumer<String> onUrlTransfered) {
|
||||
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
|
||||
event.setDropCompleted(true);
|
||||
event.consume();
|
||||
onUrlTransfered.accept(url);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+1
-2
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -81,7 +80,7 @@ public class AuthlibInjectorDownloader {
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(), Proxy.NO_PROXY,
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(),
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
public class AuthlibInjectorServer {
|
||||
|
||||
@@ -36,7 +36,7 @@ public class AuthlibInjectorServer {
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
|
||||
String name = extractServerName(response).orElse(url);
|
||||
return new AuthlibInjectorServer(url, name);
|
||||
} catch (JsonSyntaxException e) {
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,4 +105,17 @@ public class OfflineAccount extends Account {
|
||||
.append("uuid", uuid)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return username.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof OfflineAccount))
|
||||
return false;
|
||||
OfflineAccount another = (OfflineAccount) obj;
|
||||
return username.equals(another.username);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -39,12 +38,12 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) {
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData) {
|
||||
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
public OfflineAccount fromStorage(Map<Object, Object> storage) {
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalStateException("Offline account configuration malformed."));
|
||||
UUID uuid = tryCast(storage.get("uuid"), String.class)
|
||||
|
||||
@@ -114,6 +114,7 @@ public class YggdrasilAccount extends Account {
|
||||
}
|
||||
|
||||
this.characterUUID = this.session.getSelectedProfile().getId();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,4 +169,16 @@ public class YggdrasilAccount extends Account {
|
||||
return "YggdrasilAccount[username=" + getUsername() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return characterUUID.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof YggdrasilAccount))
|
||||
return false;
|
||||
YggdrasilAccount another = (YggdrasilAccount) obj;
|
||||
return characterUUID.equals(another.characterUUID);
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -42,28 +41,26 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, null, null);
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider), username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
public YggdrasilAccount fromStorage(Map<Object, Object> storage) {
|
||||
Objects.requireNonNull(storage);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilSession session = YggdrasilSession.fromStorage(storage);
|
||||
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
|
||||
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, session.getSelectedProfile().getId(), session);
|
||||
return new YggdrasilAccount(new YggdrasilService(provider), username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
|
||||
public static String randomToken() {
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
@@ -21,15 +20,9 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
public class YggdrasilService {
|
||||
|
||||
private final YggdrasilProvider provider;
|
||||
private final Proxy proxy;
|
||||
|
||||
public YggdrasilService(YggdrasilProvider provider) {
|
||||
this(provider, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public YggdrasilService(YggdrasilProvider provider, Proxy proxy) {
|
||||
this.provider = provider;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public YggdrasilSession authenticate(String username, String password, String clientToken) throws AuthenticationException {
|
||||
@@ -155,7 +148,7 @@ public class YggdrasilService {
|
||||
private String request(URL url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url, proxy);
|
||||
return NetworkUtils.doGet(url);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -67,7 +67,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
|
||||
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
|
||||
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven");
|
||||
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replace("https://authlib-injector.yushi.moe", "https://bmclapi2.bangbang93.com/mirrors/authlib-injector");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,8 +29,6 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* Note: This class has no state.
|
||||
*
|
||||
@@ -40,16 +38,10 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
|
||||
private final DefaultGameRepository repository;
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final Proxy proxy;
|
||||
|
||||
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider) {
|
||||
this(repository, downloadProvider, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, Proxy proxy) {
|
||||
this.repository = repository;
|
||||
this.downloadProvider = downloadProvider;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,11 +54,6 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
return downloadProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameBuilder gameBuilder() {
|
||||
return new DefaultGameBuilder(this);
|
||||
|
||||
@@ -21,8 +21,6 @@ import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* Do everything that will connect to Internet.
|
||||
* Downloading Minecraft files.
|
||||
@@ -36,11 +34,6 @@ public interface DependencyManager {
|
||||
*/
|
||||
GameRepository getGameRepository();
|
||||
|
||||
/**
|
||||
* The proxy that all network operations should go through.
|
||||
*/
|
||||
Proxy getProxy();
|
||||
|
||||
/**
|
||||
* Check if the game is complete.
|
||||
* Check libraries, assets, logging files and so on.
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -93,7 +94,7 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
args.add("optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
|
||||
return version.setMinecraftArguments(StringUtils.makeCommand(args));
|
||||
return version.setMinecraftArguments(new CommandBuilder().addAll(args).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
@@ -107,7 +107,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
flag = !file.exists();
|
||||
}
|
||||
if (flag) {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
dependencies.add(task);
|
||||
}
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ public final class GameAssetIndexDownloadTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
@@ -67,7 +67,7 @@ public final class GameAssetIndexDownloadTask extends Task {
|
||||
File assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())),
|
||||
assetIndexFile, dependencyManager.getProxy()
|
||||
assetIndexFile
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
|
||||
@@ -56,7 +56,6 @@ public final class GameDownloadTask extends Task {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
jar,
|
||||
dependencyManager.getProxy(),
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
));
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class GameLibrariesTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ public final class GameLoggingDownloadTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
|
||||
@@ -66,7 +66,7 @@ public final class GameLoggingDownloadTask extends Task {
|
||||
LoggingInfo logging = version.getLogging().get(DownloadType.CLIENT);
|
||||
File file = dependencyManager.getGameRepository().getLoggingObject(version.getId(), version.getAssetIndex().getId(), logging);
|
||||
if (!file.exists())
|
||||
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file, dependencyManager.getProxy()));
|
||||
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
|
||||
public class LibraryDownloadException extends Exception {
|
||||
private final Library library;
|
||||
|
||||
public LibraryDownloadException(Library library, Throwable cause) {
|
||||
super("Unable to download library " + library, cause);
|
||||
|
||||
this.library = library;
|
||||
}
|
||||
|
||||
public Library getLibrary() {
|
||||
return library;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user