Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9981423927 | ||
|
|
0e5250dfb3 | ||
|
|
2dc956b357 | ||
|
|
b8bd0a08e0 | ||
|
|
fb31eebcf2 | ||
|
|
3c8689a3f1 | ||
|
|
2957d66dd6 | ||
|
|
6f6efe0874 | ||
|
|
355fc04069 | ||
|
|
b4738f383b | ||
|
|
b12711969c | ||
|
|
94727353c4 | ||
|
|
5eb7449b74 | ||
|
|
dae41dfb30 | ||
|
|
07a701e572 | ||
|
|
5323aaed6d | ||
|
|
daf659373f | ||
|
|
0eb81c29df | ||
|
|
8aea89c103 | ||
|
|
f1d97454c2 | ||
|
|
ffb6a45e28 | ||
|
|
f0d1c9b361 | ||
|
|
aedd4c290e | ||
|
|
749ecfe37f | ||
|
|
c51da28e31 | ||
|
|
f3dca75a10 | ||
|
|
c3dcb0292c | ||
|
|
2a42ecda78 | ||
|
|
08be1896ee | ||
|
|
6b3152c4b5 | ||
|
|
19ea27dece | ||
|
|
b9e26a4112 | ||
|
|
a71aea8e43 | ||
|
|
1b1f22b9fa | ||
|
|
705a5659b6 | ||
|
|
4121921aca | ||
|
|
8cb4ca0e8b | ||
|
|
6205937a10 | ||
|
|
2f522b744f | ||
|
|
b8a84f831a | ||
|
|
5f9490d780 | ||
|
|
a1c5b87b55 | ||
|
|
641e308c53 | ||
|
|
64bc70a15d | ||
|
|
acadbdb5c9 | ||
|
|
3323ea2be0 | ||
|
|
45f55b2075 | ||
|
|
372005dae1 | ||
|
|
2c7ca6d354 | ||
|
|
e21f315239 | ||
|
|
223c2bc7c7 | ||
|
|
3facbba4ee | ||
|
|
b8bf56c8c3 | ||
|
|
2fa08e0a70 | ||
|
|
8e1e13058b | ||
|
|
92520e46a3 | ||
|
|
6ae6c9c971 | ||
|
|
caf85d2a92 | ||
|
|
030b577dee | ||
|
|
83516ba679 | ||
|
|
c8f74ee8e3 | ||
|
|
c50e41ab91 | ||
|
|
c03a0feb75 | ||
|
|
7da5b8fbc8 | ||
|
|
83f7e61d37 | ||
|
|
86765275c8 | ||
|
|
a01e15d841 | ||
|
|
5e5db332d0 | ||
|
|
2de2f45124 | ||
|
|
b1b3eb9369 | ||
|
|
507665cd91 | ||
|
|
c779172cc2 | ||
|
|
684813e131 | ||
|
|
76ef47efc4 | ||
|
|
71a8fa0e66 | ||
|
|
48d98b2424 | ||
|
|
5a0e17b121 | ||
|
|
5f686f7205 | ||
|
|
518969d2f2 | ||
|
|
01ff1327f7 | ||
|
|
4d366397a8 | ||
|
|
80f79d20a5 | ||
|
|
1611d11544 | ||
|
|
c8e8b25c85 | ||
|
|
92cfc3c1d3 | ||
|
|
c4cebe72b4 | ||
|
|
6d346a3ad6 | ||
|
|
6e2696dea3 | ||
|
|
ba67c968cf | ||
|
|
332719f49a | ||
|
|
fb18975c5c | ||
|
|
08a7c27353 | ||
|
|
4c9cb99a94 | ||
|
|
cbecca5f55 | ||
|
|
4a0831a155 | ||
|
|
fa38c53915 | ||
|
|
e7c2170ce3 | ||
|
|
9b69fe8c56 | ||
|
|
ed91771594 | ||
|
|
b33e014f71 | ||
|
|
19632ac162 | ||
|
|
74e3a9e2d1 | ||
|
|
b15291dc57 | ||
|
|
1b5d4c1b42 | ||
|
|
923924638e | ||
|
|
8b52c46664 | ||
|
|
aa8c386cdd | ||
|
|
bafe17107c | ||
|
|
9c169fb560 | ||
|
|
61ab8a6769 | ||
|
|
b1cd7057db | ||
|
|
d426952d47 | ||
|
|
67b6a60382 | ||
|
|
9d5da5a39d | ||
|
|
efb14572d6 | ||
|
|
bc3ae5cc7d | ||
|
|
9752dea0e1 | ||
|
|
9afdc99157 | ||
|
|
07febc18d4 | ||
|
|
68fdfb36fd | ||
|
|
09287f9c72 | ||
|
|
4a5ac1b899 | ||
|
|
afad520669 | ||
|
|
9950bef260 | ||
|
|
1753b4d27e | ||
|
|
2bfc85b18f | ||
|
|
24f3dec453 | ||
|
|
4e6237c062 | ||
|
|
5cbde9cc33 | ||
|
|
25d79a94f8 | ||
|
|
a5997be9c4 | ||
|
|
c679667079 | ||
|
|
6b311c679d |
No files matched your search
@@ -25,8 +25,12 @@ NVIDIA
|
||||
/HMCLCore/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# netbeans
|
||||
.nb-gradle
|
||||
+6
-3
@@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '2.0.4'
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.0'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
import java.nio.file.FileSystems
|
||||
@@ -20,9 +21,11 @@ import org.tukaani.xz.XZOutputStream
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") ?: "SNAPSHOT"
|
||||
if (System.getenv("BUILD_NUMBER") != null && System.getenv("BUILD_NUMBER_OFFSET") != null)
|
||||
buildnumber = (Integer.parseInt(System.getenv("BUILD_NUMBER")) - Integer.parseInt(System.getenv("BUILD_NUMBER_OFFSET"))).toString()
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.1"
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.2"
|
||||
version = versionroot + '.' + buildnumber
|
||||
|
||||
mainClassName = 'org.jackhuang.hmcl.Main'
|
||||
|
||||
dependencies {
|
||||
compile project(":HMCLCore")
|
||||
compile rootProject.files("lib/JFoenix.jar")
|
||||
@@ -75,7 +78,7 @@ def repack(File file) {
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Created-By': 'Copyright(c) 2013-2018 huangyuhui.',
|
||||
'Main-Class': 'org.jackhuang.hmcl.Main',
|
||||
'Main-Class': mainClassName,
|
||||
'Multi-Release': 'true',
|
||||
'Implementation-Version': version
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
@@ -37,6 +41,7 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
|
||||
@@ -44,6 +49,12 @@ public final class Launcher extends Application {
|
||||
public void start(Stage primaryStage) {
|
||||
Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
} catch (IOException e) {
|
||||
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
try {
|
||||
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
|
||||
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
|
||||
@@ -69,9 +80,6 @@ public final class Launcher extends Application {
|
||||
try {
|
||||
Logging.start(LOG_DIRECTORY);
|
||||
|
||||
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
|
||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
|
||||
@@ -17,16 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -40,6 +36,7 @@ public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("java.net.useSystemProxies", "true");
|
||||
System.setProperty("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
@@ -49,12 +46,6 @@ public final class Main {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
} catch (IOException e) {
|
||||
showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
@@ -100,7 +91,7 @@ public final class Main {
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
private static void showErrorAndExit(String message) {
|
||||
static void showErrorAndExit(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("A fatal error has occurred, forcibly exiting.");
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
@@ -110,7 +101,7 @@ public final class Main {
|
||||
/**
|
||||
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
|
||||
*/
|
||||
private static void showWarningAndContinue(String message) {
|
||||
static void showWarningAndContinue(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("Potential issues have been detected.");
|
||||
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
|
||||
/**
|
||||
* Stores metadata about this application.
|
||||
@@ -31,5 +31,6 @@ public final class Metadata {
|
||||
|
||||
public static final String UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link");
|
||||
public static final String CONTACT_URL = "https://hmcl.huangyuhui.net/contact";
|
||||
public static final String HELP_URL = "https://hmcl.huangyuhui.net/help";
|
||||
public static final String PUBLISH_URL = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
@@ -30,8 +30,10 @@ import org.jackhuang.hmcl.task.Scheduler;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
@@ -91,9 +93,19 @@ public final class AccountHelper {
|
||||
return scale(url, scaleRatio);
|
||||
}
|
||||
|
||||
public static Rectangle2D getViewport(double scaleRatio) {
|
||||
double size = 8.0 * scaleRatio;
|
||||
return new Rectangle2D(size, size, size, size);
|
||||
public static Image getHead(Image skin, int scaleRatio) {
|
||||
final int size = 8 * scaleRatio;
|
||||
final int faceOffset = (int) Math.round(scaleRatio * 4d / 9d);
|
||||
BufferedImage image = SwingFXUtils.fromFXImage(skin, null);
|
||||
BufferedImage head = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = head.createGraphics();
|
||||
g2d.drawImage(image, faceOffset, faceOffset, size - faceOffset, size - faceOffset,
|
||||
size, size, size + size, size + size, null);
|
||||
if (image.getHeight() > 32) {
|
||||
g2d.drawImage(image, 0, 0, size, size,
|
||||
40 * scaleRatio, 8 * scaleRatio, 48 * scaleRatio, 16 * scaleRatio, null);
|
||||
}
|
||||
return SwingFXUtils.toFXImage(head, null);
|
||||
}
|
||||
|
||||
private static class SkinLoadTask extends Task {
|
||||
@@ -128,12 +140,12 @@ public final class AccountHelper {
|
||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh) throws Exception {
|
||||
account.clearCache();
|
||||
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(profile.getId());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
@@ -141,12 +153,12 @@ public final class AccountHelper {
|
||||
account.clearCache();
|
||||
|
||||
if (account.getCharacter() == null) return;
|
||||
Optional<Texture> texture = account.getSkin();
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(account.getUUID());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
Optional<Texture> texture = account.getSkin();
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
@@ -159,11 +171,11 @@ public final class AccountHelper {
|
||||
}
|
||||
|
||||
public static Image getSteveSkin(double scaleRatio) {
|
||||
return scale("/assets/img/steve.png", 4);
|
||||
return scale("/assets/img/steve.png", scaleRatio);
|
||||
}
|
||||
|
||||
public static Image getAlexSkin(double scaleRatio) {
|
||||
return scale("/assets/img/alex.png", 4);
|
||||
return scale("/assets/img/alex.png", scaleRatio);
|
||||
}
|
||||
|
||||
public static Image getDefaultSkin(UUID uuid, double scaleRatio) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
@@ -26,8 +27,8 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -144,10 +145,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return setting;
|
||||
}
|
||||
|
||||
public File getVersionIcon(String id) {
|
||||
public File getVersionIconFile(String id) {
|
||||
return new File(getVersionRoot(id), "icon.png");
|
||||
}
|
||||
|
||||
public Image getVersionIconImage(String id) {
|
||||
if (id == null || !isLoaded())
|
||||
return new Image("/assets/img/grass.png");
|
||||
|
||||
Version version = getVersion(id);
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
|
||||
return new Image("/assets/img/furnace.png");
|
||||
else
|
||||
return new Image("/assets/img/grass.png");
|
||||
}
|
||||
|
||||
public boolean saveVersionSetting(String id) {
|
||||
if (!versionSettings.containsKey(id))
|
||||
return false;
|
||||
@@ -164,6 +179,27 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make version use self version settings instead of the global one.
|
||||
* @param id the version id.
|
||||
* @return specialized version setting, null if given version does not exist.
|
||||
*/
|
||||
public VersionSetting specializeVersionSetting(String id) {
|
||||
VersionSetting vs = getVersionSetting(id);
|
||||
if (vs == null)
|
||||
vs = createVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public void globalizeVersionSetting(String id) {
|
||||
VersionSetting vs = getVersionSetting(id);
|
||||
if (vs != null)
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public boolean forbidsVersion(String id) {
|
||||
return FORBIDDEN.contains(id);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.Zipper;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -61,6 +61,8 @@ public class HMCLModpackExportTask extends Task {
|
||||
Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
|
||||
try (Zipper zip = new Zipper(output.toPath())) {
|
||||
zip.putDirectory(repository.getRunDirectory(version).toPath(), "minecraft", path -> {
|
||||
if (path.isEmpty())
|
||||
return true;
|
||||
for (String s : blackList)
|
||||
if (path.equals(s))
|
||||
return false;
|
||||
@@ -73,8 +75,8 @@ public class HMCLModpackExportTask extends Task {
|
||||
Version mv = repository.getResolvedVersion(version);
|
||||
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
|
||||
zip.putTextFile(Constants.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
|
||||
zip.putTextFile(Constants.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -65,7 +65,7 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
ModpackConfiguration<Modpack> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
@@ -89,7 +89,7 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = Constants.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -36,7 +36,7 @@ public final class HMCLModpackManager {
|
||||
public static final List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(
|
||||
"usernamecache.json", "usercache.json", // Minecraft
|
||||
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
||||
"pack.json", "launcher.jar", "hmclmc.log", // HMCL
|
||||
"pack.json", "launcher.jar", "hmclmc.log", "cache", // HMCL
|
||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||
"minetweaker.log", // Mods
|
||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||
|
||||
@@ -30,9 +30,9 @@ import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -41,53 +41,84 @@ 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.*;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LauncherHelper {
|
||||
public static final LauncherHelper INSTANCE = new LauncherHelper();
|
||||
private LauncherHelper(){}
|
||||
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
private final Profile profile;
|
||||
private final Account account;
|
||||
private final String selectedVersion;
|
||||
private File scriptFile;
|
||||
private final VersionSetting setting;
|
||||
private LauncherVisibility launcherVisibility;
|
||||
private boolean showLogs;
|
||||
|
||||
public LauncherHelper(Profile profile, Account account, String selectedVersion) {
|
||||
this.profile = Objects.requireNonNull(profile);
|
||||
this.account = Objects.requireNonNull(account);
|
||||
this.selectedVersion = Objects.requireNonNull(selectedVersion);
|
||||
this.setting = profile.getVersionSetting(selectedVersion);
|
||||
this.launcherVisibility = setting.getLauncherVisibility();
|
||||
this.showLogs = setting.isShowLogs();
|
||||
}
|
||||
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
|
||||
|
||||
public void launch(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
if (account == null)
|
||||
throw new IllegalArgumentException("No account");
|
||||
public void setTestMode() {
|
||||
launcherVisibility = LauncherVisibility.KEEP;
|
||||
showLogs = true;
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
Logging.LOG.info("Launching game version: " + selectedVersion);
|
||||
|
||||
GameRepository repository = profile.getRepository();
|
||||
|
||||
Version version = repository.getResolvedVersion(selectedVersion);
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
checkGameState(profile, setting, version, () -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile));
|
||||
Schedulers.newThread().schedule(this::launch0);
|
||||
});
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
public void makeLaunchScript(File scriptFile) {
|
||||
this.scriptFile = Objects.requireNonNull(scriptFile);
|
||||
|
||||
launch();
|
||||
}
|
||||
|
||||
private void launch0() {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
@@ -127,7 +158,7 @@ public final class LauncherHelper {
|
||||
selectedVersion,
|
||||
variables.get("account"),
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
setting.getLauncherVisibility() == LauncherVisibility.CLOSE
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
|
||||
));
|
||||
@@ -147,7 +178,7 @@ public final class LauncherHelper {
|
||||
if (scriptFile == null) {
|
||||
ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID);
|
||||
PROCESSES.add(process);
|
||||
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
|
||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
else
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
@@ -186,7 +217,10 @@ public final class LauncherHelper {
|
||||
if (ex != null) {
|
||||
String message;
|
||||
if (ex instanceof CurseCompletionException) {
|
||||
message = i18n("modpack.type.curse.error");
|
||||
if (ex.getCause() instanceof FileNotFoundException)
|
||||
message = i18n("modpack.type.curse.not_found");
|
||||
else
|
||||
message = i18n("modpack.type.curse.error");
|
||||
} else if (ex instanceof PermissionException) {
|
||||
message = i18n("launch.failed.executable_permission");
|
||||
} else if (ex instanceof ProcessCreationException) {
|
||||
@@ -231,7 +265,7 @@ public final class LauncherHelper {
|
||||
|
||||
// Game later than 1.7.2 accepts Java 8.
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
@@ -256,7 +290,7 @@ public final class LauncherHelper {
|
||||
&& version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
|
||||
if (java8.isPresent()) {
|
||||
java8required = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
@@ -267,9 +301,9 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0.51");
|
||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0_51");
|
||||
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
@@ -281,13 +315,13 @@ public final class LauncherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.platform.Platform.IS_64_BIT) {
|
||||
final JavaVersion java32 = java;
|
||||
|
||||
// First find if same java version but whose platform is 64-bit installed.
|
||||
Optional<JavaVersion> java64 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.Platform.PLATFORM)
|
||||
Optional<JavaVersion> java64 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
|
||||
@@ -295,8 +329,8 @@ public final class LauncherHelper {
|
||||
final boolean java8requiredFinal = java8required, newJavaRequiredFinal = newJavaRequired;
|
||||
|
||||
// Then find if other java version which satisfies requirements installed.
|
||||
java64 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.Platform.PLATFORM)
|
||||
java64 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
|
||||
.filter(javaVersion -> {
|
||||
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;
|
||||
if (newJavaRequiredFinal) return javaVersion.getParsedVersion() >= JavaVersion.JAVA_8;
|
||||
@@ -313,28 +347,36 @@ public final class LauncherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
// 32-bit JVM cannot make use of too much memory.
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Forge 2760 and above will crash game with LiteLoader.
|
||||
if (!flag) {
|
||||
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it -> VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2760") >= 0);
|
||||
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag)
|
||||
onAccept.run();
|
||||
}
|
||||
|
||||
public static void stopManagedProcesses() {
|
||||
while (!PROCESSES.isEmpty())
|
||||
Optional.ofNullable(PROCESSES.poll()).ifPresent(ManagedProcess::stop);
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
@@ -344,8 +386,8 @@ public final class LauncherHelper {
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
}
|
||||
|
||||
private void checkExit(LauncherVisibility v) {
|
||||
switch (v) {
|
||||
private void checkExit() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(Controllers.getStage()::show);
|
||||
break;
|
||||
@@ -394,7 +436,6 @@ public final class LauncherHelper {
|
||||
|
||||
private final VersionSetting setting;
|
||||
private final Map<String, String> forbiddenTokens;
|
||||
private final LauncherVisibility visibility;
|
||||
private ManagedProcess process;
|
||||
private boolean lwjgl;
|
||||
private LogWindow logWindow;
|
||||
@@ -415,7 +456,6 @@ public final class LauncherHelper {
|
||||
pair(authInfo.getUsername(), "<player>")
|
||||
);
|
||||
|
||||
visibility = setting.getLauncherVisibility();
|
||||
logs = new LinkedList<>();
|
||||
}
|
||||
|
||||
@@ -423,7 +463,7 @@ public final class LauncherHelper {
|
||||
public void setProcess(ManagedProcess process) {
|
||||
this.process = process;
|
||||
|
||||
if (setting.isShowLogs())
|
||||
if (showLogs)
|
||||
Platform.runLater(() -> {
|
||||
logWindow = new LogWindow();
|
||||
logWindow.show();
|
||||
@@ -431,6 +471,31 @@ public final class LauncherHelper {
|
||||
});
|
||||
}
|
||||
|
||||
private void finishLaunch() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().hide();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case CLOSE:
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().close();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onLog(String log, Log4jLevel level) {
|
||||
String newLog = log;
|
||||
@@ -443,10 +508,10 @@ public final class LauncherHelper {
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.instance().getLogLines())
|
||||
if (logs.size() > config().getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
if (setting.isShowLogs()) {
|
||||
if (showLogs) {
|
||||
try {
|
||||
latch.await();
|
||||
logWindow.waitForLoaded();
|
||||
@@ -460,28 +525,7 @@ public final class LauncherHelper {
|
||||
|
||||
if (!lwjgl && (log.contains("LWJGL Version: ") || !detectWindow)) {
|
||||
lwjgl = true;
|
||||
switch (visibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().hide();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case CLOSE:
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().close();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
}
|
||||
finishLaunch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,6 +534,9 @@ public final class LauncherHelper {
|
||||
if (exitType == ExitType.INTERRUPTED)
|
||||
return;
|
||||
|
||||
// Game crashed before opening the game window.
|
||||
if (!lwjgl) finishLaunch();
|
||||
|
||||
if (exitType != ExitType.NORMAL && logWindow == null)
|
||||
Platform.runLater(() -> {
|
||||
logWindow = new LogWindow();
|
||||
@@ -511,8 +558,14 @@ public final class LauncherHelper {
|
||||
});
|
||||
});
|
||||
|
||||
checkExit(visibility);
|
||||
checkExit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
public static void stopManagedProcesses() {
|
||||
while (!PROCESSES.isEmpty())
|
||||
Optional.ofNullable(PROCESSES.poll()).ifPresent(ManagedProcess::stop);
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.FinalizedCallback;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -64,7 +64,7 @@ public final class ModpackHelper {
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
|
||||
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
|
||||
}.getType());
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
@@ -87,9 +87,10 @@ public final class ModpackHelper {
|
||||
|
||||
FinalizedCallback finalizeTask = (variables, isDependentsSucceeded) -> {
|
||||
if (isDependentsSucceeded) {
|
||||
profile.getRepository().refreshVersions();
|
||||
VersionSetting vs = profile.specializeVersionSetting(name);
|
||||
profile.getRepository().undoMark(name);
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.refreshVersions();
|
||||
VersionSetting vs = repository.specializeVersionSetting(name);
|
||||
repository.undoMark(name);
|
||||
if (vs != null)
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public final class MultiMCInstallVersionSettingTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VersionSetting vs = Objects.requireNonNull(profile.specializeVersionSetting(version));
|
||||
VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));
|
||||
ModpackHelper.toVersionSetting(manifest, vs);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader;
|
||||
@@ -35,6 +36,7 @@ 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 org.jackhuang.hmcl.task.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -59,7 +61,7 @@ public final class Accounts {
|
||||
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,
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), DownloadProviders::getDownloadProvider)::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer);
|
||||
|
||||
private static final String TYPE_OFFLINE = "offline";
|
||||
@@ -91,9 +93,9 @@ public final class Accounts {
|
||||
}
|
||||
|
||||
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(accounts);
|
||||
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts);
|
||||
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>(Accounts.class, "selectedAccount") {
|
||||
{
|
||||
accounts.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
@@ -181,11 +183,20 @@ public final class Accounts {
|
||||
config().getAuthlibInjectorServers().addListener(onInvalidating(Accounts::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
// load selected account
|
||||
selectedAccount.set(
|
||||
accounts.stream()
|
||||
.filter(it -> accountId(it).equals(config().getSelectedAccount()))
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
Account selected = accounts.stream()
|
||||
.filter(it -> accountId(it).equals(config().getSelectedAccount()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
selectedAccount.set(selected);
|
||||
|
||||
Schedulers.io().schedule(() -> {
|
||||
if (selected != null)
|
||||
try {
|
||||
selected.logIn();
|
||||
} catch (AuthenticationException e) {
|
||||
LOG.log(Level.WARNING, "Failed to log " + selected + " in", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static ObservableList<Account> getAccounts() {
|
||||
|
||||
@@ -35,11 +35,11 @@ import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.util.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.gson.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.gson.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -95,7 +95,7 @@ public final class Config implements Cloneable, Observable {
|
||||
private BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
private ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.DIRECT);
|
||||
private ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.HTTP);
|
||||
|
||||
@SerializedName("proxyHost")
|
||||
private StringProperty proxyHost = new SimpleStringProperty();
|
||||
@@ -115,8 +115,8 @@ public final class Config implements Cloneable, Observable {
|
||||
@SerializedName("localization")
|
||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
private IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
@SerializedName("downloadType")
|
||||
private StringProperty downloadType = new SimpleStringProperty("bmclapi");
|
||||
|
||||
@SerializedName("configurations")
|
||||
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
@@ -142,9 +142,6 @@ public final class Config implements Cloneable, Observable {
|
||||
@SerializedName("updateChannel")
|
||||
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
|
||||
|
||||
@SerializedName("enableMainPageGameList")
|
||||
private BooleanProperty enableMainPageGameList = new SimpleBooleanProperty(false);
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
@@ -359,15 +356,15 @@ public final class Config implements Cloneable, Observable {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
public String getDownloadType() {
|
||||
return downloadType.get();
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
public void setDownloadType(String downloadType) {
|
||||
this.downloadType.set(downloadType);
|
||||
}
|
||||
|
||||
public IntegerProperty downloadTypeProperty() {
|
||||
public StringProperty downloadTypeProperty() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
@@ -443,15 +440,4 @@ public final class Config implements Cloneable, Observable {
|
||||
this.updateChannel.set(updateChannel);
|
||||
}
|
||||
|
||||
public boolean isEnableMainPageGameList() {
|
||||
return enableMainPageGameList.get();
|
||||
}
|
||||
|
||||
public BooleanProperty enableMainPageGameListProperty() {
|
||||
return enableMainPageGameList;
|
||||
}
|
||||
|
||||
public void setEnableMainPageGameList(boolean enableMainPageGameList) {
|
||||
this.enableMainPageGameList.set(enableMainPageGameList);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -28,6 +27,10 @@ import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
@@ -68,7 +71,7 @@ public final class ConfigHolder {
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
saveConfig();
|
||||
saveConfigSync();
|
||||
|
||||
// hide the config file on windows
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
@@ -122,22 +125,26 @@ public final class ConfigHolder {
|
||||
return new Config();
|
||||
}
|
||||
|
||||
private static void saveConfig() throws IOException {
|
||||
LOG.info("Saving config");
|
||||
private static InvocationDispatcher<String> configWriter = InvocationDispatcher.runOn(Lang::thread, content -> {
|
||||
try {
|
||||
Files.write(configLocation, configInstance.toJson().getBytes(UTF_8));
|
||||
writeToConfig(content);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
private static void writeToConfig(String content) throws IOException {
|
||||
LOG.info("Saving config");
|
||||
synchronized (configLocation) {
|
||||
Files.write(configLocation, content.getBytes(UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
static void markConfigDirty() {
|
||||
// TODO: change this to async
|
||||
try {
|
||||
saveConfig();
|
||||
} catch (IOException ignored) {
|
||||
// ignore it as it has been logged
|
||||
}
|
||||
configWriter.accept(configInstance.toJson());
|
||||
}
|
||||
|
||||
private static void saveConfigSync() throws IOException {
|
||||
writeToConfig(configInstance.toJson());
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.setting;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -86,5 +86,17 @@ final class ConfigUpgrader {
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
|
||||
if (!rawJson.containsKey("downloadType")) {
|
||||
tryCast(rawJson.get("downloadtype"), Number.class)
|
||||
.map(Number::intValue)
|
||||
.ifPresent(id -> {
|
||||
if (id == 0) {
|
||||
deserialized.setDownloadType("mojang");
|
||||
} else if (id == 1) {
|
||||
deserialized.setDownloadType("bmclapi");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,44 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.CurseCDNDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
|
||||
public final class DownloadProviders {
|
||||
private DownloadProviders() {}
|
||||
|
||||
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
|
||||
public static final Map<String, DownloadProvider> providersById = mapOf(
|
||||
pair("mojang", new MojangDownloadProvider()),
|
||||
pair("bmclapi", new BMCLAPIDownloadProvider()));
|
||||
|
||||
public static DownloadProvider getDownloadProvider(int index) {
|
||||
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
|
||||
public static final String DEFAULT_PROVIDER_ID = "bmclapi";
|
||||
|
||||
private static ObjectBinding<DownloadProvider> downloadProviderProperty;
|
||||
|
||||
static void init() {
|
||||
downloadProviderProperty = Bindings.createObjectBinding(
|
||||
() -> Optional.ofNullable(providersById.get(config().getDownloadType()))
|
||||
.orElse(providersById.get(DEFAULT_PROVIDER_ID)),
|
||||
config().downloadTypeProperty());
|
||||
}
|
||||
|
||||
public static DownloadProvider getDownloadProvider() {
|
||||
return downloadProviderProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
|
||||
return downloadProviderProperty;
|
||||
}
|
||||
}
|
||||
@@ -18,18 +18,24 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.EventPriority;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHelper;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
@@ -42,7 +48,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Profile implements Observable {
|
||||
private final WeakListenerHelper helper = new WeakListenerHelper();
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
private final HMCLGameRepository repository;
|
||||
private final ModManager modManager;
|
||||
|
||||
@@ -135,21 +141,23 @@ public final class Profile implements Observable {
|
||||
|
||||
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
|
||||
this.selectedVersion.addListener(o -> checkSelectedVersion());
|
||||
helper.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion()));
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
|
||||
addPropertyChangedListener(onInvalidating(this::invalidate));
|
||||
}
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
|
||||
if (version.isPresent())
|
||||
selectedVersion.setValue(version.get());
|
||||
else if (StringUtils.isNotBlank(newValue))
|
||||
selectedVersion.setValue(null);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
|
||||
if (version.isPresent())
|
||||
selectedVersion.setValue(version.get());
|
||||
else if (newValue != null)
|
||||
selectedVersion.setValue(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
@@ -161,7 +169,7 @@ public final class Profile implements Observable {
|
||||
}
|
||||
|
||||
public DefaultDependencyManager getDependency() {
|
||||
return new DefaultDependencyManager(repository, Settings.instance().getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
||||
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
@@ -174,32 +182,6 @@ public final class Profile implements Observable {
|
||||
return vs;
|
||||
}
|
||||
|
||||
public boolean isVersionGlobal(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
return vs == null || vs.isUsesGlobal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make version use self version settings instead of the global one.
|
||||
* @param id the version id.
|
||||
* @return specialized version setting, null if given version does not exist.
|
||||
*/
|
||||
public VersionSetting specializeVersionSetting(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
if (vs == null)
|
||||
vs = repository.createVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public void globalizeVersionSetting(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
if (vs != null)
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
@@ -230,8 +212,8 @@ public final class Profile implements Observable {
|
||||
observableHelper.removeListener(listener);
|
||||
}
|
||||
|
||||
protected void invalidate() {
|
||||
observableHelper.invalidate();
|
||||
private void invalidate() {
|
||||
Platform.runLater(observableHelper::invalidate);
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
|
||||
@@ -17,19 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
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.beans.property.*;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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;
|
||||
@@ -64,7 +67,11 @@ public final class Profiles {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
Profile profile = get();
|
||||
|
||||
if (profiles.isEmpty()) {
|
||||
if (profile != null) {
|
||||
set(null);
|
||||
@@ -77,9 +84,20 @@ public final class Profiles {
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized)
|
||||
return;
|
||||
config().setSelectedProfile(profile == null ? "" : profile.getName());
|
||||
if (profile != null) {
|
||||
if (profile.getRepository().isLoaded())
|
||||
selectedVersion.bind(profile.selectedVersionProperty());
|
||||
else {
|
||||
selectedVersion.unbind();
|
||||
selectedVersion.set(null);
|
||||
// bind when repository was reloaded.
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
} else {
|
||||
selectedVersion.unbind();
|
||||
selectedVersion.set(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,7 +105,7 @@ public final class Profiles {
|
||||
if (profiles.isEmpty()) {
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE, new File(".minecraft"), new VersionSetting(), null, true);
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY);
|
||||
profiles.addAll(current, home);
|
||||
Platform.runLater(() -> profiles.addAll(current, home));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,18 +144,33 @@ public final class Profiles {
|
||||
HashSet<String> names = new HashSet<>();
|
||||
config().getConfigurations().forEach((name, profile) -> {
|
||||
if (!names.add(name)) return;
|
||||
profile.setName(name);
|
||||
profiles.add(profile);
|
||||
profile.setName(name);
|
||||
});
|
||||
checkProfiles();
|
||||
|
||||
initialized = true;
|
||||
// Platform.runLater is necessary or profiles will be empty
|
||||
// since checkProfiles adds 2 base profile later.
|
||||
Platform.runLater(() -> {
|
||||
initialized = true;
|
||||
|
||||
selectedProfile.set(
|
||||
profiles.stream()
|
||||
.filter(it -> it.getName().equals(config().getSelectedProfile()))
|
||||
.findFirst()
|
||||
.orElse(profiles.get(0)));
|
||||
selectedProfile.set(
|
||||
profiles.stream()
|
||||
.filter(it -> it.getName().equals(config().getSelectedProfile()))
|
||||
.findFirst()
|
||||
.orElse(profiles.get(0)));
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
Profile profile = selectedProfile.get();
|
||||
if (profile != null && profile.getRepository() == event.getSource()) {
|
||||
selectedVersion.bind(profile.selectedVersionProperty());
|
||||
for (Consumer<Profile> listener : versionsListeners)
|
||||
listener.accept(profile);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static ObservableList<Profile> getProfiles() {
|
||||
@@ -159,4 +192,24 @@ public final class Profiles {
|
||||
public static ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper();
|
||||
|
||||
public static ReadOnlyStringProperty selectedVersionProperty() {
|
||||
return selectedVersion.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// Guaranteed that the repository is loaded.
|
||||
public static String getSelectedVersion() {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
private static final List<Consumer<Profile>> versionsListeners = new LinkedList<>();
|
||||
|
||||
public static void registerVersionsListener(Consumer<Profile> listener) {
|
||||
Profile profile = getSelectedProfile();
|
||||
if (profile != null && profile.getRepository().isLoaded())
|
||||
listener.accept(profile);
|
||||
versionsListeners.add(listener);
|
||||
}
|
||||
}
|
||||
@@ -35,20 +35,7 @@ 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());
|
||||
private static ObjectBinding<Proxy> proxyProperty;
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxyProperty.get();
|
||||
@@ -59,12 +46,25 @@ public final class ProxyManager {
|
||||
}
|
||||
|
||||
static void init() {
|
||||
proxyProperty.addListener(observable -> updateSystemProxy());
|
||||
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());
|
||||
|
||||
proxyProperty.addListener(any -> updateSystemProxy());
|
||||
updateSystemProxy();
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class Settings {
|
||||
@@ -44,29 +46,21 @@ public class Settings {
|
||||
}
|
||||
|
||||
private Settings() {
|
||||
DownloadProviders.init();
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
Profiles.init();
|
||||
|
||||
CacheRepository.setInstance(HMCLCacheRepository.REPOSITORY);
|
||||
HMCLCacheRepository.REPOSITORY.directoryProperty().bind(config().commonDirectoryProperty());
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return Font.font(config().getFontFamily(), config().getFontSize());
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
config().setFontFamily(font.getFamily());
|
||||
config().setFontSize(font.getSize());
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(config().getLogLines(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
config().setLogLines(logLines);
|
||||
HMCLCacheRepository.REPOSITORY.directoryProperty().bind(Bindings.createStringBinding(() -> {
|
||||
String str = getCommonDirectory();
|
||||
try {
|
||||
Paths.get(str);
|
||||
return str;
|
||||
} catch (InvalidPathException e) {
|
||||
return getDefaultCommonDirectory();
|
||||
}
|
||||
}, config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
}
|
||||
|
||||
public static String getDefaultCommonDirectory() {
|
||||
@@ -83,19 +77,4 @@ public class Settings {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* DOWNLOAD PROVIDERS *
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
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);
|
||||
config().setDownloadType(index);
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,10 @@ import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -22,10 +22,17 @@ import javafx.beans.InvalidationListener;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateIntegerProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -449,12 +456,12 @@ public final class VersionSetting {
|
||||
if ("Default".equals(getJava())) return JavaVersion.fromCurrentEnvironment();
|
||||
else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(new File(getJavaDir()));
|
||||
return JavaVersion.fromExecutable(Paths.get(getJavaDir()));
|
||||
} catch (IOException e) {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
List<JavaVersion> matchedJava = JavaVersion.getJREs().stream()
|
||||
List<JavaVersion> matchedJava = JavaVersion.getJavas().stream()
|
||||
.filter(java -> java.getVersion().equals(getJava()))
|
||||
.collect(Collectors.toList());
|
||||
if (matchedJava.isEmpty()) {
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
@@ -30,31 +31,39 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public final class FXUtils {
|
||||
@@ -81,13 +90,18 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
onChange(value, consumer);
|
||||
consumer.accept(value.getValue());
|
||||
onChange(value, consumer);
|
||||
}
|
||||
|
||||
public static <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
onWeakChange(value, consumer);
|
||||
consumer.accept(value.getValue());
|
||||
onWeakChange(value, consumer);
|
||||
}
|
||||
|
||||
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
||||
if (condition.getAsBoolean()) Platform.runLater(() -> runLaterIf(condition, runnable));
|
||||
else runnable.run();
|
||||
}
|
||||
|
||||
public static void limitSize(ImageView imageView, double maxWidth, double maxHeight) {
|
||||
@@ -193,6 +207,14 @@ public final class FXUtils {
|
||||
return region.getMaxHeight();
|
||||
}
|
||||
|
||||
public static Node limitingSize(Node node, double width, double height) {
|
||||
StackPane pane = new StackPane(node);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(pane, width);
|
||||
FXUtils.setLimitHeight(pane, height);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void smoothScrolling(ScrollPane scrollPane) {
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
@@ -201,7 +223,11 @@ public final class FXUtils {
|
||||
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), I18n.getResourceBundle());
|
||||
loader.setRoot(node);
|
||||
loader.setController(node);
|
||||
Lang.invoke((ExceptionalSupplier<Object, IOException>) loader::load);
|
||||
try {
|
||||
loader.load();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, String tooltip) {
|
||||
@@ -209,27 +235,29 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
try {
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
try {
|
||||
// Java 9
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
try {
|
||||
// Java 9
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void openFolder(File file) {
|
||||
@@ -249,11 +277,15 @@ public final class FXUtils {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
thread(() -> {
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,17 +297,21 @@ public final class FXUtils {
|
||||
public static void openLink(String link) {
|
||||
if (link == null)
|
||||
return;
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
thread(() -> {
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[] { "/usr/bin/open", link });
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", link});
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
|
||||
}
|
||||
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
|
||||
}
|
||||
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
@@ -316,8 +352,10 @@ public final class FXUtils {
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @deprecated Use {@link ExtendedProperties#selectedItemPropertyFor(ComboBox)}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
|
||||
unbindEnum(comboBox);
|
||||
ChangeListener<Number> listener = (a, b, newValue) ->
|
||||
@@ -334,6 +372,7 @@ public final class FXUtils {
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
||||
if (listener == null) return;
|
||||
@@ -444,4 +483,11 @@ public final class FXUtils {
|
||||
return "Interpolator.SINE";
|
||||
}
|
||||
};
|
||||
|
||||
public static Runnable withJFXPopupClosing(Runnable runnable, JFXPopup popup) {
|
||||
return () -> {
|
||||
runnable.run();
|
||||
popup.hide();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
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.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameVersionListPage extends StackPane {
|
||||
private final JFXSpinner spinner;
|
||||
private final StackPane contentPane;
|
||||
private final JFXMasonryPane masonryPane;
|
||||
|
||||
private Profile profile;
|
||||
|
||||
public GameVersionListPage() {
|
||||
spinner = new JFXSpinner();
|
||||
spinner.getStyleClass().setAll("first-spinner");
|
||||
|
||||
contentPane = new StackPane();
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
masonryPane = new JFXMasonryPane();
|
||||
masonryPane.setHSpacing(3);
|
||||
masonryPane.setVSpacing(3);
|
||||
masonryPane.setCellWidth(182);
|
||||
masonryPane.setCellHeight(153);
|
||||
|
||||
scrollPane.setContent(masonryPane);
|
||||
|
||||
VBox vBox = new VBox();
|
||||
vBox.setPadding(new Insets(15));
|
||||
vBox.setPickOnBounds(false);
|
||||
vBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
vBox.setSpacing(15);
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
btnRefresh.setPrefWidth(40);
|
||||
btnRefresh.setPrefHeight(40);
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.getStyleClass().setAll("jfx-button-raised-round");
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnRefresh.setOnMouseClicked(e -> profile.getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
|
||||
|
||||
vBox.getChildren().setAll(btnRefresh);
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, vBox);
|
||||
|
||||
getChildren().setAll(spinner);
|
||||
|
||||
Profiles.selectedProfileProperty().addListener((o, a, b) -> this.profile = b);
|
||||
profile = Profiles.getSelectedProfile();
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
loadVersions((HMCLGameRepository) event.getSource());
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
// This will occupy 0.5s. Too slow!
|
||||
JFXUtilities.runInFX(this::loadingVersions);
|
||||
});
|
||||
if (profile.getRepository().isLoaded())
|
||||
loadVersions(profile.getRepository());
|
||||
else
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, Callable<String> gameCallable) {
|
||||
Profile profile = repository.getProfile();
|
||||
String id = version.getId();
|
||||
VersionItem item = new VersionItem();
|
||||
item.setUpdate(repository.isModpack(id));
|
||||
Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> {
|
||||
String game = vars.get("game");
|
||||
item.setGameVersion(game);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n"));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n"));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n"));
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
});
|
||||
item.setVersionName(id);
|
||||
item.setOnLaunchButtonClicked(e -> Versions.launch(profile, id));
|
||||
item.setOnScriptButtonClicked(e -> Versions.generateLaunchScript(profile, id));
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
Controllers.getVersionPage().load(id, profile);
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
});
|
||||
item.setOnUpdateButtonClicked(event -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
} catch (UnsupportedModpackException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
item.setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
JFXListView<String> versionList = new JFXListView<>();
|
||||
JFXPopup versionPopup = new JFXPopup(versionList);
|
||||
versionList.getStyleClass().add("option-list-view");
|
||||
FXUtils.setLimitWidth(versionList, 150);
|
||||
versionList.getItems().setAll(Lang.immutableListOf(
|
||||
i18n("version.manage.rename"),
|
||||
i18n("version.manage.remove"),
|
||||
i18n("modpack.export"),
|
||||
i18n("folder.game")
|
||||
));
|
||||
versionList.setOnMouseClicked(e -> {
|
||||
versionPopup.hide();
|
||||
switch (versionList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
Versions.renameVersion(profile, id);
|
||||
break;
|
||||
case 1:
|
||||
Versions.deleteVersion(profile, id);
|
||||
break;
|
||||
case 2:
|
||||
Versions.exportVersion(profile, id);
|
||||
break;
|
||||
case 3:
|
||||
FXUtils.openFolder(repository.getRunDirectory(id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
Versions.launch(profile, id);
|
||||
}
|
||||
});
|
||||
File iconFile = repository.getVersionIcon(id);
|
||||
if (iconFile.exists())
|
||||
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
return item;
|
||||
}
|
||||
|
||||
private void loadingVersions() {
|
||||
getChildren().setAll(spinner);
|
||||
masonryPane.getChildren().clear();
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.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(() -> {
|
||||
if (profile == repository.getProfile()) {
|
||||
masonryPane.getChildren().setAll(children);
|
||||
getChildren().setAll(contentPane);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,8 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -29,19 +28,19 @@ import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.account.AddAccountPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -55,38 +54,48 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||
|
||||
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
|
||||
gameListItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
gameListItem.actionButtonVisibleProperty().bind(Profiles.selectedVersionProperty().isNotNull());
|
||||
gameListItem.setOnAction(e -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
String version = Profiles.getSelectedVersion();
|
||||
if (version == null) {
|
||||
Controllers.navigate(Controllers.getGameListPage());
|
||||
} else {
|
||||
Versions.modifyGameSettings(profile, version);
|
||||
}
|
||||
});
|
||||
|
||||
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
|
||||
profileListItem.setOnAction(e -> Controllers.navigate(Controllers.getProfileListPage()));
|
||||
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
||||
|
||||
IconedItem launcherSettingsItem = new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), "iconed-item");
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
gameItem.setImage(new Image("/assets/img/bookshelf.png"));
|
||||
gameItem.setTitle(i18n("version.manage"));
|
||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
|
||||
launcherSettingsItem.getLabel().textProperty().bind(
|
||||
new When(UpdateChecker.outdatedProperty())
|
||||
.then(i18n("update.found"))
|
||||
.otherwise(i18n("settings.launcher")));
|
||||
|
||||
launcherSettingsItem.getLabel().textFillProperty().bind(
|
||||
new When(UpdateChecker.outdatedProperty())
|
||||
.then(Color.RED)
|
||||
.otherwise(Color.BLACK));
|
||||
|
||||
launcherSettingsItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.setImage(new Image("/assets/img/command.png"));
|
||||
launcherSettingsItem.setTitle(i18n("settings.launcher"));
|
||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
|
||||
this
|
||||
.startCategory(i18n("account").toUpperCase())
|
||||
.add(accountListItem)
|
||||
.startCategory(i18n("version").toUpperCase())
|
||||
.add(gameListItem)
|
||||
.add(gameItem)
|
||||
.startCategory(i18n("profile.title").toUpperCase())
|
||||
.add(profileListItem)
|
||||
.startCategory(i18n("launcher").toUpperCase())
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
||||
if (Profiles.selectedProfileProperty().get().getRepository().isLoaded())
|
||||
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile != null && profile.getRepository().isLoaded())
|
||||
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,26 +22,43 @@ import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
public abstract class ListPage<T extends Node> extends Control {
|
||||
private final ListProperty<T> items = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
|
||||
|
||||
public abstract void add();
|
||||
|
||||
public ListProperty<T> itemsProperty() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ListPageSkin(this);
|
||||
}
|
||||
|
||||
public ObservableList<T> getItems() {
|
||||
return items.get();
|
||||
}
|
||||
|
||||
public void setItems(ObservableList<T> items) {
|
||||
this.items.set(items);
|
||||
}
|
||||
|
||||
public ListProperty<T> itemsProperty() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return loading.get();
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
}
|
||||
}
|
||||
@@ -33,11 +33,10 @@ import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -176,12 +175,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}", config().getFontSize() + "px \"" + config().getFontFamily() + "\""));
|
||||
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=" + config().getLogLines());
|
||||
latch.countDown();
|
||||
onDone.fireEvent(new Event(LogWindow.this));
|
||||
}
|
||||
@@ -189,14 +188,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(config().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());
|
||||
config().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
|
||||
@@ -17,33 +17,172 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page"));
|
||||
|
||||
private final PopupMenu menu = new PopupMenu();
|
||||
private final JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
@FXML
|
||||
private StackPane main;
|
||||
@FXML
|
||||
private StackPane updatePane;
|
||||
@FXML
|
||||
private JFXButton btnLaunch;
|
||||
@FXML
|
||||
private JFXButton btnMenu;
|
||||
@FXML
|
||||
private JFXButton closeUpdateButton;
|
||||
@FXML
|
||||
private Label lblCurrentGame;
|
||||
@FXML
|
||||
private Label lblIcon;
|
||||
@FXML
|
||||
private TwoLineListItem lblLatestVersion;
|
||||
@FXML
|
||||
private Rectangle separator;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
|
||||
FXUtils.onChangeAndOperate(ConfigHolder.config().enableMainPageGameListProperty(), newValue -> {
|
||||
if (newValue)
|
||||
getChildren().setAll(new GameVersionListPage());
|
||||
else
|
||||
getChildren().setAll(main);
|
||||
btnLaunch.setClip(new Rectangle(-100, -100, 310, 200));
|
||||
btnMenu.setClip(new Rectangle(211, -100, 100, 200));
|
||||
menu.setMaxHeight(365);
|
||||
menu.setMaxWidth(545);
|
||||
menu.setAlwaysShowingVBar(true);
|
||||
|
||||
updatePane.visibleProperty().bind(UpdateChecker.outdatedProperty());
|
||||
closeUpdateButton.setGraphic(SVG.close(Theme.whiteFillBinding(), 10, 10));
|
||||
closeUpdateButton.setOnMouseClicked(event -> {
|
||||
Duration duration = Duration.millis(320);
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(updatePane.translateXProperty(), 0, Interpolator.EASE_IN)),
|
||||
new KeyFrame(duration,
|
||||
new KeyValue(updatePane.translateXProperty(), 260, Interpolator.EASE_IN)),
|
||||
new KeyFrame(duration, e -> {
|
||||
updatePane.visibleProperty().unbind();
|
||||
updatePane.setVisible(false);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
});
|
||||
lblIcon.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20));
|
||||
lblLatestVersion.titleProperty().bind(
|
||||
MultiStepBinding.of(UpdateChecker.latestVersionProperty())
|
||||
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
||||
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(12);
|
||||
btnMenu.setGraphic(graphic);
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
if (version != null) {
|
||||
lblCurrentGame.setText(version);
|
||||
} else {
|
||||
lblCurrentGame.setText(i18n("version.empty"));
|
||||
}
|
||||
});
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
setOnDragOver(event -> {
|
||||
if (event.getGestureSource() != this && event.getDragboard().hasFiles()) {
|
||||
if (event.getDragboard().getFiles().stream().anyMatch(it -> "zip".equals(FileUtils.getExtension(it))))
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
|
||||
setOnDragDropped(event -> {
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
List<File> modpacks = files.stream()
|
||||
.filter(it -> "zip".equals(FileUtils.getExtension(it)))
|
||||
.collect(Collectors.toList());
|
||||
if (!modpacks.isEmpty()) {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(modpack), i18n("install.modpack"));
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator.comparing(Version::getReleaseTime).thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(version -> {
|
||||
StackPane pane = new StackPane();
|
||||
GameItem item = new GameItem(profile, version.getId());
|
||||
pane.getChildren().setAll(item);
|
||||
pane.getStyleClass().setAll("menu-container");
|
||||
item.setMouseTransparent(true);
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
container.setOnMouseClicked(e -> {
|
||||
profile.setSelectedVersion(version.getId());
|
||||
popup.hide();
|
||||
});
|
||||
return container;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile())
|
||||
menu.getContent().setAll(children);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,6 +192,20 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Versions.launch(profile, profile.getSelectedVersion());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMenu() {
|
||||
popup.show(btnMenu, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -btnMenu.getHeight());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onUpgrade() {
|
||||
RemoteVersion target = UpdateChecker.getLatestVersion();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
UpdateHandler.updateFrom(target);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@@ -146,4 +146,20 @@ public final class SVG {
|
||||
public static Node export(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node openInNew(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node triangle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M1,21H23L12,2", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node home(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node viewList(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z", fill, width, height);
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,10 @@ import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -38,59 +36,50 @@ import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reservedSelectedPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
|
||||
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));
|
||||
|
||||
private ObjectProperty<Proxy.Type> selectedProxyType;
|
||||
|
||||
private InvalidationListener updateListener;
|
||||
|
||||
public SettingsPage() {
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
chkEnableGameList.selectedProperty().bindBidirectional(config().enableMainPageGameListProperty());
|
||||
// ==== Download sources ====
|
||||
cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet());
|
||||
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
|
||||
// ====
|
||||
|
||||
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.instance().getDownloadProvider()));
|
||||
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.instance().setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
|
||||
// ==== Font ====
|
||||
cboFont.valueProperty().bindBidirectional(config().fontFamilyProperty());
|
||||
|
||||
cboFont.initValue(Settings.instance().getFont());
|
||||
cboFont.valueProperty().addListener((a, b, newValue) -> {
|
||||
Font font = Font.font(newValue, Settings.instance().getFont().getSize());
|
||||
Settings.instance().setFont(font);
|
||||
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
|
||||
});
|
||||
txtFontSize.textProperty().bindBidirectional(config().fontSizeProperty(),
|
||||
SafeStringConverter.fromFiniteDouble()
|
||||
.restrict(it -> it > 0)
|
||||
.fallbackTo(12.0)
|
||||
.asPredicate(Validator.addTo(txtFontSize)));
|
||||
|
||||
txtFontSize.setText(Double.toString(Settings.instance().getFont().getSize()));
|
||||
txtFontSize.getValidators().add(new Validator(it -> Lang.toDoubleOrNull(it) != null));
|
||||
txtFontSize.textProperty().addListener((a, b, newValue) -> {
|
||||
if (txtFontSize.validate()) {
|
||||
Font font = Font.font(Settings.instance().getFont().getFamily(), Double.parseDouble(newValue));
|
||||
Settings.instance().setFont(font);
|
||||
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
|
||||
}
|
||||
});
|
||||
lblDisplay.fontProperty().bind(Bindings.createObjectBinding(
|
||||
() -> Font.font(config().getFontFamily(), config().getFontSize()),
|
||||
config().fontFamilyProperty(), config().fontSizeProperty()));
|
||||
// ====
|
||||
|
||||
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(config().getLocalization().getResourceBundle())));
|
||||
|
||||
cboLanguage.setItems(list);
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(config().getLocalization()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> config().setLocalization(Locales.getLocale(newValue.intValue())));
|
||||
// ==== Languages ====
|
||||
cboLanguage.getItems().setAll(Locales.LOCALES);
|
||||
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||
// ====
|
||||
|
||||
// ==== Proxy ====
|
||||
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
||||
@@ -101,40 +90,15 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
chkDisableProxy.setSelected(!config().hasProxy());
|
||||
chkDisableProxy.selectedProperty().addListener(o -> config().setHasProxy(!chkDisableProxy.isSelected()));
|
||||
config().hasProxyProperty().addListener(o -> chkDisableProxy.setSelected(!config().hasProxy()));
|
||||
reservedSelectedPropertyFor(chkDisableProxy).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());
|
||||
}
|
||||
}));
|
||||
selectedItemPropertyFor(proxyConfigurationGroup, Proxy.Type.class).bindBidirectional(config().proxyTypeProperty());
|
||||
// ====
|
||||
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
@@ -179,27 +143,12 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
|
||||
lblUpdateNote.setWrappingWidth(470);
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<UpdateChannel>() {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
UpdateChannel updateChannel = Objects.requireNonNull(get());
|
||||
chkUpdateDev.setSelected(updateChannel == UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setSelected(updateChannel == UpdateChannel.STABLE);
|
||||
}
|
||||
};
|
||||
|
||||
ToggleGroup updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
updateChannelGroup.getToggles().forEach(
|
||||
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
updateChannel.set((UpdateChannel) toggle.getUserData());
|
||||
}
|
||||
}));
|
||||
updateChannel.bindBidirectional(ConfigHolder.config().updateChannelProperty());
|
||||
selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class).bindBidirectional(config().updateChannelProperty());
|
||||
// ====
|
||||
|
||||
// ==== Background ====
|
||||
@@ -250,4 +199,9 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
}
|
||||
UpdateHandler.updateFrom(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHelp() {
|
||||
FXUtils.openLink(Metadata.HELP_URL);
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,17 @@ import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
public abstract class SettingsView extends StackPane {
|
||||
protected final JFXTextField txtProxyHost;
|
||||
@@ -38,8 +44,8 @@ public abstract class SettingsView extends StackPane {
|
||||
protected final JFXTextField txtProxyUsername;
|
||||
protected final JFXPasswordField txtProxyPassword;
|
||||
protected final JFXTextField txtFontSize;
|
||||
protected final JFXComboBox<Label> cboLanguage;
|
||||
protected final JFXComboBox<Label> cboDownloadSource;
|
||||
protected final JFXComboBox<SupportedLocale> cboLanguage;
|
||||
protected final JFXComboBox<String> cboDownloadSource;
|
||||
protected final FontComboBox cboFont;
|
||||
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
protected final Label lblDisplay;
|
||||
@@ -58,7 +64,6 @@ public abstract class SettingsView extends StackPane {
|
||||
protected final JFXCheckBox chkProxyAuthentication;
|
||||
protected final GridPane authPane;
|
||||
protected final Pane proxyPane;
|
||||
protected final JFXToggleButton chkEnableGameList;
|
||||
|
||||
public SettingsView() {
|
||||
scroll = new ScrollPane();
|
||||
@@ -73,12 +78,12 @@ public abstract class SettingsView extends StackPane {
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(I18n.i18n("update"));
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
lblUpdate = new Label(I18n.i18n("update"));
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
@@ -99,12 +104,12 @@ public abstract class SettingsView extends StackPane {
|
||||
VBox content = new VBox();
|
||||
content.setSpacing(8);
|
||||
|
||||
chkUpdateStable = new JFXRadioButton(I18n.i18n("update.channel.stable"));
|
||||
chkUpdateDev = new JFXRadioButton(I18n.i18n("update.channel.dev"));
|
||||
chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
|
||||
VBox noteWrapper = new VBox();
|
||||
noteWrapper.setStyle("-fx-padding: 10 0 0 0;");
|
||||
lblUpdateNote = new Text(I18n.i18n("update.note"));
|
||||
lblUpdateNote = new Text(i18n("update.note"));
|
||||
noteWrapper.getChildren().setAll(lblUpdateNote);
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
@@ -114,11 +119,35 @@ public abstract class SettingsView extends StackPane {
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane updatePane = new BorderPane();
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
Label help = new Label(i18n("help"));
|
||||
Label helpSubtitle = new Label(i18n("help.detail"));
|
||||
helpSubtitle.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(help, helpSubtitle);
|
||||
updatePane.setLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnExternal = new JFXButton();
|
||||
btnExternal.setOnMouseClicked(e -> onHelp());
|
||||
btnExternal.getStyleClass().setAll("toggle-icon4");
|
||||
btnExternal.setGraphic(SVG.openInNew(Theme.blackFillBinding(), -1, -1));
|
||||
|
||||
updatePane.setRight(btnExternal);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
fileCommonLocation = new MultiFileItem<>(true);
|
||||
fileCommonLocation.setTitle(I18n.i18n("launcher.common_directory"));
|
||||
fileCommonLocation.setTitle(i18n("launcher.common_directory"));
|
||||
fileCommonLocation.setDirectory(true);
|
||||
fileCommonLocation.setChooserTitle(I18n.i18n("launcher.common_directory.choose"));
|
||||
fileCommonLocation.setChooserTitle(i18n("launcher.common_directory.choose"));
|
||||
fileCommonLocation.setHasSubtitle(true);
|
||||
fileCommonLocation.setCustomText("settings.custom");
|
||||
|
||||
@@ -127,46 +156,25 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
backgroundItem = new MultiFileItem<>(true);
|
||||
backgroundItem.setTitle(I18n.i18n("launcher.background"));
|
||||
backgroundItem.setChooserTitle(I18n.i18n("launcher.background.choose"));
|
||||
backgroundItem.setTitle(i18n("launcher.background"));
|
||||
backgroundItem.setChooserTitle(i18n("launcher.background.choose"));
|
||||
backgroundItem.setHasSubtitle(true);
|
||||
backgroundItem.setCustomText(I18n.i18n("settings.custom"));
|
||||
backgroundItem.setCustomText(i18n("settings.custom"));
|
||||
|
||||
settingsPane.getContent().add(backgroundItem);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane borderPane = new BorderPane();
|
||||
|
||||
Label left = new Label(I18n.i18n("settings.launcher.enable_game_list"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
borderPane.setLeft(left);
|
||||
|
||||
chkEnableGameList = new JFXToggleButton();
|
||||
chkEnableGameList.setSize(8);
|
||||
FXUtils.setLimitHeight(chkEnableGameList, 20);
|
||||
borderPane.setRight(chkEnableGameList);
|
||||
|
||||
// Do not uncomment this line,
|
||||
// since option for user to enable game list in main page is unnecessary
|
||||
// settingsPane.addChildren(borderPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane downloadSourcePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(I18n.i18n("settings.launcher.download_source"));
|
||||
Label label = new Label(i18n("settings.launcher.download_source"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
downloadSourcePane.setLeft(label);
|
||||
}
|
||||
|
||||
{
|
||||
cboDownloadSource = new JFXComboBox<>();
|
||||
FXUtils.setLimitWidth(cboDownloadSource, 400);
|
||||
cboDownloadSource.getItems().setAll(
|
||||
new Label(I18n.i18n("download.mojang")),
|
||||
new Label(I18n.i18n("download.BMCL"))
|
||||
);
|
||||
cboDownloadSource.setConverter(stringConverter(key -> i18n("download.provider." + key)));
|
||||
downloadSourcePane.setRight(cboDownloadSource);
|
||||
}
|
||||
settingsPane.getContent().add(downloadSourcePane);
|
||||
@@ -175,11 +183,12 @@ public abstract class SettingsView extends StackPane {
|
||||
{
|
||||
BorderPane languagePane = new BorderPane();
|
||||
|
||||
Label left = new Label(I18n.i18n("settings.launcher.language"));
|
||||
Label left = new Label(i18n("settings.launcher.language"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
languagePane.setLeft(left);
|
||||
|
||||
cboLanguage = new JFXComboBox<>();
|
||||
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
|
||||
FXUtils.setLimitWidth(cboLanguage, 400);
|
||||
languagePane.setRight(cboLanguage);
|
||||
|
||||
@@ -188,13 +197,13 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
ComponentList proxyList = new ComponentList();
|
||||
proxyList.setTitle(I18n.i18n("settings.launcher.proxy"));
|
||||
proxyList.setTitle(i18n("settings.launcher.proxy"));
|
||||
|
||||
VBox proxyWrapper = new VBox();
|
||||
proxyWrapper.setSpacing(10);
|
||||
|
||||
{
|
||||
chkDisableProxy = new JFXCheckBox(I18n.i18n("settings.launcher.proxy.disable"));
|
||||
chkDisableProxy = new JFXCheckBox(i18n("settings.launcher.proxy.disable"));
|
||||
proxyWrapper.getChildren().add(chkDisableProxy);
|
||||
}
|
||||
|
||||
@@ -207,8 +216,8 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
chkProxyHttp = new JFXRadioButton(I18n.i18n("settings.launcher.proxy.http"));
|
||||
chkProxySocks = new JFXRadioButton(I18n.i18n("settings.launcher.proxy.socks"));
|
||||
chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
|
||||
chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
|
||||
hBox.getChildren().setAll(chkProxyHttp, chkProxySocks);
|
||||
proxyPane.getChildren().add(hBox);
|
||||
}
|
||||
@@ -222,7 +231,7 @@ public abstract class SettingsView extends StackPane {
|
||||
gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
{
|
||||
Label host = new Label(I18n.i18n("settings.launcher.proxy.host"));
|
||||
Label host = new Label(i18n("settings.launcher.proxy.host"));
|
||||
GridPane.setRowIndex(host, 1);
|
||||
GridPane.setColumnIndex(host, 0);
|
||||
GridPane.setHalignment(host, HPos.RIGHT);
|
||||
@@ -231,14 +240,13 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
txtProxyHost = new JFXTextField();
|
||||
txtProxyHost.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyHost, 1);
|
||||
GridPane.setColumnIndex(txtProxyHost, 1);
|
||||
gridPane.getChildren().add(txtProxyHost);
|
||||
}
|
||||
|
||||
{
|
||||
Label port = new Label(I18n.i18n("settings.launcher.proxy.port"));
|
||||
Label port = new Label(i18n("settings.launcher.proxy.port"));
|
||||
GridPane.setRowIndex(port, 2);
|
||||
GridPane.setColumnIndex(port, 0);
|
||||
GridPane.setHalignment(port, HPos.RIGHT);
|
||||
@@ -247,11 +255,10 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
txtProxyPort = new JFXTextField();
|
||||
txtProxyPort.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyPort, 2);
|
||||
GridPane.setColumnIndex(txtProxyPort, 1);
|
||||
FXUtils.setValidateWhileTextChanged(txtProxyPort, true);
|
||||
txtProxyHost.getValidators().setAll(new NumberValidator(I18n.i18n("input.number"), false));
|
||||
txtProxyHost.getValidators().setAll(new NumberValidator(i18n("input.number"), false));
|
||||
gridPane.getChildren().add(txtProxyPort);
|
||||
}
|
||||
proxyPane.getChildren().add(gridPane);
|
||||
@@ -261,7 +268,7 @@ public abstract class SettingsView extends StackPane {
|
||||
VBox vBox = new VBox();
|
||||
vBox.setStyle("-fx-padding: 20 0 20 5;");
|
||||
|
||||
chkProxyAuthentication = new JFXCheckBox(I18n.i18n("settings.launcher.proxy.authentication"));
|
||||
chkProxyAuthentication = new JFXCheckBox(i18n("settings.launcher.proxy.authentication"));
|
||||
vBox.getChildren().setAll(chkProxyAuthentication);
|
||||
|
||||
proxyPane.getChildren().add(vBox);
|
||||
@@ -276,7 +283,7 @@ public abstract class SettingsView extends StackPane {
|
||||
authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
{
|
||||
Label username = new Label(I18n.i18n("settings.launcher.proxy.username"));
|
||||
Label username = new Label(i18n("settings.launcher.proxy.username"));
|
||||
GridPane.setRowIndex(username, 0);
|
||||
GridPane.setColumnIndex(username, 0);
|
||||
authPane.getChildren().add(username);
|
||||
@@ -284,14 +291,13 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
txtProxyUsername = new JFXTextField();
|
||||
txtProxyUsername.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyUsername, 0);
|
||||
GridPane.setColumnIndex(txtProxyUsername, 1);
|
||||
authPane.getChildren().add(txtProxyUsername);
|
||||
}
|
||||
|
||||
{
|
||||
Label password = new Label(I18n.i18n("settings.launcher.proxy.password"));
|
||||
Label password = new Label(i18n("settings.launcher.proxy.password"));
|
||||
GridPane.setRowIndex(password, 1);
|
||||
GridPane.setColumnIndex(password, 0);
|
||||
authPane.getChildren().add(password);
|
||||
@@ -299,7 +305,6 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
txtProxyPassword = new JFXPasswordField();
|
||||
txtProxyPassword.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyPassword, 1);
|
||||
GridPane.setColumnIndex(txtProxyPassword, 1);
|
||||
authPane.getChildren().add(txtProxyPassword);
|
||||
@@ -316,7 +321,7 @@ public abstract class SettingsView extends StackPane {
|
||||
{
|
||||
BorderPane themePane = new BorderPane();
|
||||
|
||||
Label left = new Label(I18n.i18n("settings.launcher.theme"));
|
||||
Label left = new Label(i18n("settings.launcher.theme"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
themePane.setLeft(left);
|
||||
|
||||
@@ -336,7 +341,7 @@ public abstract class SettingsView extends StackPane {
|
||||
BorderPane borderPane = new BorderPane();
|
||||
fontPane.getChildren().add(borderPane);
|
||||
{
|
||||
Label left = new Label(I18n.i18n("settings.launcher.log_font"));
|
||||
Label left = new Label(i18n("settings.launcher.log_font"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
borderPane.setLeft(left);
|
||||
}
|
||||
@@ -383,65 +388,65 @@ public abstract class SettingsView extends StackPane {
|
||||
gridPane.getRowConstraints().setAll(row, row, row, row, row, row);
|
||||
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.copyright"));
|
||||
Label label = new Label(i18n("about.copyright"));
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.copyright.statement"));
|
||||
Label label = new Label(i18n("about.copyright.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.author"));
|
||||
Label label = new Label(i18n("about.author"));
|
||||
GridPane.setRowIndex(label, 1);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.author.statement"));
|
||||
Label label = new Label(i18n("about.author.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 1);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.thanks_to"));
|
||||
Label label = new Label(i18n("about.thanks_to"));
|
||||
GridPane.setRowIndex(label, 2);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.thanks_to.statement"));
|
||||
Label label = new Label(i18n("about.thanks_to.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 2);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.dependency"));
|
||||
Label label = new Label(i18n("about.dependency"));
|
||||
GridPane.setRowIndex(label, 3);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.dependency.statement"));
|
||||
Label label = new Label(i18n("about.dependency.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 3);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.claim"));
|
||||
Label label = new Label(i18n("about.claim"));
|
||||
GridPane.setRowIndex(label, 4);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.claim.statement"));
|
||||
Label label = new Label(i18n("about.claim.statement"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 4);
|
||||
@@ -449,13 +454,13 @@ public abstract class SettingsView extends StackPane {
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.open_source"));
|
||||
Label label = new Label(i18n("about.open_source"));
|
||||
GridPane.setRowIndex(label, 5);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(I18n.i18n("about.open_source.statement"));
|
||||
Label label = new Label(i18n("about.open_source.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 5);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
@@ -471,4 +476,5 @@ public abstract class SettingsView extends StackPane {
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
protected abstract void onHelp();
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionItem extends StackPane {
|
||||
@FXML
|
||||
private Pane icon;
|
||||
@FXML
|
||||
private VBox content;
|
||||
@FXML
|
||||
private StackPane header;
|
||||
@FXML
|
||||
private StackPane body;
|
||||
@FXML
|
||||
private JFXButton btnSettings;
|
||||
@FXML
|
||||
private JFXButton btnUpdate;
|
||||
@FXML
|
||||
private JFXButton btnLaunch;
|
||||
@FXML
|
||||
private JFXButton btnScript;
|
||||
@FXML
|
||||
private Label lblVersionName;
|
||||
@FXML
|
||||
private Label lblGameVersion;
|
||||
@FXML
|
||||
private Label lblLibraries;
|
||||
@FXML
|
||||
private ImageView iconView;
|
||||
|
||||
private EventHandler<? super MouseEvent> launchClickedHandler = null;
|
||||
|
||||
private void initializeComponents() {
|
||||
setPickOnBounds(false);
|
||||
FXUtils.setLimitWidth(this, 160);
|
||||
FXUtils.setLimitHeight(this, 149);
|
||||
|
||||
content = new VBox();
|
||||
{
|
||||
header = new StackPane();
|
||||
VBox.setVgrow(header, Priority.ALWAYS);
|
||||
header.setPickOnBounds(false);
|
||||
header.setPadding(new Insets(8));
|
||||
header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87);");
|
||||
{
|
||||
VBox headerContent = new VBox();
|
||||
headerContent.setPadding(new Insets(8, 8, 0, 8));
|
||||
{
|
||||
lblVersionName = new Label();
|
||||
lblVersionName.setStyle("-fx-font-size: 15;");
|
||||
lblVersionName.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblVersionName.setWrapText(true);
|
||||
headerContent.getChildren().add(lblVersionName);
|
||||
|
||||
lblGameVersion = new Label();
|
||||
lblGameVersion.setStyle("-fx-font-size: 11;");
|
||||
lblGameVersion.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblGameVersion.setWrapText(true);
|
||||
headerContent.getChildren().add(lblGameVersion);
|
||||
|
||||
lblLibraries = new Label();
|
||||
lblLibraries.setStyle("-fx-font-size: 10; -fx-text-fill: gray;");
|
||||
lblLibraries.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblLibraries.setWrapText(true);
|
||||
headerContent.getChildren().add(lblLibraries);
|
||||
}
|
||||
header.getChildren().add(headerContent);
|
||||
}
|
||||
content.getChildren().add(header);
|
||||
|
||||
body = new StackPane();
|
||||
body.setStyle("-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;");
|
||||
body.setMinHeight(40);
|
||||
body.setPickOnBounds(false);
|
||||
{
|
||||
BorderPane bodyContent = new BorderPane();
|
||||
{
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(8);
|
||||
{
|
||||
btnSettings = new JFXButton();
|
||||
btnSettings.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnSettings, 30);
|
||||
FXUtils.setLimitHeight(btnSettings, 30);
|
||||
hbox.getChildren().add(btnSettings);
|
||||
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnUpdate, 30);
|
||||
FXUtils.setLimitHeight(btnUpdate, 30);
|
||||
hbox.getChildren().add(btnUpdate);
|
||||
}
|
||||
bodyContent.setLeft(hbox);
|
||||
}
|
||||
{
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(8);
|
||||
{
|
||||
btnScript = new JFXButton();
|
||||
btnScript.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnScript, 30);
|
||||
FXUtils.setLimitHeight(btnScript, 30);
|
||||
hbox.getChildren().add(btnScript);
|
||||
|
||||
btnLaunch = new JFXButton();
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnLaunch, 30);
|
||||
FXUtils.setLimitHeight(btnLaunch, 30);
|
||||
hbox.getChildren().add(btnLaunch);
|
||||
}
|
||||
bodyContent.setRight(hbox);
|
||||
}
|
||||
body.getChildren().setAll(bodyContent);
|
||||
}
|
||||
content.getChildren().add(body);
|
||||
}
|
||||
getChildren().add(content);
|
||||
|
||||
icon = new StackPane();
|
||||
StackPane.setAlignment(icon, Pos.TOP_RIGHT);
|
||||
icon.setPickOnBounds(false);
|
||||
{
|
||||
iconView = new ImageView();
|
||||
StackPane.setAlignment(iconView, Pos.CENTER_RIGHT);
|
||||
StackPane.setMargin(iconView, new Insets(0, 12, 0, 0));
|
||||
iconView.setImage(new Image("/assets/img/icon.png"));
|
||||
icon.getChildren().add(iconView);
|
||||
}
|
||||
getChildren().add(icon);
|
||||
}
|
||||
|
||||
public VersionItem() {
|
||||
initializeComponents();
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
|
||||
btnSettings.setGraphic(SVG.gear(Theme.blackFillBinding(), 15, 15));
|
||||
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 15, 15));
|
||||
btnLaunch.setGraphic(SVG.launch(Theme.blackFillBinding(), 15, 15));
|
||||
btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installTooltip(btnSettings, i18n("version.settings"));
|
||||
FXUtils.installTooltip(btnUpdate, i18n("version.update"));
|
||||
FXUtils.installTooltip(btnLaunch, i18n("version.launch"));
|
||||
FXUtils.installTooltip(btnScript, i18n("version.launch_script"));
|
||||
});
|
||||
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
FXUtils.limitSize(iconView, 32, 32);
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY)
|
||||
if (e.getClickCount() == 2)
|
||||
Optional.ofNullable(launchClickedHandler).ifPresent(h -> h.handle(e));
|
||||
});
|
||||
}
|
||||
|
||||
public void setUpdate(boolean update) {
|
||||
btnUpdate.setVisible(update);
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
lblVersionName.setText(versionName);
|
||||
}
|
||||
|
||||
public void setGameVersion(String gameVersion) {
|
||||
lblGameVersion.setText(gameVersion);
|
||||
}
|
||||
|
||||
public void setLibraries(String libraries) {
|
||||
lblLibraries.setText(libraries);
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
iconView.setImage(image);
|
||||
}
|
||||
|
||||
public void setOnSettingsButtonClicked(EventHandler<? super MouseEvent> handler) {
|
||||
btnSettings.setOnMouseClicked(handler);
|
||||
}
|
||||
|
||||
public void setOnScriptButtonClicked(EventHandler<? super MouseEvent> handler) {
|
||||
btnScript.setOnMouseClicked(handler);
|
||||
}
|
||||
|
||||
public void setOnLaunchButtonClicked(EventHandler<? super MouseEvent> handler) {
|
||||
launchClickedHandler = handler;
|
||||
btnLaunch.setOnMouseClicked(handler);
|
||||
}
|
||||
|
||||
public void setOnUpdateButtonClicked(EventHandler<? super MouseEvent> handler) {
|
||||
btnUpdate.setOnMouseClicked(handler);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -27,10 +27,10 @@ import javafx.collections.WeakListChangeListener;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class WeakListenerHelper {
|
||||
List<Object> refs = new LinkedList<>();
|
||||
public class WeakListenerHolder {
|
||||
private List<Object> refs = new LinkedList<>();
|
||||
|
||||
public WeakListenerHelper() {
|
||||
public WeakListenerHolder() {
|
||||
}
|
||||
|
||||
public WeakInvalidationListener weak(InvalidationListener listener) {
|
||||
@@ -24,7 +24,9 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
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.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -36,21 +38,21 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
protected void invalidated() {
|
||||
Account account = get();
|
||||
if (account == null) {
|
||||
titleProperty().set(i18n("account.missing"));
|
||||
subtitleProperty().set(i18n("account.missing.add"));
|
||||
imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
viewportProperty().set(null);
|
||||
setTitle(i18n("account.missing"));
|
||||
setSubtitle(i18n("account.missing.add"));
|
||||
setImage(new Image("/assets/img/craft_table.png"));
|
||||
} else {
|
||||
titleProperty().set(account.getCharacter());
|
||||
subtitleProperty().set(accountSubtitle(account));
|
||||
setTitle(account.getCharacter());
|
||||
setSubtitle(accountSubtitle(account));
|
||||
|
||||
imageProperty().set(AccountHelper.getDefaultSkin(account.getUUID(), 4));
|
||||
viewportProperty().set(AccountHelper.getViewport(4));
|
||||
final int scaleRatio = 4;
|
||||
Image defaultSkin = AccountHelper.getDefaultSkin(account.getUUID(), scaleRatio);
|
||||
setImage(AccountHelper.getHead(defaultSkin, scaleRatio));
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
AccountHelper.loadSkinAsync((YggdrasilAccount) account).subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
imageProperty().set(image);
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio);
|
||||
setImage(AccountHelper.getHead(image, scaleRatio));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,6 +60,7 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
};
|
||||
|
||||
public AccountAdvancedListItem() {
|
||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> accountProperty() {
|
||||
|
||||
@@ -19,48 +19,22 @@ package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class AccountList extends ListPage<AccountListItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(i18n("account.manage"));
|
||||
private ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
itemsProperty().addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account selected = get();
|
||||
itemsProperty().forEach(item -> item.selectedProperty().set(item.getAccount() == selected));
|
||||
}
|
||||
};
|
||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
private final ObservableList<AccountListItem> accountItems;
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.manage"));
|
||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
||||
private final ObjectProperty<Account> selectedAccount;
|
||||
|
||||
public AccountList() {
|
||||
toggleGroup = new ToggleGroup();
|
||||
|
||||
accountItems = MappedObservableList.create(
|
||||
accountsProperty(),
|
||||
account -> new AccountListItem(toggleGroup, account));
|
||||
|
||||
itemsProperty().bindContent(accountItems);
|
||||
|
||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||
if (toggle == null || toggle.getUserData() == null) return;
|
||||
selectedAccount.set(((AccountListItem) toggle.getUserData()).getAccount());
|
||||
});
|
||||
setItems(MappedObservableList.create(accounts, AccountListItem::new));
|
||||
selectedAccount = createSelectedItemPropertyFor(getItems(), Account.class);
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> selectedAccountProperty() {
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
@@ -34,18 +32,17 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountListItem extends Control {
|
||||
public class AccountListItem extends RadioButton {
|
||||
|
||||
private final Account account;
|
||||
private final ToggleGroup toggleGroup;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>();
|
||||
|
||||
public AccountListItem(ToggleGroup toggleGroup, Account account) {
|
||||
public AccountListItem(Account account) {
|
||||
this.account = account;
|
||||
this.toggleGroup = toggleGroup;
|
||||
getStyleClass().clear();
|
||||
setUserData(account);
|
||||
|
||||
StringBuilder subtitleString = new StringBuilder(Accounts.getAccountTypeName(account));
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
@@ -58,15 +55,12 @@ public class AccountListItem extends Control {
|
||||
else
|
||||
title.set(account.getUsername() + " - " + account.getCharacter());
|
||||
subtitle.set(subtitleString.toString());
|
||||
selected.set(Accounts.selectedAccountProperty().get() == account);
|
||||
|
||||
viewport.set(AccountHelper.getViewport(4));
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
this.image.set(image);
|
||||
} else {
|
||||
this.image.set(AccountHelper.getDefaultSkin(account.getUUID(), 4));
|
||||
}
|
||||
final int scaleRatio = 4;
|
||||
Image image = account instanceof YggdrasilAccount ?
|
||||
AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio) :
|
||||
AccountHelper.getDefaultSkin(account.getUUID(), scaleRatio);
|
||||
this.image.set(AccountHelper.getHead(image, scaleRatio));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,34 +68,6 @@ public class AccountListItem extends Control {
|
||||
return new AccountListItemSkin(this);
|
||||
}
|
||||
|
||||
public ToggleGroup getToggleGroup() {
|
||||
return toggleGroup;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public ObjectProperty<Rectangle2D> viewportProperty() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
// progressBar.setVisible(true);
|
||||
@@ -110,8 +76,9 @@ public class AccountListItem extends Control {
|
||||
// progressBar.setVisible(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
this.image.set(image);
|
||||
final int scaleRatio = 4;
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio);
|
||||
this.image.set(AccountHelper.getHead(image, scaleRatio));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@@ -120,4 +87,44 @@ public class AccountListItem extends Control {
|
||||
public void remove() {
|
||||
Accounts.getAccounts().remove(account);
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -41,26 +40,23 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.setUserData(skinnable);
|
||||
chkSelected.selectedProperty().bindBidirectional(skinnable.selectedProperty());
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageView.viewportProperty().bind(skinnable.viewportProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
|
||||
@@ -31,6 +31,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
@@ -42,7 +43,6 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.List;
|
||||
@@ -66,6 +66,8 @@ public class AddAccountPane extends StackPane {
|
||||
@FXML private Hyperlink linkManageInjectorServers;
|
||||
@FXML private JFXDialogLayout layout;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private JFXButton btnAddServer;
|
||||
@FXML private JFXButton btnManageServer;
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
|
||||
public AddAccountPane() {
|
||||
@@ -81,6 +83,9 @@ public class AddAccountPane extends StackPane {
|
||||
cboType.setConverter(stringConverter(Accounts::getAccountTypeName));
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
btnAddServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
btnManageServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
|
||||
cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer));
|
||||
checkIfNoServer();
|
||||
|
||||
@@ -91,7 +96,6 @@ public class AddAccountPane extends StackPane {
|
||||
|
||||
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("@")));
|
||||
|
||||
@@ -189,7 +193,13 @@ public class AddAccountPane extends StackPane {
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddInjecterServer() {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
private class Selector extends BorderPane implements CharacterSelector {
|
||||
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXButton cancel = new JFXButton();
|
||||
|
||||
@@ -221,20 +231,17 @@ public class AddAccountPane extends StackPane {
|
||||
|
||||
for (GameProfile profile : names) {
|
||||
Image image;
|
||||
final int scaleRatio = 4;
|
||||
try {
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4);
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, scaleRatio);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
|
||||
image = null;
|
||||
image = AccountHelper.getDefaultSkin(profile.getId(), scaleRatio);
|
||||
}
|
||||
|
||||
ImageView portraitView = new ImageView();
|
||||
portraitView.setSmooth(false);
|
||||
if (image == null) {
|
||||
portraitView.setImage(Constants.DEFAULT_ICON.get());
|
||||
} else {
|
||||
portraitView.setImage(image);
|
||||
portraitView.setViewport(AccountHelper.getViewport(4));
|
||||
}
|
||||
portraitView.setImage(AccountHelper.getHead(image, scaleRatio));
|
||||
FXUtils.limitSize(portraitView, 32, 32);
|
||||
|
||||
IconedItem accountItem = new IconedItem(portraitView, profile.getName());
|
||||
@@ -281,6 +288,8 @@ public class AddAccountPane extends StackPane {
|
||||
return i18n("account.failed.invalid_password");
|
||||
}
|
||||
return exception.getMessage();
|
||||
} else if (exception instanceof AuthlibInjectorDownloadException) {
|
||||
return i18n("account.failed.injector_download_failure");
|
||||
} else {
|
||||
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
|
||||
+10
-12
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui.account;
|
||||
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;
|
||||
@@ -29,9 +29,10 @@ 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.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -39,7 +40,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware {
|
||||
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerUrl;
|
||||
@@ -67,16 +68,13 @@ public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
|
||||
btnAddNext.disableProperty().bind(
|
||||
Bindings.createBooleanBinding(txtServerUrl::validate, txtServerUrl.textProperty()).not());
|
||||
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
|
||||
nextPane.hideSpinner();
|
||||
}
|
||||
|
||||
private String fixInputUrl(String url) {
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
return url;
|
||||
@Override
|
||||
public void onDialogShown() {
|
||||
txtServerUrl.requestFocus();
|
||||
}
|
||||
|
||||
private String resolveFetchExceptionMessage(Throwable exception) {
|
||||
@@ -99,13 +97,13 @@ public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
|
||||
lblCreationWarning.setText("");
|
||||
|
||||
String url = fixInputUrl(txtServerUrl.getText());
|
||||
String url = txtServerUrl.getText();
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
serverBeingAdded = AuthlibInjectorServer.locateServer(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
@@ -28,7 +28,7 @@ import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
|
||||
@@ -17,39 +17,86 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
public class AdvancedListItem extends Control {
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>();
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
private final ObjectProperty<Node> rightGraphic = new SimpleObjectProperty<>(this, "rightGraphic");
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
|
||||
private final BooleanProperty actionButtonVisible = new SimpleBooleanProperty(this, "actionButtonVisible", true);
|
||||
|
||||
public AdvancedListItem() {
|
||||
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent()));
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public ObjectProperty<Rectangle2D> viewportProperty() {
|
||||
return viewport;
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public Node getRightGraphic() {
|
||||
return rightGraphic.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> rightGraphicProperty() {
|
||||
return rightGraphic;
|
||||
}
|
||||
|
||||
public void setRightGraphic(Node rightGraphic) {
|
||||
this.rightGraphic.set(rightGraphic);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public boolean isActionButtonVisible() {
|
||||
return actionButtonVisible.get();
|
||||
}
|
||||
|
||||
public BooleanProperty actionButtonVisibleProperty() {
|
||||
return actionButtonVisible;
|
||||
}
|
||||
|
||||
public void setActionButtonVisible(boolean actionButtonVisible) {
|
||||
this.actionButtonVisible.set(actionButtonVisible);
|
||||
}
|
||||
|
||||
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
|
||||
return onAction;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -28,9 +27,7 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
|
||||
@@ -38,6 +35,7 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
super(skinnable);
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
RipplerContainer container = new RipplerContainer(stackPane);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPickOnBounds(false);
|
||||
@@ -54,7 +52,6 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageView.viewportProperty().bind(skinnable.viewportProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
@@ -85,20 +82,27 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER);
|
||||
right.setPickOnBounds(false);
|
||||
|
||||
JFXButton settings = new JFXButton();
|
||||
FXUtils.setLimitWidth(settings, 40);
|
||||
settings.getStyleClass().setAll("toggle-icon4");
|
||||
settings.setGraphic(SVG.dotsVertical(Theme.blackFillBinding(), -1, -1));
|
||||
right.getChildren().setAll(settings);
|
||||
right.setMouseTransparent(true);
|
||||
right.getStyleClass().setAll("toggle-icon4");
|
||||
FXUtils.setLimitWidth(right, 40);
|
||||
FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(),
|
||||
newGraphic -> {
|
||||
if (newGraphic == null) {
|
||||
right.getChildren().clear();
|
||||
} else {
|
||||
right.getChildren().setAll(newGraphic);
|
||||
}
|
||||
});
|
||||
root.setRight(right);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.actionButtonVisibleProperty(),
|
||||
visible -> root.setRight(visible ? right : null));
|
||||
|
||||
stackPane.setStyle("-fx-padding: 10 16 10 16;");
|
||||
stackPane.getStyleClass().setAll("transparent");
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getChildren().setAll(root);
|
||||
|
||||
getChildren().setAll(stackPane);
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -17,22 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class ComponentList extends Control {
|
||||
@@ -100,21 +100,33 @@ public class ComponentList extends Control {
|
||||
}
|
||||
|
||||
protected static class Skin extends SkinBase<ComponentList> {
|
||||
private static final PseudoClass PSEUDO_CLASS_FIRST = PseudoClass.getPseudoClass("first");
|
||||
|
||||
private final ObservableList<Node> list;
|
||||
private final ObjectBinding<Node> firstItem;
|
||||
|
||||
protected Skin(ComponentList control) {
|
||||
super(control);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
Bindings.bindContent(vbox.getChildren(),
|
||||
MappedObservableList.create(control.getContent(), this::mapper));
|
||||
getChildren().setAll(vbox);
|
||||
}
|
||||
list = MappedObservableList.create(control.getContent(), node -> {
|
||||
ComponentListCell cell = new ComponentListCell(node);
|
||||
cell.getStyleClass().add("options-list-item");
|
||||
return cell;
|
||||
});
|
||||
|
||||
private Node mapper(Node node) {
|
||||
StackPane child = new StackPane();
|
||||
child.getChildren().add(new ComponentListCell(node));
|
||||
child.getStyleClass().add("options-list-item");
|
||||
return child;
|
||||
firstItem = Bindings.valueAt(list, 0);
|
||||
firstItem.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
newValue.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);
|
||||
if (oldValue != null)
|
||||
oldValue.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, false);
|
||||
});
|
||||
if (!list.isEmpty())
|
||||
list.get(0).pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
Bindings.bindContent(vbox.getChildren(), list);
|
||||
getChildren().setAll(vbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -78,7 +79,6 @@ class ComponentListCell extends StackPane {
|
||||
content.getStyleClass().add("options-sublist");
|
||||
|
||||
BorderPane groupNode = new BorderPane();
|
||||
groupNode.getStyleClass().add("options-list-item-header");
|
||||
|
||||
Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10);
|
||||
JFXButton expandButton = new JFXButton();
|
||||
@@ -86,6 +86,7 @@ class ComponentListCell extends StackPane {
|
||||
expandButton.getStyleClass().add("options-list-item-expand-button");
|
||||
|
||||
VBox labelVBox = new VBox();
|
||||
labelVBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
if (list instanceof ComponentSublist) {
|
||||
Node leftNode = ((ComponentSublist) list).getHeaderLeft();
|
||||
@@ -94,13 +95,11 @@ class ComponentListCell extends StackPane {
|
||||
} else {
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(list.titleProperty());
|
||||
label.setMouseTransparent(true);
|
||||
labelVBox.getChildren().add(label);
|
||||
|
||||
if (list.isHasSubtitle()) {
|
||||
Label subtitleLabel = new Label();
|
||||
subtitleLabel.textProperty().bind(list.subtitleProperty());
|
||||
subtitleLabel.setMouseTransparent(true);
|
||||
subtitleLabel.getStyleClass().add("subtitle-label");
|
||||
labelVBox.getChildren().add(subtitleLabel);
|
||||
}
|
||||
@@ -109,6 +108,7 @@ class ComponentListCell extends StackPane {
|
||||
groupNode.setLeft(labelVBox);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
if (list instanceof ComponentSublist) {
|
||||
Node rightNode = ((ComponentSublist) list).getHeaderRight();
|
||||
if (rightNode != null)
|
||||
@@ -116,21 +116,13 @@ class ComponentListCell extends StackPane {
|
||||
}
|
||||
right.getChildren().add(expandButton);
|
||||
groupNode.setRight(right);
|
||||
labelVBox.setAlignment(Pos.CENTER_LEFT);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
VBox container = new VBox();
|
||||
container.setStyle("-fx-padding: 8 0 0 0;");
|
||||
container.setPadding(new Insets(8, 0, 0, 0));
|
||||
FXUtils.setLimitHeight(container, 0);
|
||||
Rectangle clipRect = new Rectangle();
|
||||
clipRect.widthProperty().bind(container.widthProperty());
|
||||
clipRect.heightProperty().bind(container.heightProperty());
|
||||
container.setClip(clipRect);
|
||||
FXUtils.setOverflowHidden(container, true);
|
||||
container.getChildren().setAll(content);
|
||||
|
||||
VBox holder = new VBox();
|
||||
holder.getChildren().setAll(groupNode, container);
|
||||
holder.getStyleClass().add("options-list-item-container");
|
||||
groupNode.setBottom(container);
|
||||
|
||||
expandButton.setOnMouseClicked(e -> {
|
||||
if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {
|
||||
@@ -162,7 +154,7 @@ class ComponentListCell extends StackPane {
|
||||
expandedProperty().addListener((a, b, newValue) ->
|
||||
expandIcon.setRotate(newValue ? 180 : 0));
|
||||
|
||||
getChildren().setAll(holder);
|
||||
getChildren().setAll(groupNode);
|
||||
} else
|
||||
getChildren().setAll(content);
|
||||
}
|
||||
|
||||
+7
-11
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,21 +15,17 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
* @see Controllers#dialog(javafx.scene.layout.Region)
|
||||
*/
|
||||
public final class JsonUtils {
|
||||
public interface DialogAware {
|
||||
|
||||
private JsonUtils() {}
|
||||
|
||||
public static <T> T fromNonNullJson(String json, Class<T> classOfT) throws JsonParseException {
|
||||
T parsed = Constants.GSON.fromJson(json, classOfT);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
default void onDialogShown() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,20 +17,26 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import static javafx.collections.FXCollections.emptyObservableList;
|
||||
import static javafx.collections.FXCollections.observableList;
|
||||
import static javafx.collections.FXCollections.singletonObservableList;
|
||||
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
public class FontComboBox extends JFXComboBox<String> {
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize,
|
||||
@NamedArg(value = "enableStyle", defaultValue = "false") boolean enableStyle) {
|
||||
valueProperty().addListener((a, b, newValue) -> {
|
||||
if (enableStyle)
|
||||
setStyle("-fx-font-family: \"" + newValue + "\";");
|
||||
});
|
||||
styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\""));
|
||||
|
||||
setCellFactory(listView -> new ListCell<String>() {
|
||||
@Override
|
||||
@@ -43,15 +49,15 @@ public class FontComboBox extends JFXComboBox<String> {
|
||||
}
|
||||
});
|
||||
|
||||
itemsProperty().bind(MultiStepBinding.of(valueProperty())
|
||||
.map(value -> value == null ? emptyObservableList() : singletonObservableList(value)));
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
if (loaded) return;
|
||||
getItems().setAll(Font.getFamilies());
|
||||
if (loaded)
|
||||
return;
|
||||
itemsProperty().unbind();
|
||||
setItems(observableList(Font.getFamilies()));
|
||||
loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
public void initValue(Font font) {
|
||||
getItems().setAll(font.getFamily());
|
||||
getSelectionModel().select(font.getFamily());
|
||||
}
|
||||
}
|
||||
@@ -39,12 +39,17 @@ public class IconedItem extends RipplerContainer {
|
||||
|
||||
private static HBox createHBox(Node icon) {
|
||||
HBox hBox = new HBox();
|
||||
|
||||
if (icon != null) {
|
||||
icon.setMouseTransparent(true);
|
||||
hBox.getChildren().add(icon);
|
||||
}
|
||||
|
||||
hBox.getStyleClass().setAll("iconed-item-container");
|
||||
icon.setMouseTransparent(true);
|
||||
Label textLabel = new Label();
|
||||
textLabel.setId("label");
|
||||
textLabel.setMouseTransparent(true);
|
||||
hBox.getChildren().addAll(icon, textLabel);
|
||||
hBox.getChildren().addAll(textLabel);
|
||||
return hBox;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
|
||||
public class MenuSeparator extends StackPane {
|
||||
|
||||
public MenuSeparator() {
|
||||
Rectangle rect = new Rectangle();
|
||||
rect.widthProperty().bind(widthProperty().add(-14));
|
||||
rect.setHeight(1);
|
||||
rect.setFill(Color.GRAY);
|
||||
maxHeightProperty().set(10);
|
||||
setPadding(new Insets(3));
|
||||
getChildren().setAll(rect);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import javafx.beans.NamedArg;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -138,7 +139,7 @@ public class MultiFileItem<T> extends ComponentList {
|
||||
|
||||
public Node createChildren(String title, String subtitle, T userData) {
|
||||
BorderPane pane = new BorderPane();
|
||||
pane.setStyle("-fx-padding: 3;");
|
||||
pane.setPadding(new Insets(3));
|
||||
FXUtils.setLimitHeight(pane, 20);
|
||||
|
||||
JFXRadioButton left = new JFXRadioButton(title);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@@ -43,6 +44,9 @@ public class Navigator extends StackPane {
|
||||
public Navigator(Node init) {
|
||||
stack.push(init);
|
||||
getChildren().setAll(init);
|
||||
|
||||
Platform.runLater(() ->
|
||||
fireEvent(new NavigationEvent(this, init, NavigationEvent.NAVIGATED)));
|
||||
}
|
||||
|
||||
public void navigate(Node node) {
|
||||
@@ -66,15 +70,26 @@ public class Navigator extends StackPane {
|
||||
close(stack.peek());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
while (stack.size() > 1)
|
||||
close(stack.peek());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void close(Node from) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
stack.remove(from);
|
||||
if (stack.peek() != from)
|
||||
throw new IllegalStateException();
|
||||
stack.pop();
|
||||
Node node = stack.peek();
|
||||
fireEvent(new NavigationEvent(this, from, NavigationEvent.NAVIGATING));
|
||||
NavigationEvent navigating = new NavigationEvent(this, from, NavigationEvent.NAVIGATING);
|
||||
fireEvent(navigating);
|
||||
node.fireEvent(navigating);
|
||||
setContent(node);
|
||||
fireEvent(new NavigationEvent(this, node, NavigationEvent.NAVIGATED));
|
||||
NavigationEvent navigated = new NavigationEvent(this, node, NavigationEvent.NAVIGATED);
|
||||
fireEvent(navigated);
|
||||
node.fireEvent(navigated);
|
||||
|
||||
Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> from.removeEventHandler(PageCloseEvent.CLOSE, (EventHandler<PageCloseEvent>) handler));
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class PopupMenu extends Control {
|
||||
|
||||
private final ObservableList<Node> content = FXCollections.observableArrayList();
|
||||
private final BooleanProperty alwaysShowingVBar = new SimpleBooleanProperty();
|
||||
|
||||
public PopupMenu() {
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public boolean isAlwaysShowingVBar() {
|
||||
return alwaysShowingVBar.get();
|
||||
}
|
||||
|
||||
public BooleanProperty alwaysShowingVBarProperty() {
|
||||
return alwaysShowingVBar;
|
||||
}
|
||||
|
||||
public void setAlwaysShowingVBar(boolean alwaysShowingVBar) {
|
||||
this.alwaysShowingVBar.set(alwaysShowingVBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new PopupMenuSkin();
|
||||
}
|
||||
|
||||
private class PopupMenuSkin extends SkinBase<PopupMenu> {
|
||||
|
||||
protected PopupMenuSkin() {
|
||||
super(PopupMenu.this);
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.vbarPolicyProperty().bind(new When(alwaysShowingVBar)
|
||||
.then(ScrollPane.ScrollBarPolicy.ALWAYS)
|
||||
.otherwise(ScrollPane.ScrollBarPolicy.AS_NEEDED));
|
||||
|
||||
VBox content = new VBox();
|
||||
content.getStyleClass().add("menu");
|
||||
Bindings.bindContent(content.getChildren(), PopupMenu.this.getContent());
|
||||
scrollPane.setContent(content);
|
||||
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
|
||||
getChildren().setAll(scrollPane);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TwoLineListItem extends StackPane {
|
||||
private static final String DEFAULT_STYLE_CLASS = "two-line-list-item";
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
|
||||
@@ -64,6 +65,8 @@ public class TwoLineListItem extends StackPane {
|
||||
VBox vbox = new VBox();
|
||||
vbox.getChildren().setAll(lblTitle, lblSubtitle);
|
||||
getChildren().setAll(vbox);
|
||||
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public class URLValidator extends ValidatorBase {
|
||||
|
||||
private final ObservableList<String> protocols = FXCollections.observableArrayList();
|
||||
|
||||
public URLValidator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ObservableList<String> getProtocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
if (srcControl.get() instanceof TextInputControl) {
|
||||
try {
|
||||
URL url = new URL(((TextInputControl) srcControl.get()).getText());
|
||||
if (protocols.isEmpty())
|
||||
hasErrors.set(false);
|
||||
else
|
||||
hasErrors.set(!protocols.contains(url.getProtocol()));
|
||||
} catch (MalformedURLException e) {
|
||||
hasErrors.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,37 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
public final class Validator extends ValidatorBase {
|
||||
|
||||
public static Consumer<Predicate<String>> addTo(JFXTextField control) {
|
||||
return addTo(control, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SafeStringConverter#asPredicate(Consumer)
|
||||
*/
|
||||
public static Consumer<Predicate<String>> addTo(JFXTextField control, String message) {
|
||||
return predicate -> {
|
||||
Validator validator = new Validator(message, predicate);
|
||||
InvalidationListener listener = any -> control.validate();
|
||||
validator.getProperties().put(validator, listener);
|
||||
control.textProperty().addListener(new WeakInvalidationListener(listener));
|
||||
control.getValidators().add(validator);
|
||||
};
|
||||
}
|
||||
|
||||
private final Predicate<String> validator;
|
||||
|
||||
/**
|
||||
|
||||
+7
-2
@@ -30,7 +30,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
public class DecoratorControl extends Control {
|
||||
public class Decorator extends Control {
|
||||
private final ListProperty<Node> drawer = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
@@ -45,10 +45,11 @@ public class DecoratorControl extends Control {
|
||||
private final BooleanProperty canRefresh = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty canBack = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty canClose = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty showCloseAsHome = new SimpleBooleanProperty(false);
|
||||
private final Stage primaryStage;
|
||||
private StackPane drawerWrapper;
|
||||
|
||||
public DecoratorControl(Stage primaryStage) {
|
||||
public Decorator(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
@@ -174,6 +175,10 @@ public class DecoratorControl extends Control {
|
||||
return canClose;
|
||||
}
|
||||
|
||||
public BooleanProperty showCloseAsHomeProperty() {
|
||||
return showCloseAsHome;
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonActionProperty() {
|
||||
return onBackNavButtonAction;
|
||||
}
|
||||
@@ -22,7 +22,10 @@ import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
@@ -66,7 +69,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
public class DecoratorController {
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final DecoratorControl decorator;
|
||||
private final Decorator decorator;
|
||||
private final ImageView welcomeView;
|
||||
private final Navigator navigator;
|
||||
private final Node mainPage;
|
||||
@@ -77,7 +80,7 @@ public class DecoratorController {
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
this.mainPage = mainPage;
|
||||
|
||||
decorator = new DecoratorControl(stage);
|
||||
decorator = new Decorator(stage);
|
||||
decorator.titleProperty().set(Metadata.TITLE);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
|
||||
@@ -93,6 +96,7 @@ public class DecoratorController {
|
||||
welcomeView = new ImageView();
|
||||
welcomeView.setImage(new Image("/assets/img/welcome.png"));
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
FXUtils.limitSize(welcomeView, 796, 517);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
@@ -110,7 +114,7 @@ public class DecoratorController {
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
public DecoratorControl getDecorator() {
|
||||
public Decorator getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
@@ -202,10 +206,12 @@ public class DecoratorController {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
page.onForceToClose();
|
||||
} else {
|
||||
navigator.close();
|
||||
if (page.canForceToClose()) {
|
||||
page.onForceToClose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
navigator.clear();
|
||||
}
|
||||
|
||||
private void back() {
|
||||
@@ -247,14 +253,15 @@ public class DecoratorController {
|
||||
|
||||
if (to instanceof DecoratorPage) {
|
||||
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
||||
decorator.canCloseProperty().set(((DecoratorPage) to).canForceToClose());
|
||||
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).canForceToClose());
|
||||
} else {
|
||||
decorator.drawerTitleProperty().unbind();
|
||||
decorator.drawerTitleProperty().set("");
|
||||
decorator.canCloseProperty().set(false);
|
||||
decorator.showCloseAsHomeProperty().set(true);
|
||||
}
|
||||
|
||||
decorator.canBackProperty().set(navigator.canGoBack());
|
||||
decorator.canCloseProperty().set(navigator.canGoBack());
|
||||
|
||||
if (navigator.canGoBack()) {
|
||||
decorator.setContentBackground(new Background(new BackgroundFill(Color.rgb(244, 244, 244, 0.5), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
@@ -277,12 +284,17 @@ public class DecoratorController {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialog == null) {
|
||||
if (decorator.getDrawerWrapper() == null) {
|
||||
// Sometimes showDialog will be invoked before decorator was initialized.
|
||||
// Keep trying again.
|
||||
Platform.runLater(() -> showDialog(node));
|
||||
return;
|
||||
}
|
||||
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
if (decorator.getDrawerWrapper() == null)
|
||||
throw new IllegalStateException("Decorator has not been initialized");
|
||||
dialog.setDialogContainer(decorator.getDrawerWrapper());
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
@@ -293,6 +305,23 @@ public class DecoratorController {
|
||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
DialogAware dialogAware = (DialogAware) node;
|
||||
if (dialog.isVisible()) {
|
||||
dialogAware.onDialogShown();
|
||||
} else {
|
||||
dialog.visibleProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
dialogAware.onDialogShown();
|
||||
observable.removeListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
|
||||
@@ -62,14 +62,14 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
*
|
||||
* @param control The control for which this Skin should attach to.
|
||||
*/
|
||||
public DecoratorSkin(DecoratorControl control) {
|
||||
public DecoratorSkin(Decorator control) {
|
||||
super(control);
|
||||
|
||||
primaryStage = control.getPrimaryStage();
|
||||
|
||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
|
||||
DecoratorControl skinnable = getSkinnable();
|
||||
Decorator skinnable = getSkinnable();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().setAll("jfx-decorator", "resize-border");
|
||||
@@ -146,7 +146,7 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
titleContainer.setOnMouseMoved(this::onMouseMoved);
|
||||
titleContainer.setPickOnBounds(false);
|
||||
titleContainer.setMinHeight(40);
|
||||
titleContainer.getStyleClass().setAll("jfx-tool-bar");
|
||||
titleContainer.getStyleClass().addAll("jfx-tool-bar", "window-title-bar");
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
||||
if (!isDragging) allowMove = false;
|
||||
@@ -169,9 +169,9 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
titleWrapper.setCenter(lblTitle);
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.getStyleClass().add("separator");
|
||||
separator.heightProperty().bind(titleWrapper.heightProperty());
|
||||
separator.setWidth(1);
|
||||
separator.setFill(Color.GRAY);
|
||||
titleWrapper.setRight(separator);
|
||||
}
|
||||
titleContainer.setLeft(titleWrapper);
|
||||
@@ -201,6 +201,13 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
if (newValue) navLeft.getChildren().setAll(backNavButton, closeNavButton);
|
||||
else navLeft.getChildren().setAll(backNavButton);
|
||||
});
|
||||
|
||||
skinnable.showCloseAsHomeProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue)
|
||||
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
});
|
||||
}
|
||||
navBar.setLeft(navLeft);
|
||||
|
||||
@@ -233,6 +240,7 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
separator.setFill(Color.GRAY);
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
StackPane pane = new StackPane(minus);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
private final File file;
|
||||
|
||||
public ModpackInstallWizardProvider() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(File modpackFile) {
|
||||
this.file = modpackFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
profile = Profiles.getSelectedProfile();
|
||||
|
||||
if (file != null)
|
||||
settings.put(ModpackPage.MODPACK_FILE, file);
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
return finishModpackInstallingAsync(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final String PROFILE = "PROFILE";
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WebStage;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
@@ -39,7 +40,9 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackPage extends StackPane implements WizardPage {
|
||||
@@ -75,31 +78,41 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) Platform.runLater(controller::onEnd);
|
||||
else {
|
||||
File selectedFile;
|
||||
|
||||
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
|
||||
if (filePath.isPresent()) {
|
||||
selectedFile = filePath.get();
|
||||
} else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) {
|
||||
Platform.runLater(controller::onEnd);
|
||||
return;
|
||||
}
|
||||
|
||||
controller.getSettings().put(MODPACK_FILE, selectedFile);
|
||||
}
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
|
||||
lblModpackLocation.setText(selectedFile.getAbsolutePath());
|
||||
txtModpackName.getValidators().addAll(
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
|
||||
new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
|
||||
);
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
txtModpackName.setText(i18n("modpack.task.install.error"));
|
||||
btnInstall.setDisable(true);
|
||||
}
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Platform.runLater(controller::onEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-34
@@ -21,8 +21,6 @@ import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -30,18 +28,14 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class DownloadWizardProvider implements WizardProvider {
|
||||
public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
private final int type;
|
||||
|
||||
public DownloadWizardProvider(int type) {
|
||||
this.type = type;
|
||||
public VanillaInstallWizardProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,29 +64,12 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
switch (type) {
|
||||
case 0: return finishVersionDownloadingAsync(settings);
|
||||
case 1: return finishModpackInstallingAsync(settings);
|
||||
default: return null;
|
||||
}
|
||||
return finishVersionDownloadingAsync(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,14 +77,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
switch (type) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));
|
||||
case 1:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + type + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Zipper;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@@ -31,8 +31,8 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
|
||||
@@ -22,6 +22,8 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
public class ProfileAdvancedListItem extends AdvancedListItem {
|
||||
@@ -32,14 +34,15 @@ public class ProfileAdvancedListItem extends AdvancedListItem {
|
||||
Profile profile = get();
|
||||
if (profile == null) {
|
||||
} else {
|
||||
titleProperty().set(Profiles.getProfileDisplayName(profile));
|
||||
subtitleProperty().set(profile.getGameDir().toString());
|
||||
setTitle(Profiles.getProfileDisplayName(profile));
|
||||
setSubtitle(profile.getGameDir().toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ProfileAdvancedListItem() {
|
||||
imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
setImage(new Image("/assets/img/craft_table.png"));
|
||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
||||
}
|
||||
|
||||
public ObjectProperty<Profile> profileProperty() {
|
||||
|
||||
@@ -19,48 +19,23 @@ package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class ProfileList extends ListPage<ProfileListItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(i18n("profile.manage"));
|
||||
private ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
{
|
||||
itemsProperty().addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Profile selected = get();
|
||||
itemsProperty().forEach(item -> item.selectedProperty().set(item.getProfile() == selected));
|
||||
}
|
||||
};
|
||||
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
private final ObservableList<ProfileListItem> profileItems;
|
||||
private ObjectProperty<Profile> selectedProfile;
|
||||
|
||||
public ProfileList() {
|
||||
toggleGroup = new ToggleGroup();
|
||||
|
||||
profileItems = MappedObservableList.create(
|
||||
profilesProperty(),
|
||||
profile -> new ProfileListItem(toggleGroup, profile));
|
||||
|
||||
itemsProperty().bindContent(profileItems);
|
||||
|
||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||
if (toggle == null || toggle.getUserData() == null) return;
|
||||
selectedProfile.set(((ProfileListItem) toggle.getUserData()).getProfile());
|
||||
});
|
||||
setItems(MappedObservableList.create(profilesProperty(), ProfileListItem::new));
|
||||
selectedProfile = createSelectedItemPropertyFor(getItems(), Profile.class);
|
||||
}
|
||||
|
||||
public ObjectProperty<Profile> selectedProfileProperty() {
|
||||
|
||||
@@ -17,30 +17,26 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
|
||||
public class ProfileListItem extends Control {
|
||||
public class ProfileListItem extends RadioButton {
|
||||
private final Profile profile;
|
||||
private final ToggleGroup toggleGroup;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
|
||||
public ProfileListItem(ToggleGroup toggleGroup, Profile profile) {
|
||||
public ProfileListItem(Profile profile) {
|
||||
this.profile = profile;
|
||||
this.toggleGroup = toggleGroup;
|
||||
getStyleClass().clear();
|
||||
setUserData(profile);
|
||||
|
||||
title.set(Profiles.getProfileDisplayName(profile));
|
||||
subtitle.set(profile.getGameDir().toString());
|
||||
selected.set(Profiles.selectedProfileProperty().get() == profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,27 +44,35 @@ public class ProfileListItem extends Control {
|
||||
return new ProfileListItemSkin(this);
|
||||
}
|
||||
|
||||
public ToggleGroup getToggleGroup() {
|
||||
return toggleGroup;
|
||||
public void remove() {
|
||||
Profiles.getProfiles().remove(profile);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
Profiles.getProfiles().remove(profile);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -39,25 +38,23 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.setUserData(skinnable);
|
||||
chkSelected.selectedProperty().bindBidirectional(skinnable.selectedProperty());
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
|
||||
@@ -20,11 +20,13 @@ package org.jackhuang.hmcl.ui.profile;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -36,6 +38,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -60,11 +63,24 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
title = new ReadOnlyStringWrapper(this, "title",
|
||||
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(".minecraft"));
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/profile.fxml");
|
||||
|
||||
txtProfileName.setText(profileDisplayName);
|
||||
txtProfileName.getValidators().add(new ValidatorBase() {
|
||||
{
|
||||
setMessage(i18n("profile.already_exists"));
|
||||
}
|
||||
@Override
|
||||
protected void eval() {
|
||||
JFXTextField control = (JFXTextField) this.getSrcControl();
|
||||
if (Profiles.getProfiles().stream().anyMatch(profile -> profile.getName().equals(control.getText())))
|
||||
hasErrors.set(true);
|
||||
else
|
||||
hasErrors.set(false);
|
||||
}
|
||||
});
|
||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DatapackListItem extends BorderPane {
|
||||
|
||||
public DatapackListItem(Datapack.Pack info, Consumer<DatapackListItem> deleteCallback) {
|
||||
JFXCheckBox chkEnabled = new JFXCheckBox();
|
||||
BorderPane.setAlignment(chkEnabled, Pos.CENTER);
|
||||
setLeft(chkEnabled);
|
||||
|
||||
TwoLineListItem modItem = new TwoLineListItem();
|
||||
BorderPane.setAlignment(modItem, Pos.CENTER);
|
||||
setCenter(modItem);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
FXUtils.installTooltip(btnRemove, i18n("datapack.remove"));
|
||||
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
|
||||
setRight(btnRemove);
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
modItem.setTitle(info.getId());
|
||||
modItem.setSubtitle(info.getDescription());
|
||||
chkEnabled.selectedProperty().bindBidirectional(info.activeProperty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DatapackListPage extends ListPage<DatapackListItem> implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final Path worldDir;
|
||||
private final Datapack datapack;
|
||||
|
||||
public DatapackListPage(String worldName, Path worldDir) {
|
||||
this.worldDir = worldDir;
|
||||
|
||||
title.set(i18n("datapack.title", worldName));
|
||||
|
||||
datapack = new Datapack(worldDir.resolve("datapacks"));
|
||||
datapack.loadFromDir();
|
||||
|
||||
setItems(MappedObservableList.create(datapack.getInfo(),
|
||||
pack -> new DatapackListItem(pack, item -> {
|
||||
try {
|
||||
datapack.deletePack(pack);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.warning("Failed to delete datapack");
|
||||
}
|
||||
})));
|
||||
|
||||
setOnDragOver(event -> {
|
||||
if (event.getGestureSource() != this && event.getDragboard().hasFiles())
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
event.consume();
|
||||
});
|
||||
|
||||
setOnDragDropped(event -> {
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
Collection<File> mods = files.stream()
|
||||
.filter(it -> Objects.equals("zip", FileUtils.getExtension(it)))
|
||||
.collect(Collectors.toList());
|
||||
if (!mods.isEmpty()) {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
Datapack zip = new Datapack(it.toPath());
|
||||
zip.loadFromZip();
|
||||
zip.installTo(worldDir);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
|
||||
}
|
||||
});
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
}
|
||||
datapack.loadFromDir();
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("datapack.choose_datapack"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("datapack.extension"), "*.zip"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
if (res != null)
|
||||
res.forEach(it -> {
|
||||
try {
|
||||
Datapack zip = new Datapack(it.toPath());
|
||||
zip.loadFromZip();
|
||||
zip.installTo(worldDir);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
|
||||
}
|
||||
});
|
||||
|
||||
datapack.loadFromDir();
|
||||
}
|
||||
}
|
||||
@@ -17,67 +17,30 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHelper;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final WeakListenerHelper helper = new WeakListenerHelper();
|
||||
|
||||
private Profile profile;
|
||||
private InvalidationListener listener = o -> loadVersion();
|
||||
|
||||
public GameAdvancedListItem() {
|
||||
Profiles.selectedProfileProperty().addListener(helper.weak((a, b, newValue) -> {
|
||||
JFXUtilities.runInFX(() -> loadProfile(newValue));
|
||||
}));
|
||||
helper.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile != null && profile.getRepository() == event.getSource())
|
||||
loadVersion();
|
||||
});
|
||||
}));
|
||||
loadProfile(Profiles.getSelectedProfile());
|
||||
}
|
||||
|
||||
private void loadProfile(Profile newProfile) {
|
||||
if (profile != null)
|
||||
profile.selectedVersionProperty().removeListener(listener);
|
||||
profile = newProfile;
|
||||
if (profile != null)
|
||||
profile.selectedVersionProperty().addListener(listener);
|
||||
loadVersion();
|
||||
}
|
||||
|
||||
private void loadVersion() {
|
||||
Profile profile = this.profile;
|
||||
if (profile == null || !profile.getRepository().isLoaded()) return;
|
||||
String version = profile.getSelectedVersion();
|
||||
File iconFile = profile.getRepository().getVersionIcon(version);
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (iconFile.exists())
|
||||
imageProperty().set(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
imageProperty().set(new Image("/assets/img/grass.png"));
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
if (version != null) {
|
||||
titleProperty().set(version);
|
||||
subtitleProperty().set(null);
|
||||
setTitle(version);
|
||||
setSubtitle(null);
|
||||
setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
} else {
|
||||
titleProperty().set(i18n("version.empty"));
|
||||
subtitleProperty().set(i18n("version.empty.add"));
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
setImage(new Image("/assets/img/grass.png"));
|
||||
}
|
||||
});
|
||||
|
||||
setRightGraphic(SVG.gear(Theme.blackFillBinding(), -1, -1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameItem extends Control {
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
|
||||
public GameItem(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
this.version = id;
|
||||
|
||||
String game = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown");
|
||||
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
|
||||
|
||||
title.set(id);
|
||||
subtitle.set(libraries.toString());
|
||||
image.set(profile.getRepository().getVersionIconImage(version));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new GameItemSkin(this);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
private static String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
public class GameItemSkin extends SkinBase<GameItem> {
|
||||
public GameItemSkin(GameItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
getChildren().setAll(center);
|
||||
}
|
||||
}
|
||||
@@ -25,18 +25,20 @@ import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHelper;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -47,43 +49,34 @@ public class GameList extends Control implements DecoratorPage {
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(true);
|
||||
private final ListProperty<GameListItem> items = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
|
||||
private Profile profile;
|
||||
private ToggleGroup toggleGroup;
|
||||
|
||||
public GameList() {
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
loadVersions((HMCLGameRepository) event.getSource());
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
if (event.getSource() == Profiles.getSelectedProfile().getRepository())
|
||||
JFXUtilities.runInFX(() -> loading.set(true));
|
||||
});
|
||||
Profiles.selectedProfileProperty().addListener((a, b, newValue) -> profile = newValue);
|
||||
|
||||
profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded())
|
||||
loadVersions(profile.getRepository());
|
||||
else
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
private void loadVersions(Profile profile) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
toggleGroup = new ToggleGroup();
|
||||
WeakListenerHelper helper = new WeakListenerHelper();
|
||||
toggleGroup.getProperties().put("ReferenceHolder", helper);
|
||||
WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
toggleGroup.getProperties().put("ReferenceHolder", listenerHolder);
|
||||
List<GameListItem> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.sorted(Comparator.comparing(Version::getReleaseTime).thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(version -> new GameListItem(toggleGroup, profile, version.getId()))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == repository.getProfile()) {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
loading.set(false);
|
||||
items.setAll(children);
|
||||
children.forEach(GameListItem::checkSelection);
|
||||
|
||||
profile.selectedVersionProperty().addListener(helper.weak((a, b, newValue) -> {
|
||||
profile.selectedVersionProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
children.forEach(it -> it.selectedProperty().set(false));
|
||||
children.stream()
|
||||
@@ -107,19 +100,19 @@ public class GameList extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
public void addNewGame() {
|
||||
Controllers.getDecorator().startWizard(new DownloadWizardProvider(0), i18n("install.new_game"));
|
||||
Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(), i18n("install.new_game"));
|
||||
}
|
||||
|
||||
public void importModpack() {
|
||||
Controllers.getDecorator().startWizard(new DownloadWizardProvider(1), i18n("install.modpack"));
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(), i18n("install.modpack"));
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Profiles.getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
public void modifyGlobalGameSettings() {
|
||||
Versions.modifyGlobalSettings(profile);
|
||||
Versions.modifyGlobalSettings(Profiles.getSelectedProfile());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,31 +17,19 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameListItem extends Control {
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
private final boolean isModpack;
|
||||
private final ToggleGroup toggleGroup;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
|
||||
public GameListItem(ToggleGroup toggleGroup, Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
@@ -49,23 +37,7 @@ public class GameListItem extends Control {
|
||||
this.toggleGroup = toggleGroup;
|
||||
this.isModpack = profile.getRepository().isModpack(id);
|
||||
|
||||
String game = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown");
|
||||
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
|
||||
|
||||
title.set(id);
|
||||
subtitle.set(libraries.toString());
|
||||
selected.set(profile.getSelectedVersion().equals(id));
|
||||
|
||||
File iconFile = profile.getRepository().getVersionIcon(version);
|
||||
if (iconFile.exists())
|
||||
image.set(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
image.set(new Image("/assets/img/grass.png"));
|
||||
selected.set(id.equals(profile.getSelectedVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,22 +57,10 @@ public class GameListItem extends Control {
|
||||
return version;
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void checkSelection() {
|
||||
selected.set(version.equals(profile.getSelectedVersion()));
|
||||
}
|
||||
@@ -122,12 +82,11 @@ public class GameListItem extends Control {
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
Versions.launch(profile, version);
|
||||
Versions.testGame(profile, version);
|
||||
}
|
||||
|
||||
public void modifyGameSettings() {
|
||||
Controllers.getVersionPage().load(version, profile);
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
Versions.modifyGameSettings(profile, version);
|
||||
}
|
||||
|
||||
public void generateLaunchScript() {
|
||||
@@ -141,8 +100,4 @@ public class GameListItem extends Control {
|
||||
public void update() {
|
||||
Versions.updateVersion(profile, version);
|
||||
}
|
||||
|
||||
private static String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
}
|
||||
@@ -19,27 +19,19 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import org.jackhuang.hmcl.ui.construct.MenuSeparator;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -57,48 +49,22 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
root.setCenter(new GameItem(skinnable.getProfile(), skinnable.getVersion()));
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
|
||||
VBox menu = new VBox();
|
||||
PopupMenu menu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
Function<Runnable, Runnable> wrap = r -> () -> {
|
||||
r.run();
|
||||
popup.hide();
|
||||
};
|
||||
|
||||
Function<Node, Node> limitWidth = node -> {
|
||||
StackPane pane = new StackPane(node);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(pane, 14);
|
||||
FXUtils.setLimitHeight(pane, 14);
|
||||
return pane;
|
||||
};
|
||||
|
||||
menu.getChildren().setAll(
|
||||
new IconedMenuItem(limitWidth.apply(SVG.gear(Theme.blackFillBinding(), 14, 14)), i18n("settings"), wrap.apply(skinnable::modifyGameSettings)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.pencil(Theme.blackFillBinding(), 14, 14)), i18n("version.manage.rename"), wrap.apply(skinnable::rename)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.delete(Theme.blackFillBinding(), 14, 14)), i18n("version.manage.remove"), wrap.apply(skinnable::remove)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.export(Theme.blackFillBinding(), 14, 14)), i18n("modpack.export"), wrap.apply(skinnable::export)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.folderOpen(Theme.blackFillBinding(), 14, 14)), i18n("folder.game"), wrap.apply(skinnable::browse)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.launch(Theme.blackFillBinding(), 14, 14)), i18n("version.launch"), wrap.apply(skinnable::launch)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.script(Theme.blackFillBinding(), 14, 14)), i18n("version.launch_script"), wrap.apply(skinnable::generateLaunchScript)));
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(skinnable::modifyGameSettings, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(skinnable::rename, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(skinnable::remove, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)));
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
@@ -123,8 +89,6 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class InstallerListPage extends ListPage<InstallerItem> {
|
||||
.start();
|
||||
};
|
||||
|
||||
itemsProperty().clear();
|
||||
analyzer.getForge().ifPresent(library -> itemsProperty().add(new InstallerItem("Forge", library.getVersion(), removeAction.apply(library))));
|
||||
analyzer.getLiteLoader().ifPresent(library -> itemsProperty().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction.apply(library))));
|
||||
analyzer.getOptiFine().ifPresent(library -> itemsProperty().add(new InstallerItem("OptiFine", library.getVersion(), removeAction.apply(library))));
|
||||
|
||||
+3
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@@ -25,6 +25,8 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
@@ -23,14 +23,14 @@ import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.ModItem;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -78,6 +78,10 @@ public final class ModListPage extends ListPage<ModItem> {
|
||||
});
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
loadMods(profile.getModManager(), id);
|
||||
}
|
||||
|
||||
public void loadMods(ModManager modManager, String versionId) {
|
||||
this.modManager = modManager;
|
||||
this.versionId = versionId;
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -28,9 +28,15 @@ import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -48,9 +54,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private InstallerListPage installer;
|
||||
@FXML
|
||||
private JFXListView<?> browseList;
|
||||
@FXML
|
||||
private JFXListView<?> managementList;
|
||||
private WorldListPage world;
|
||||
@FXML
|
||||
private JFXButton btnBrowseMenu;
|
||||
@FXML
|
||||
@@ -60,6 +64,8 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private JFXButton btnExport;
|
||||
@FXML
|
||||
private JFXButton btnTestGame;
|
||||
@FXML
|
||||
private StackPane rootPane;
|
||||
@FXML
|
||||
private StackPane contentPane;
|
||||
@@ -75,93 +81,85 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml");
|
||||
|
||||
getChildren().removeAll(browseList, managementList);
|
||||
|
||||
PopupMenu browseList = new PopupMenu();
|
||||
browsePopup = new JFXPopup(browseList);
|
||||
browseList.getContent().setAll(
|
||||
new IconedMenuItem(null, i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> onBrowse(""), browsePopup)),
|
||||
new IconedMenuItem(null, i18n("folder.mod"), FXUtils.withJFXPopupClosing(() -> onBrowse("mods"), browsePopup)),
|
||||
new IconedMenuItem(null, i18n("folder.config"), FXUtils.withJFXPopupClosing(() -> onBrowse("config"), browsePopup)),
|
||||
new IconedMenuItem(null, i18n("folder.resourcepacks"), FXUtils.withJFXPopupClosing(() -> onBrowse("resourcepacks"), browsePopup)),
|
||||
new IconedMenuItem(null, i18n("folder.screenshots"), FXUtils.withJFXPopupClosing(() -> onBrowse("screenshots"), browsePopup)),
|
||||
new IconedMenuItem(null, i18n("folder.saves"), FXUtils.withJFXPopupClosing(() -> onBrowse("resourcepacks"), browsePopup))
|
||||
);
|
||||
|
||||
PopupMenu managementList = new PopupMenu();
|
||||
managementPopup = new JFXPopup(managementList);
|
||||
managementList.getContent().setAll(
|
||||
new IconedMenuItem(null, i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> Versions.renameVersion(profile, version), managementPopup)),
|
||||
new IconedMenuItem(null, i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> Versions.deleteVersion(profile, version), managementPopup)),
|
||||
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(() -> new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getResolvedVersion(version)).start(), managementPopup)),
|
||||
new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(() -> FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries")), managementPopup))
|
||||
);
|
||||
|
||||
FXUtils.installTooltip(btnDelete, i18n("version.manage.remove"));
|
||||
FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
|
||||
FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management"));
|
||||
FXUtils.installTooltip(btnExport, i18n("modpack.export"));
|
||||
|
||||
btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
|
||||
FXUtils.installTooltip(btnTestGame, i18n("version.launch.test"));
|
||||
|
||||
setEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
}
|
||||
|
||||
public void load(String id, Profile profile) {
|
||||
this.version = id;
|
||||
this.profile = profile;
|
||||
|
||||
title.set(i18n("settings.game") + " - " + id);
|
||||
title.set(i18n("version.manage.manage") + " - " + id);
|
||||
|
||||
versionSettings.loadVersionSetting(profile, id);
|
||||
versionSettings.loadVersion(profile, id);
|
||||
mod.setParentTab(tabPane);
|
||||
modTab.setUserData(mod);
|
||||
mod.loadMods(profile.getModManager(), id);
|
||||
mod.loadVersion(profile, id);
|
||||
installer.loadVersion(profile, id);
|
||||
world.loadVersion(profile, id);
|
||||
}
|
||||
|
||||
private void onNavigated(Navigator.NavigationEvent event) {
|
||||
if (this.version == null || this.profile == null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
// If we jumped to game list page and deleted this version
|
||||
// and back to this page, we should return to main page.
|
||||
if (!this.profile.getRepository().isLoaded() ||
|
||||
!this.profile.getRepository().hasVersion(version)) {
|
||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||
return;
|
||||
}
|
||||
|
||||
load(this.version, this.profile);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onTestGame() {
|
||||
Versions.testGame(profile, version);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBrowseMenu() {
|
||||
browseList.getSelectionModel().select(-1);
|
||||
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
|
||||
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnBrowseMenu.getHeight());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onManagementMenu() {
|
||||
managementList.getSelectionModel().select(-1);
|
||||
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
|
||||
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnManagementMenu.getHeight());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBrowse() {
|
||||
String sub;
|
||||
switch (browseList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
sub = "";
|
||||
break;
|
||||
case 1:
|
||||
sub = "mods";
|
||||
break;
|
||||
case 2:
|
||||
sub = "coremods";
|
||||
break;
|
||||
case 3:
|
||||
sub = "config";
|
||||
break;
|
||||
case 4:
|
||||
sub = "resourcepacks";
|
||||
break;
|
||||
case 5:
|
||||
sub = "screenshots";
|
||||
break;
|
||||
case 6:
|
||||
sub = "saves";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
private void onBrowse(String sub) {
|
||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onManagement() {
|
||||
switch (managementList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0: // rename a version
|
||||
Versions.renameVersion(profile, version);
|
||||
break;
|
||||
case 1: // remove a version
|
||||
Versions.deleteVersion(profile, version);
|
||||
break;
|
||||
case 2: // redownload asset index
|
||||
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getResolvedVersion(version)).start();
|
||||
break;
|
||||
case 3: // delete libraries
|
||||
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
|
||||
break;
|
||||
case 4:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@@ -46,13 +46,14 @@ import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ImagePickerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -106,11 +107,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
Task.of(variables -> variables.set("list", JavaVersion.getJREs()))
|
||||
Task.of(variables -> variables.set("list", JavaVersion.getJavas()))
|
||||
.subscribe(Schedulers.javafx(), variables -> {
|
||||
javaItem.loadChildren(
|
||||
(variables.<List<JavaVersion>>get("list")).stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion))
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit", javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
javaItemsLoaded = true;
|
||||
initializeSelectedJava();
|
||||
@@ -134,11 +135,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
// because versionSettings can be the global one.
|
||||
// global versionSettings.usesGlobal is always true.
|
||||
if (newValue)
|
||||
profile.specializeVersionSetting(versionId);
|
||||
profile.getRepository().specializeVersionSetting(versionId);
|
||||
else
|
||||
profile.globalizeVersionSetting(versionId);
|
||||
profile.getRepository().globalizeVersionSetting(versionId);
|
||||
|
||||
Platform.runLater(() -> loadVersionSetting(profile, versionId));
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
});
|
||||
|
||||
specificSettingsListener = o -> {
|
||||
@@ -149,7 +150,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
advancedSettingsPane.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not());
|
||||
}
|
||||
|
||||
public void loadVersionSetting(Profile profile, String versionId) {
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
|
||||
@@ -245,7 +246,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
if (lastVersionSetting.isUsesCustomJavaDir()) {
|
||||
javaItem.setSelectedData(null);
|
||||
javaItem.getGroup().getToggles().stream()
|
||||
.filter(javaItem::isCustomToggle)
|
||||
.findFirst().get()
|
||||
.setSelected(true);
|
||||
} else {
|
||||
try {
|
||||
javaItem.setSelectedData(lastVersionSetting.getJavaVersion());
|
||||
@@ -259,7 +263,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
|
||||
.subscribe(Task.of(Schedulers.javafx(),
|
||||
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java")
|
||||
.map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory"))));
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Directory"))));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -276,7 +280,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
try {
|
||||
FileUtils.copyFile(selectedFile, iconFile);
|
||||
loadIcon();
|
||||
@@ -291,7 +295,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
if (versionId == null)
|
||||
return;
|
||||
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
if (iconFile.exists())
|
||||
iconFile.delete();
|
||||
loadIcon();
|
||||
@@ -303,7 +307,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
return;
|
||||
}
|
||||
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
if (iconFile.exists())
|
||||
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -36,8 +35,8 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -92,24 +91,18 @@ public class Versions {
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
} catch (UnsupportedModpackException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateLaunchScript(Profile profile, String id) {
|
||||
GameRepository repository = profile.getRepository();
|
||||
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else {
|
||||
if (checkForLaunching(profile, id)) {
|
||||
GameRepository repository = profile.getRepository();
|
||||
FileChooser chooser = new FileChooser();
|
||||
if (repository.getRunDirectory(id).isDirectory())
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
@@ -119,22 +112,41 @@ public class Versions {
|
||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, file);
|
||||
new LauncherHelper(profile, Accounts.getSelectedAccount(), id).makeLaunchScript(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static void launch(Profile profile, String id) {
|
||||
if (checkForLaunching(profile, id))
|
||||
new LauncherHelper(profile, Accounts.getSelectedAccount(), id).launch();
|
||||
}
|
||||
|
||||
public static void testGame(Profile profile, String id) {
|
||||
if (checkForLaunching(profile, id)) {
|
||||
LauncherHelper helper = new LauncherHelper(profile, Accounts.getSelectedAccount(), id);
|
||||
helper.setTestMode();
|
||||
helper.launch();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkForLaunching(Profile profile, String id) {
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
else if (id == null)
|
||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
||||
Controllers.dialog(i18n("version.empty.launch"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void modifyGlobalSettings(Profile profile) {
|
||||
VersionSettingsPage page = new VersionSettingsPage();
|
||||
page.loadVersionSetting(profile, null);
|
||||
page.loadVersion(profile, null);
|
||||
Controllers.navigate(page);
|
||||
}
|
||||
|
||||
public static void modifyGameSettings(Profile profile, String version) {
|
||||
Controllers.getVersionPage().load(version, profile);
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Skin;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardSinglePage;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldExportPage extends WizardSinglePage {
|
||||
private final StringProperty path = new SimpleStringProperty();
|
||||
private final StringProperty gameVersion = new SimpleStringProperty();
|
||||
private final StringProperty worldName = new SimpleStringProperty();
|
||||
private final World world;
|
||||
|
||||
public WorldExportPage(World world, Path export, Runnable onFinish) {
|
||||
super(onFinish);
|
||||
|
||||
this.world = world;
|
||||
|
||||
path.set(export.toString());
|
||||
gameVersion.set(world.getGameVersion());
|
||||
worldName.set(world.getWorldName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new WorldExportPageSkin(this);
|
||||
}
|
||||
|
||||
|
||||
public StringProperty pathProperty() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public StringProperty gameVersionProperty() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public StringProperty worldNameProperty() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
public void export() {
|
||||
onFinish.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return i18n("world.export.wizard", world.getFileName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object finish() {
|
||||
return Task.of(i18n("world.export.wizard", worldName.get()), () -> world.export(Paths.get(path.get()), worldName.get()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldExportPageSkin extends SkinBase<WorldExportPage> {
|
||||
|
||||
public WorldExportPageSkin(WorldExportPage skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
Insets insets = new Insets(0, 0, 12, 0);
|
||||
VBox container = new VBox();
|
||||
container.setSpacing(16);
|
||||
container.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(container, 500);
|
||||
{
|
||||
HBox labelContainer = new HBox();
|
||||
labelContainer.setPadding(new Insets(0, 0, 0, 5));
|
||||
Label label = new Label(i18n("world.export"));
|
||||
labelContainer.getChildren().setAll(label);
|
||||
container.getChildren().add(labelContainer);
|
||||
}
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(i18n("world.export.location"));
|
||||
fileItem.pathProperty().bindBidirectional(skinnable.pathProperty());
|
||||
list.getContent().add(fileItem);
|
||||
|
||||
JFXTextField txtWorldName = new JFXTextField();
|
||||
txtWorldName.textProperty().bindBidirectional(skinnable.worldNameProperty());
|
||||
txtWorldName.setLabelFloat(true);
|
||||
txtWorldName.setPromptText(i18n("world.name"));
|
||||
StackPane.setMargin(txtWorldName, insets);
|
||||
list.getContent().add(txtWorldName);
|
||||
|
||||
Label lblGameVersionTitle = new Label(i18n("world.game_version"));
|
||||
Label lblGameVersion = new Label();
|
||||
lblGameVersion.textProperty().bind(skinnable.gameVersionProperty());
|
||||
BorderPane gameVersionPane = new BorderPane();
|
||||
gameVersionPane.setPadding(new Insets(4, 0, 4, 0));
|
||||
gameVersionPane.setLeft(lblGameVersionTitle);
|
||||
gameVersionPane.setRight(lblGameVersion);
|
||||
list.getContent().add(gameVersionPane);
|
||||
|
||||
container.getChildren().add(list);
|
||||
|
||||
JFXButton btnExport = new JFXButton(i18n("button.export"));
|
||||
btnExport.disableProperty().bind(Bindings.createBooleanBinding(() -> txtWorldName.getText().isEmpty() || Files.exists(Paths.get(fileItem.getPath())),
|
||||
txtWorldName.textProperty().isEmpty(), fileItem.pathProperty()));
|
||||
btnExport.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnExport.getStyleClass().add("jfx-button-raised");
|
||||
btnExport.setOnMouseClicked(e -> skinnable.export());
|
||||
HBox bottom = new HBox();
|
||||
bottom.setAlignment(Pos.CENTER_RIGHT);
|
||||
bottom.getChildren().setAll(btnExport);
|
||||
container.getChildren().add(bottom);
|
||||
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItem extends Control {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
private final World world;
|
||||
private final SimpleDateFormat simpleDateFormat;
|
||||
|
||||
public WorldListItem(World world) {
|
||||
this.world = world;
|
||||
this.simpleDateFormat = new SimpleDateFormat(i18n("world.time"));
|
||||
|
||||
title.set(world.getWorldName());
|
||||
subtitle.set(i18n("world.description", world.getFileName(), simpleDateFormat.format(new Date(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new WorldListItemSkin(this);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void export() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("world.export.title"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("world"), "*.zip"));
|
||||
fileChooser.setInitialFileName(world.getWorldName());
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new WorldExportPage(world, file.toPath(), controller::onFinish)));
|
||||
}
|
||||
|
||||
public void manageDatapacks() {
|
||||
if (world.getGameVersion() == null || // old game will not write game version to level.dat
|
||||
(VersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
|
||||
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
Controllers.dialog(i18n("world.datapack.1_13"));
|
||||
return;
|
||||
}
|
||||
Controllers.navigate(new DatapackListPage(world.getWorldName(), world.getFile()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
|
||||
public WorldListItemSkin(WorldListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
|
||||
PopupMenu menu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("world.datapack"), FXUtils.withJFXPopupClosing(skinnable::manageDatapacks, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("world.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)));
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnManage = new JFXButton();
|
||||
btnManage.setOnMouseClicked(e -> {
|
||||
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(SVG.dotsVertical(Theme.blackFillBinding(), -1, -1));
|
||||
right.getChildren().add(btnManage);
|
||||
root.setRight(right);
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListPage extends ListPage<WorldListItem> {
|
||||
private Path savesDir;
|
||||
|
||||
public WorldListPage() {
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||
|
||||
itemsProperty().setAll(World.getWorlds(savesDir).stream()
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("world.import.choose"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("world.extension"), "*.zip"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
if (res == null || res.isEmpty()) return;
|
||||
try {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
World world = new World(res.get(0).toPath());
|
||||
|
||||
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.of(() -> world.install(savesDir, name))
|
||||
.finalized(Schedulers.javafx(), var -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
if (e instanceof FileAlreadyExistsException)
|
||||
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
|
||||
else
|
||||
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
|
||||
}).start();
|
||||
}).setInitialText(world.getWorldName());
|
||||
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + res.get(0), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SinglePageWizardProvider implements WizardProvider {
|
||||
|
||||
private final Function<WizardController, WizardSinglePage> provider;
|
||||
private WizardSinglePage page;
|
||||
|
||||
public SinglePageWizardProvider(Function<WizardController, WizardSinglePage> provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
return page.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
if (step != 0) throw new IllegalStateException("Step must be 0");
|
||||
|
||||
return page = provider.apply(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.control.Control;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class WizardSinglePage extends Control implements WizardPage {
|
||||
protected final Runnable onFinish;
|
||||
|
||||
protected WizardSinglePage(Runnable onFinish) {
|
||||
this.onFinish = onFinish;
|
||||
}
|
||||
|
||||
protected abstract Object finish();
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -30,6 +28,8 @@ import java.util.Optional;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.*;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -21,8 +21,8 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -24,7 +24,7 @@ import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
@@ -35,7 +35,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
|
||||
import static org.jackhuang.hmcl.util.versioning.VersionNumber.asVersion;
|
||||
|
||||
public final class UpdateChecker {
|
||||
private UpdateChecker() {}
|
||||
@@ -83,7 +83,7 @@ public final class UpdateChecker {
|
||||
}
|
||||
|
||||
private static RemoteVersion checkUpdate(UpdateChannel channel) throws IOException {
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
if (!IntegrityChecker.isSelfVerified() && !"true".equals(System.getProperty("hmcl.self_integrity_check.disable"))) {
|
||||
throw new IOException("Self verification failed");
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
@@ -28,9 +29,9 @@ 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.JarUtils;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
@@ -158,7 +159,7 @@ public final class UpdateHandler {
|
||||
|
||||
private static void startJava(Path jar, String... appArgs) throws IOException {
|
||||
List<String> commandline = new ArrayList<>();
|
||||
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath());
|
||||
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
commandline.add("-jar");
|
||||
commandline.add(jar.toAbsolutePath().toString());
|
||||
for (String arg : appArgs) {
|
||||
@@ -239,18 +240,20 @@ public final class UpdateHandler {
|
||||
}
|
||||
|
||||
private static void breakForceUpdateFeature() {
|
||||
try {
|
||||
Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
|
||||
if (Files.isRegularFile(hmclVersionJson)) {
|
||||
Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
|
||||
if (Files.isRegularFile(hmclVersionJson)) {
|
||||
try {
|
||||
Map<?, ?> content = new Gson().fromJson(new String(Files.readAllBytes(hmclVersionJson), UTF_8), Map.class);
|
||||
Object ver = content.get("ver");
|
||||
if (ver instanceof String && ((String) ver).startsWith("3.")) {
|
||||
Files.delete(hmclVersionJson);
|
||||
LOG.info("Successfully broke the force update feature");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to break the force update feature", e);
|
||||
} catch (JsonParseException e) {
|
||||
hmclVersionJson.toFile().delete();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to break the force update feature", e);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
|
||||
@@ -23,6 +23,8 @@ import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.root {
|
||||
-fx-base-color: #5c6bc0;
|
||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: white;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
.root {
|
||||
-fx-base-color: %base-color%;
|
||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: %font-color%;
|
||||
}
|
||||
Loaded 100 of 266 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user