Compare commits
101
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a141c6b137 | ||
|
|
300efb99d2 | ||
|
|
6fb3610bcc | ||
|
|
43fb3ca10a | ||
|
|
adc360eaaa | ||
|
|
5e659352d7 | ||
|
|
6595e0a3cf | ||
|
|
c43b2b3bb9 | ||
|
|
ea19e6efd8 | ||
|
|
55d38f2b36 | ||
|
|
e0abf3e0c1 | ||
|
|
974cba8dee | ||
|
|
019cf5fc8f | ||
|
|
1956ba7a73 | ||
|
|
c209617ee7 | ||
|
|
f4b5e061a4 | ||
|
|
06b67a4b9a | ||
|
|
a385265956 | ||
|
|
6ac32a40c7 | ||
|
|
20c612c660 | ||
|
|
7e8cc5fe6c | ||
|
|
9ed14b227b | ||
|
|
ee466d0f4c | ||
|
|
b198eea9e4 | ||
|
|
8c7580488e | ||
|
|
3f4364c67c | ||
|
|
23e2df5e58 | ||
|
|
79136db162 | ||
|
|
3d008dcab5 | ||
|
|
5953cc6411 | ||
|
|
2dce675fe2 | ||
|
|
aa013a86dd | ||
|
|
f866f692e2 | ||
|
|
68a2514cef | ||
|
|
32562d7435 | ||
|
|
78be1aaa7c | ||
|
|
1eb4b1ca9e | ||
|
|
bf37db2cbe | ||
|
|
5b30617a6b | ||
|
|
266c686a3a | ||
|
|
dc1df98277 | ||
|
|
def45cc3e4 | ||
|
|
8007f1958e | ||
|
|
b22e724a3f | ||
|
|
ed66b016db | ||
|
|
34890f21b3 | ||
|
|
9f85e4a63b | ||
|
|
7fcd4d94a6 | ||
|
|
84fbca7905 | ||
|
|
9add2387b9 | ||
|
|
a388450f94 | ||
|
|
fff7abc2ac | ||
|
|
e5587418c7 | ||
|
|
7c89fac1e9 | ||
|
|
bc9a9ffb72 | ||
|
|
46d27a04a2 | ||
|
|
0f33981c96 | ||
|
|
b396e0b208 | ||
|
|
1bf2f17538 | ||
|
|
608a531655 | ||
|
|
e57180eb15 | ||
|
|
3ae826d9ca | ||
|
|
e940845c60 | ||
|
|
d8aa989cc4 | ||
|
|
31ed4268b6 | ||
|
|
c698eaac6f | ||
|
|
d0dd23fa14 | ||
|
|
c6f1aa21c5 | ||
|
|
a3dc7798e7 | ||
|
|
88f6fd1965 | ||
|
|
9062c2e447 | ||
|
|
e3d2a4bf9d | ||
|
|
c2d609cdb4 | ||
|
|
b3721e29c6 | ||
|
|
eab4463c2d | ||
|
|
394873864f | ||
|
|
758216245f | ||
|
|
6edfd112a2 | ||
|
|
625215b068 | ||
|
|
4759e4957c | ||
|
|
b62f9494ae | ||
|
|
12b47e7d51 | ||
|
|
9ced29aa93 | ||
|
|
5095640ba2 | ||
|
|
c889d7cb3f | ||
|
|
84636f3f50 | ||
|
|
9a6f07d7c9 | ||
|
|
e8f5088049 | ||
|
|
0bee306d67 | ||
|
|
8d75b2d413 | ||
|
|
426ea607e8 | ||
|
|
507cade50d | ||
|
|
f0c7f54cc6 | ||
|
|
62c38482f6 | ||
|
|
0ff6ca18ed | ||
|
|
bbe6c388a3 | ||
|
|
639bc58e84 | ||
|
|
c0bcafd747 | ||
|
|
bcaa019551 | ||
|
|
38725abc48 | ||
|
|
68a02966d0 |
No files matched your search
@@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 HMCL 3 用户 QQ 群:219177735 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 HMCL 3 用户 QQ 群:219177735 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
|
||||
如果你希望在 QQ 群中反馈问题,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
|
||||
@@ -24,5 +24,5 @@
|
||||
* 对游戏做的修改(是否自行通过安装器安装 Rift 等 API,以及 mod):
|
||||
* 游戏崩溃报告(如果有):
|
||||
* 启动器崩溃报告(如果有):
|
||||
* 启动器日志文件(在启动器设置中打开日志文件夹并将文件夹内所有文件打包发在这里):
|
||||
* 问题描述(如何触发问题):
|
||||
* 启动器日志文件(出现问题后点击启动器设置中的【导出启动器日志】按钮):
|
||||
* 问题描述(如何触发问题):
|
||||
+2
-2
@@ -112,7 +112,7 @@ def createExecutable(String suffix, String header) {
|
||||
|
||||
processResources {
|
||||
ext.convertToBSS = { String resource ->
|
||||
exclude resource
|
||||
// exclude resource
|
||||
doFirst {
|
||||
def cssFile = new File(this.projectDir, "src/main/resources/" + resource)
|
||||
def bssFile = new File(this.projectDir, "build/compiled-resources/" + resource[0..-4] + "bss")
|
||||
@@ -140,7 +140,7 @@ task makePack(dependsOn: jar) {
|
||||
|
||||
task makePackXz(dependsOn: makePack) doLast {
|
||||
def packXz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".xz")
|
||||
new XZOutputStream(packXz.newOutputStream(), new LZMA2Options(6)).withCloseable { it << makePack.outputPath.bytes }
|
||||
new XZOutputStream(packXz.newOutputStream(), new LZMA2Options(5)).withCloseable { it << makePack.outputPath.bytes }
|
||||
createChecksum(packXz)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
@@ -25,7 +24,9 @@ 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.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
@@ -39,6 +40,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -97,7 +99,7 @@ public final class Launcher extends Application {
|
||||
public static void stopApplication() {
|
||||
LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
@@ -111,7 +113,7 @@ public final class Launcher extends Application {
|
||||
public static void stopWithoutPlatform() {
|
||||
LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
|
||||
@@ -35,6 +35,7 @@ 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 CHANGELOG_URL = "https://hmcl.huangyuhui.net/changelog/";
|
||||
public static final String PUBLISH_URL = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
|
||||
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
|
||||
@@ -20,9 +20,6 @@ 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;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
@@ -34,6 +31,8 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
|
||||
public class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
||||
@@ -80,13 +79,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshVersions() {
|
||||
EventBus.EVENT_BUS.fireEvent(new RefreshingVersionsEvent(this));
|
||||
refreshVersionsImpl();
|
||||
EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this));
|
||||
}
|
||||
|
||||
public void changeDirectory(File newDirectory) {
|
||||
setBaseDirectory(newDirectory);
|
||||
refreshVersionsAsync().start();
|
||||
@@ -160,16 +152,18 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public Image getVersionIconImage(String id) {
|
||||
if (id == null || !isLoaded())
|
||||
return new Image("/assets/img/grass.png");
|
||||
return newImage("/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 if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass())
|
||||
|| version.getMainClass().startsWith("net.fabricmc")
|
||||
|| "cpw.mods.modlauncher.Launcher".equals(version.getMainClass()))
|
||||
return newImage("/assets/img/furnace.png");
|
||||
else
|
||||
return new Image("/assets/img/grass.png");
|
||||
return newImage("/assets/img/grass.png");
|
||||
}
|
||||
|
||||
public boolean saveVersionSetting(String id) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
@@ -38,7 +39,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -122,14 +123,14 @@ public final class LauncherHelper {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(variables -> {
|
||||
.then(() -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
else
|
||||
return dependencyManager.checkGameCompletionAsync(version);
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(var -> {
|
||||
.then(() -> {
|
||||
try {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
@@ -141,44 +142,42 @@ public final class LauncherHelper {
|
||||
}
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(i18n("account.methods"), variables -> {
|
||||
.thenCompose(() -> Task.ofResult(i18n("account.methods"), () -> {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
return account.logIn();
|
||||
} catch (CredentialExpiredException e) {
|
||||
LOG.info("Credential has expired: " + e);
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
return DialogController.logIn(account);
|
||||
} catch (AuthenticationException e) {
|
||||
LOG.warning("Authentication failed, try playing offline: " + e);
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
return account.playOffline().orElseThrow(() -> e);
|
||||
}
|
||||
}))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
|
||||
.then(Task.of(variables -> {
|
||||
variables.set("launcher", new HMCLGameLauncher(
|
||||
repository,
|
||||
selectedVersion,
|
||||
variables.get("account"),
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
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())
|
||||
));
|
||||
}))
|
||||
.then(variables -> {
|
||||
DefaultLauncher launcher = variables.get("launcher");
|
||||
.thenApply(Schedulers.javafx(), authInfo -> {
|
||||
emitStatus(LoadingState.LAUNCHING);
|
||||
return authInfo;
|
||||
})
|
||||
.thenApply(authInfo -> new HMCLGameLauncher(
|
||||
repository,
|
||||
selectedVersion,
|
||||
authInfo,
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(authInfo, setting, gameVersion.isPresent())
|
||||
))
|
||||
.thenCompose(launcher -> { // launcher is prev task's result
|
||||
if (scriptFile == null) {
|
||||
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
|
||||
} else {
|
||||
return new LaunchTask<>(() -> {
|
||||
return new LaunchTask<ManagedProcess>(() -> {
|
||||
launcher.makeLaunchScript(scriptFile);
|
||||
return null;
|
||||
}).setName(i18n("version.launch_script"));
|
||||
}
|
||||
})
|
||||
.then(Task.of(variables -> {
|
||||
.thenAccept(process -> { // process is LaunchTask's result
|
||||
if (scriptFile == null) {
|
||||
ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID);
|
||||
PROCESSES.add(process);
|
||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
@@ -187,13 +186,13 @@ public final class LauncherHelper {
|
||||
process.stop();
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
} else
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
}))
|
||||
}
|
||||
})
|
||||
.executor();
|
||||
|
||||
launchingStepsPane.setExecutor(executor, false);
|
||||
@@ -236,7 +235,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
Controllers.dialog(message,
|
||||
scriptFile == null ? i18n("launch.failed") : i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE);
|
||||
MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -263,7 +262,7 @@ public final class LauncherHelper {
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
|
||||
JavaVersion java = setting.getJavaVersion();
|
||||
if (java == null) {
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
@@ -282,10 +281,10 @@ public final class LauncherHelper {
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, null);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
@@ -301,10 +300,10 @@ public final class LauncherHelper {
|
||||
if (java8.isPresent()) {
|
||||
java8required = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageBox.INFORMATION_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFORMATION, onAccept);
|
||||
flag = true;
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -319,7 +318,7 @@ public final class LauncherHelper {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -351,7 +350,7 @@ public final class LauncherHelper {
|
||||
if (java64.isPresent()) {
|
||||
setting.setJavaVersion(java64.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -361,13 +360,13 @@ public final class LauncherHelper {
|
||||
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);
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageType.ERROR, 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);
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
@@ -379,7 +378,7 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 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);
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -400,7 +399,10 @@ public final class LauncherHelper {
|
||||
private void checkExit() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(Controllers.getStage()::show);
|
||||
Platform.runLater(() -> {
|
||||
Optional.ofNullable(Controllers.getStage())
|
||||
.ifPresent(Stage::show);
|
||||
});
|
||||
break;
|
||||
case KEEP:
|
||||
// No operations here
|
||||
@@ -429,13 +431,6 @@ public final class LauncherHelper {
|
||||
public void execute() throws Exception {
|
||||
setResult(supplier.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return LAUNCH_ID;
|
||||
}
|
||||
|
||||
static final String LAUNCH_ID = "launch";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,13 +110,13 @@ public final class ModpackHelper {
|
||||
|
||||
if (modpack.getManifest() instanceof CurseManifest)
|
||||
return new CurseInstallTask(profile.getDependency(), zipFile, modpack, ((CurseManifest) modpack.getManifest()), name)
|
||||
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure);
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure);
|
||||
else if (modpack.getManifest() instanceof HMCLModpackManifest)
|
||||
return new HMCLModpackInstallTask(profile, zipFile, modpack, name)
|
||||
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure);
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure);
|
||||
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
@@ -17,11 +17,21 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -33,27 +43,16 @@ import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
@@ -69,8 +68,8 @@ public final class TexturesLoader {
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
public LoadedTexture(BufferedImage image, Map<String, String> metadata) {
|
||||
this.image = image;
|
||||
this.metadata = metadata;
|
||||
this.image = requireNonNull(image);
|
||||
this.metadata = requireNonNull(metadata);
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
@@ -98,6 +97,10 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
public static LoadedTexture loadTexture(Texture texture) throws IOException {
|
||||
if (StringUtils.isBlank(texture.getUrl())) {
|
||||
throw new IOException("Texture url is empty");
|
||||
}
|
||||
|
||||
Path file = getTexturePath(texture);
|
||||
if (!Files.isRegularFile(file)) {
|
||||
// download it
|
||||
@@ -118,7 +121,11 @@ public final class TexturesLoader {
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
img = ImageIO.read(in);
|
||||
}
|
||||
return new LoadedTexture(img, texture.getMetadata());
|
||||
Map<String, String> metadata = texture.getMetadata();
|
||||
if (metadata == null) {
|
||||
metadata = emptyMap();
|
||||
}
|
||||
return new LoadedTexture(img, metadata);
|
||||
}
|
||||
// ====
|
||||
|
||||
@@ -128,8 +135,9 @@ public final class TexturesLoader {
|
||||
loadDefaultSkin("/assets/img/steve.png", TextureModel.STEVE);
|
||||
loadDefaultSkin("/assets/img/alex.png", TextureModel.ALEX);
|
||||
}
|
||||
|
||||
private static void loadDefaultSkin(String path, TextureModel model) {
|
||||
try (InputStream in = TexturesLoader.class.getResourceAsStream(path)) {
|
||||
try (InputStream in = ResourceNotFoundError.getResourceAsStream(path)) {
|
||||
DEFAULT_SKINS.put(model, new LoadedTexture(ImageIO.read(in), singletonMap("model", model.modelName)));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
@@ -142,7 +150,7 @@ public final class TexturesLoader {
|
||||
|
||||
public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {
|
||||
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid));
|
||||
return MultiStepBinding.of(service.getProfileRepository().binding(uuid))
|
||||
return BindingMapping.of(service.getProfileRepository().binding(uuid))
|
||||
.map(profile -> profile
|
||||
.flatMap(it -> {
|
||||
try {
|
||||
@@ -152,20 +160,23 @@ public final class TexturesLoader {
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN))))
|
||||
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
|
||||
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
|
||||
.asyncMap(it -> {
|
||||
if (it.isPresent()) {
|
||||
Texture texture = it.get();
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return getDefaultSkin(TextureModel.detectModelName(texture.getMetadata()));
|
||||
}
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return uuidFallback;
|
||||
}
|
||||
}, POOL);
|
||||
} else {
|
||||
return uuidFallback;
|
||||
return CompletableFuture.completedFuture(uuidFallback);
|
||||
}
|
||||
}, uuidFallback, POOL);
|
||||
}, uuidFallback);
|
||||
}
|
||||
// ====
|
||||
|
||||
@@ -180,19 +191,16 @@ public final class TexturesLoader {
|
||||
faceOffset, faceOffset, size - faceOffset, size - faceOffset,
|
||||
8 * scale, 8 * scale, 16 * scale, 16 * scale,
|
||||
null);
|
||||
|
||||
if (skin.getWidth() == skin.getHeight()) {
|
||||
g.drawImage(skin,
|
||||
0, 0, size, size,
|
||||
40 * scale, 8 * scale, 48 * scale, 16 * scale, null);
|
||||
}
|
||||
g.drawImage(skin,
|
||||
0, 0, size, size,
|
||||
40 * scale, 8 * scale, 48 * scale, 16 * scale, null);
|
||||
|
||||
g.dispose();
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public static ObjectBinding<Image> fxAvatarBinding(YggdrasilService service, UUID uuid, int size) {
|
||||
return MultiStepBinding.of(skinBinding(service, uuid))
|
||||
return BindingMapping.of(skinBinding(service, uuid))
|
||||
.map(it -> toAvatar(it.image, size))
|
||||
.map(img -> SwingFXUtils.toFXImage(img, null));
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@@ -32,9 +31,7 @@ import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
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.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import java.io.File;
|
||||
@@ -42,6 +39,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -90,9 +88,9 @@ public final class Profile implements Observable {
|
||||
return global.get();
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty name;
|
||||
private final SimpleStringProperty name;
|
||||
|
||||
public ImmediateStringProperty nameProperty() {
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -131,8 +129,8 @@ public final class Profile implements Observable {
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
|
||||
this.name = new ImmediateStringProperty(this, "name", name);
|
||||
gameDir = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
this.name = new SimpleStringProperty(this, "name", name);
|
||||
gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
repository = new HMCLGameRepository(this, initialGameDir);
|
||||
this.global.set(global == null ? new VersionSetting() : global);
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
@@ -146,7 +144,7 @@ public final class Profile implements Observable {
|
||||
}
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
@@ -36,6 +35,7 @@ import java.util.stream.Collectors;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Profiles {
|
||||
@@ -162,7 +162,7 @@ public final class Profiles {
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
Profile profile = selectedProfile.get();
|
||||
if (profile != null && profile.getRepository() == event.getSource()) {
|
||||
selectedVersion.bind(profile.selectedVersionProperty());
|
||||
|
||||
@@ -25,8 +25,10 @@ import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -80,7 +82,7 @@ public class Theme {
|
||||
String css;
|
||||
try {
|
||||
File temp = File.createTempFile("hmcl", ".css");
|
||||
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
|
||||
FileUtils.writeText(temp, IOUtils.readFullyAsString(ResourceNotFoundError.getResourceAsStream("/assets/css/custom.css"))
|
||||
.replace("%base-color%", color)
|
||||
.replace("%font-color%", getColorDisplayName(getForegroundColor())));
|
||||
css = temp.toURI().toString();
|
||||
@@ -132,7 +134,8 @@ public class Theme {
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> foregroundFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> config().getTheme().getForegroundColor(), config().themeProperty());
|
||||
return BindingMapping.of(config().themeProperty())
|
||||
.map(Theme::getForegroundColor);
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> blackFillBinding() {
|
||||
|
||||
@@ -21,13 +21,18 @@ import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
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;
|
||||
|
||||
@@ -60,9 +65,9 @@ public final class VersionSetting {
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty usesGlobalProperty = new ImmediateBooleanProperty(this, "usesGlobal", false);
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", false);
|
||||
|
||||
public ImmediateBooleanProperty usesGlobalProperty() {
|
||||
public BooleanProperty usesGlobalProperty() {
|
||||
return usesGlobalProperty;
|
||||
}
|
||||
|
||||
@@ -84,9 +89,9 @@ public final class VersionSetting {
|
||||
|
||||
// java
|
||||
|
||||
private final ImmediateStringProperty javaProperty = new ImmediateStringProperty(this, "java", "");
|
||||
private final StringProperty javaProperty = new SimpleStringProperty(this, "java", "");
|
||||
|
||||
public ImmediateStringProperty javaProperty() {
|
||||
public StringProperty javaProperty() {
|
||||
return javaProperty;
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ public final class VersionSetting {
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
private final StringProperty defaultJavaPathProperty = new SimpleStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
* Path to Java executable, or null if user customizes java directory.
|
||||
@@ -124,9 +129,9 @@ public final class VersionSetting {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
|
||||
private final StringProperty javaDirProperty = new SimpleStringProperty(this, "javaDir", "");
|
||||
|
||||
public ImmediateStringProperty javaDirProperty() {
|
||||
public StringProperty javaDirProperty() {
|
||||
return javaDirProperty;
|
||||
}
|
||||
|
||||
@@ -141,9 +146,9 @@ public final class VersionSetting {
|
||||
javaDirProperty.set(javaDir);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty wrapperProperty = new ImmediateStringProperty(this, "wrapper", "");
|
||||
private final StringProperty wrapperProperty = new SimpleStringProperty(this, "wrapper", "");
|
||||
|
||||
public ImmediateStringProperty wrapperProperty() {
|
||||
public StringProperty wrapperProperty() {
|
||||
return wrapperProperty;
|
||||
}
|
||||
|
||||
@@ -158,9 +163,9 @@ public final class VersionSetting {
|
||||
wrapperProperty.set(wrapper);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty permSizeProperty = new ImmediateStringProperty(this, "permSize", "");
|
||||
private final StringProperty permSizeProperty = new SimpleStringProperty(this, "permSize", "");
|
||||
|
||||
public ImmediateStringProperty permSizeProperty() {
|
||||
public StringProperty permSizeProperty() {
|
||||
return permSizeProperty;
|
||||
}
|
||||
|
||||
@@ -175,9 +180,9 @@ public final class VersionSetting {
|
||||
permSizeProperty.set(permSize);
|
||||
}
|
||||
|
||||
private final ImmediateIntegerProperty maxMemoryProperty = new ImmediateIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
|
||||
private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
|
||||
|
||||
public ImmediateIntegerProperty maxMemoryProperty() {
|
||||
public IntegerProperty maxMemoryProperty() {
|
||||
return maxMemoryProperty;
|
||||
}
|
||||
|
||||
@@ -195,9 +200,9 @@ public final class VersionSetting {
|
||||
/**
|
||||
* The minimum memory that JVM can allocate for heap.
|
||||
*/
|
||||
private final ImmediateObjectProperty<Integer> minMemoryProperty = new ImmediateObjectProperty<>(this, "minMemory", null);
|
||||
private final ObjectProperty<Integer> minMemoryProperty = new SimpleObjectProperty<>(this, "minMemory", null);
|
||||
|
||||
public ImmediateObjectProperty<Integer> minMemoryProperty() {
|
||||
public ObjectProperty<Integer> minMemoryProperty() {
|
||||
return minMemoryProperty;
|
||||
}
|
||||
|
||||
@@ -209,9 +214,9 @@ public final class VersionSetting {
|
||||
minMemoryProperty.set(minMemory);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty preLaunchCommandProperty = new ImmediateStringProperty(this, "precalledCommand", "");
|
||||
private final StringProperty preLaunchCommandProperty = new SimpleStringProperty(this, "precalledCommand", "");
|
||||
|
||||
public ImmediateStringProperty preLaunchCommandProperty() {
|
||||
public StringProperty preLaunchCommandProperty() {
|
||||
return preLaunchCommandProperty;
|
||||
}
|
||||
|
||||
@@ -229,9 +234,9 @@ public final class VersionSetting {
|
||||
|
||||
// options
|
||||
|
||||
private final ImmediateStringProperty javaArgsProperty = new ImmediateStringProperty(this, "javaArgs", "");
|
||||
private final StringProperty javaArgsProperty = new SimpleStringProperty(this, "javaArgs", "");
|
||||
|
||||
public ImmediateStringProperty javaArgsProperty() {
|
||||
public StringProperty javaArgsProperty() {
|
||||
return javaArgsProperty;
|
||||
}
|
||||
|
||||
@@ -246,9 +251,9 @@ public final class VersionSetting {
|
||||
javaArgsProperty.set(javaArgs);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty minecraftArgsProperty = new ImmediateStringProperty(this, "minecraftArgs", "");
|
||||
private final StringProperty minecraftArgsProperty = new SimpleStringProperty(this, "minecraftArgs", "");
|
||||
|
||||
public ImmediateStringProperty minecraftArgsProperty() {
|
||||
public StringProperty minecraftArgsProperty() {
|
||||
return minecraftArgsProperty;
|
||||
}
|
||||
|
||||
@@ -263,9 +268,9 @@ public final class VersionSetting {
|
||||
minecraftArgsProperty.set(minecraftArgs);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty noJVMArgsProperty = new ImmediateBooleanProperty(this, "noJVMArgs", false);
|
||||
private final BooleanProperty noJVMArgsProperty = new SimpleBooleanProperty(this, "noJVMArgs", false);
|
||||
|
||||
public ImmediateBooleanProperty noJVMArgsProperty() {
|
||||
public BooleanProperty noJVMArgsProperty() {
|
||||
return noJVMArgsProperty;
|
||||
}
|
||||
|
||||
@@ -280,9 +285,9 @@ public final class VersionSetting {
|
||||
noJVMArgsProperty.set(noJVMArgs);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty notCheckJVMProperty = new ImmediateBooleanProperty(this, "notCheckJVM", false);
|
||||
private final BooleanProperty notCheckJVMProperty = new SimpleBooleanProperty(this, "notCheckJVM", false);
|
||||
|
||||
public ImmediateBooleanProperty notCheckJVMProperty() {
|
||||
public BooleanProperty notCheckJVMProperty() {
|
||||
return notCheckJVMProperty;
|
||||
}
|
||||
|
||||
@@ -297,9 +302,9 @@ public final class VersionSetting {
|
||||
notCheckJVMProperty.set(notCheckJVM);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty notCheckGameProperty = new ImmediateBooleanProperty(this, "notCheckGame", false);
|
||||
private final BooleanProperty notCheckGameProperty = new SimpleBooleanProperty(this, "notCheckGame", false);
|
||||
|
||||
public ImmediateBooleanProperty notCheckGameProperty() {
|
||||
public BooleanProperty notCheckGameProperty() {
|
||||
return notCheckGameProperty;
|
||||
}
|
||||
|
||||
@@ -314,9 +319,9 @@ public final class VersionSetting {
|
||||
notCheckGameProperty.set(notCheckGame);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty showLogsProperty = new ImmediateBooleanProperty(this, "showLogs", false);
|
||||
private final BooleanProperty showLogsProperty = new SimpleBooleanProperty(this, "showLogs", false);
|
||||
|
||||
public ImmediateBooleanProperty showLogsProperty() {
|
||||
public BooleanProperty showLogsProperty() {
|
||||
return showLogsProperty;
|
||||
}
|
||||
|
||||
@@ -333,14 +338,14 @@ public final class VersionSetting {
|
||||
|
||||
// Minecraft settings.
|
||||
|
||||
private final ImmediateStringProperty serverIpProperty = new ImmediateStringProperty(this, "serverIp", "");
|
||||
private final StringProperty serverIpProperty = new SimpleStringProperty(this, "serverIp", "");
|
||||
|
||||
public ImmediateStringProperty serverIpProperty() {
|
||||
public StringProperty serverIpProperty() {
|
||||
return serverIpProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The server ip that will be entered after Minecraft successfully loaded immediately.
|
||||
* The server ip that will be entered after Minecraft successfully loaded ly.
|
||||
*
|
||||
* Format: ip:port or without port.
|
||||
*/
|
||||
@@ -353,9 +358,9 @@ public final class VersionSetting {
|
||||
}
|
||||
|
||||
|
||||
private final ImmediateBooleanProperty fullscreenProperty = new ImmediateBooleanProperty(this, "fullscreen", false);
|
||||
private final BooleanProperty fullscreenProperty = new SimpleBooleanProperty(this, "fullscreen", false);
|
||||
|
||||
public ImmediateBooleanProperty fullscreenProperty() {
|
||||
public BooleanProperty fullscreenProperty() {
|
||||
return fullscreenProperty;
|
||||
}
|
||||
|
||||
@@ -370,9 +375,9 @@ public final class VersionSetting {
|
||||
fullscreenProperty.set(fullscreen);
|
||||
}
|
||||
|
||||
private final ImmediateIntegerProperty widthProperty = new ImmediateIntegerProperty(this, "width", 854);
|
||||
private final IntegerProperty widthProperty = new SimpleIntegerProperty(this, "width", 854);
|
||||
|
||||
public ImmediateIntegerProperty widthProperty() {
|
||||
public IntegerProperty widthProperty() {
|
||||
return widthProperty;
|
||||
}
|
||||
|
||||
@@ -392,9 +397,9 @@ public final class VersionSetting {
|
||||
}
|
||||
|
||||
|
||||
private final ImmediateIntegerProperty heightProperty = new ImmediateIntegerProperty(this, "height", 480);
|
||||
private final IntegerProperty heightProperty = new SimpleIntegerProperty(this, "height", 480);
|
||||
|
||||
public ImmediateIntegerProperty heightProperty() {
|
||||
public IntegerProperty heightProperty() {
|
||||
return heightProperty;
|
||||
}
|
||||
|
||||
@@ -417,9 +422,9 @@ public final class VersionSetting {
|
||||
* 0 - .minecraft<br/>
|
||||
* 1 - .minecraft/versions/<version>/<br/>
|
||||
*/
|
||||
private final ImmediateObjectProperty<EnumGameDirectory> gameDirTypeProperty = new ImmediateObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
|
||||
private final ObjectProperty<EnumGameDirectory> gameDirTypeProperty = new SimpleObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
|
||||
|
||||
public ImmediateObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
|
||||
public ObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
|
||||
return gameDirTypeProperty;
|
||||
}
|
||||
|
||||
@@ -434,9 +439,9 @@ public final class VersionSetting {
|
||||
/**
|
||||
* Your custom gameDir
|
||||
*/
|
||||
private final ImmediateStringProperty gameDirProperty = new ImmediateStringProperty(this, "gameDir", "");
|
||||
private final StringProperty gameDirProperty = new SimpleStringProperty(this, "gameDir", "");
|
||||
|
||||
public ImmediateStringProperty gameDirProperty() {
|
||||
public StringProperty gameDirProperty() {
|
||||
return gameDirProperty;
|
||||
}
|
||||
|
||||
@@ -455,9 +460,9 @@ public final class VersionSetting {
|
||||
* 1 - Hide the launcher when the game starts.<br/>
|
||||
* 2 - Keep the launcher open.<br/>
|
||||
*/
|
||||
private final ImmediateObjectProperty<LauncherVisibility> launcherVisibilityProperty = new ImmediateObjectProperty<>(this, "launcherVisibility", LauncherVisibility.HIDE);
|
||||
private final ObjectProperty<LauncherVisibility> launcherVisibilityProperty = new SimpleObjectProperty<>(this, "launcherVisibility", LauncherVisibility.HIDE);
|
||||
|
||||
public ImmediateObjectProperty<LauncherVisibility> launcherVisibilityProperty() {
|
||||
public ObjectProperty<LauncherVisibility> launcherVisibilityProperty() {
|
||||
return launcherVisibilityProperty;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
@@ -34,7 +32,11 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountList;
|
||||
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||
@@ -45,7 +47,7 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
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.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
@@ -57,6 +59,8 @@ import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Controllers {
|
||||
@@ -158,7 +162,7 @@ public final class Controllers {
|
||||
});
|
||||
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
|
||||
mainPage.latestVersionProperty().bind(
|
||||
MultiStepBinding.of(UpdateChecker.latestVersionProperty())
|
||||
BindingMapping.of(UpdateChecker.latestVersionProperty())
|
||||
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
||||
|
||||
Profiles.registerVersionsListener(profile -> {
|
||||
@@ -173,7 +177,7 @@ public final class Controllers {
|
||||
return node;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile())
|
||||
mainPage.getVersions().setAll(children);
|
||||
});
|
||||
@@ -209,7 +213,7 @@ public final class Controllers {
|
||||
scene = new Scene(decorator.getDecorator(), 800, 519);
|
||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||
|
||||
stage.getIcons().add(new Image("/assets/img/icon.png"));
|
||||
stage.getIcons().add(newImage("/assets/img/icon.png"));
|
||||
stage.setTitle(Metadata.TITLE);
|
||||
}
|
||||
|
||||
@@ -223,14 +227,14 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title) {
|
||||
dialog(text, title, MessageBox.INFORMATION_MESSAGE);
|
||||
dialog(text, title, MessageType.INFORMATION);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type) {
|
||||
public static void dialog(String text, String title, MessageType type) {
|
||||
dialog(text, title, type, null);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type, Runnable onAccept) {
|
||||
public static void dialog(String text, String title, MessageType type, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, type, onAccept));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -30,6 +29,7 @@ import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
@@ -67,7 +67,7 @@ public class CrashWindow extends Stage {
|
||||
|
||||
Scene scene = new Scene(pane, 800, 480);
|
||||
setScene(scene);
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
getIcons().add(newImage("/assets/img/icon.png"));
|
||||
setTitle(i18n("message.error"));
|
||||
|
||||
setOnCloseRequest(e -> System.exit(1));
|
||||
|
||||
@@ -17,24 +17,26 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.task.SilentException;
|
||||
import org.jackhuang.hmcl.ui.account.AccountLoginPane;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
|
||||
public final class DialogController {
|
||||
|
||||
public static AuthInfo logIn(Account account) throws Exception {
|
||||
public static AuthInfo logIn(Account account) throws CancellationException, AuthenticationException, InterruptedException {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
@@ -42,7 +44,7 @@ public final class DialogController {
|
||||
Controllers.dialog(pane);
|
||||
});
|
||||
latch.await();
|
||||
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
||||
return Optional.ofNullable(res.get()).orElseThrow(CancellationException::new);
|
||||
}
|
||||
return account.logIn();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
@@ -31,9 +30,11 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
@@ -44,10 +45,12 @@ 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.Logging;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
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.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
@@ -74,6 +77,14 @@ public final class FXUtils {
|
||||
private FXUtils() {
|
||||
}
|
||||
|
||||
public static void runInFX(Runnable runnable) {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
Platform.runLater(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkFxUserThread() {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Not on FX application thread; currentThread = "
|
||||
@@ -153,6 +164,20 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
|
||||
column.setCellValueFactory(param -> {
|
||||
if (column.validateValue(param))
|
||||
return mapper.apply(param.getValue().getValue());
|
||||
else
|
||||
return column.getComputedValue(param);
|
||||
});
|
||||
}
|
||||
|
||||
public static Node wrapMargin(Node node, Insets insets) {
|
||||
StackPane.setMargin(node, insets);
|
||||
return new StackPane(node);
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(Node field, boolean validate) {
|
||||
if (field instanceof JFXTextField) {
|
||||
if (validate) {
|
||||
@@ -251,7 +276,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
try {
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
@@ -331,7 +356,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
textField.textProperty().bindBidirectional(property, SafeIntStringConverter.INSTANCE);
|
||||
textField.textProperty().bindBidirectional(property, SafeStringConverter.fromInteger());
|
||||
}
|
||||
|
||||
public static void unbindInt(JFXTextField textField, Property<Number> property) {
|
||||
@@ -461,6 +486,21 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
|
||||
* @param url the url of image. The image resource should be a file within the jar.
|
||||
* @return the image resource within the jar.
|
||||
* @see org.jackhuang.hmcl.util.CrashReporter
|
||||
* @see ResourceNotFoundError
|
||||
*/
|
||||
public static Image newImage(String url) {
|
||||
try {
|
||||
return new Image(url);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ResourceNotFoundError("Cannot access image: " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback) {
|
||||
applyDragListener(node, filter, callback, null);
|
||||
}
|
||||
@@ -510,6 +550,7 @@ public final class FXUtils {
|
||||
public void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (!empty) {
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(graphicBuilder.apply(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -30,26 +34,42 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class InstallerItem extends BorderPane {
|
||||
private final Consumer<InstallerItem> deleteCallback;
|
||||
|
||||
@FXML
|
||||
private Label lblInstallerArtifact;
|
||||
|
||||
@FXML
|
||||
private Label lblInstallerVersion;
|
||||
|
||||
public InstallerItem(String artifact, String version, Consumer<InstallerItem> deleteCallback) {
|
||||
this.deleteCallback = deleteCallback;
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/installer-item.fxml");
|
||||
|
||||
public InstallerItem(String artifact, String version, @Nullable Runnable upgrade, @Nullable Consumer<InstallerItem> deleteCallback) {
|
||||
getStyleClass().add("two-line-list-item");
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblInstallerArtifact.setText(artifact);
|
||||
lblInstallerVersion.setText(i18n("archive.version") + ": " + version);
|
||||
|
||||
{
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.setTitle(artifact);
|
||||
item.setSubtitle(i18n("archive.version") + ": " + version);
|
||||
setCenter(item);
|
||||
}
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
|
||||
if (upgrade != null) {
|
||||
JFXButton upgradeButton = new JFXButton();
|
||||
upgradeButton.setGraphic(SVG.update(Theme.blackFillBinding(), -1, -1));
|
||||
upgradeButton.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.installFastTooltip(upgradeButton, i18n("install.change_version"));
|
||||
upgradeButton.setOnMouseClicked(e -> upgrade.run());
|
||||
hBox.getChildren().add(upgradeButton);
|
||||
}
|
||||
|
||||
if (deleteCallback != null) {
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.close(Theme.blackFillBinding(), -1, -1));
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
deleteButton.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
hBox.getChildren().add(deleteButton);
|
||||
}
|
||||
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
setRight(hBox);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
deleteCallback.accept(this);
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -44,6 +41,8 @@ import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LeftPaneController extends AdvancedListBox {
|
||||
@@ -71,12 +70,12 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
||||
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
gameItem.setImage(new Image("/assets/img/bookshelf.png"));
|
||||
gameItem.setImage(newImage("/assets/img/bookshelf.png"));
|
||||
gameItem.setTitle(i18n("version.manage"));
|
||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.setImage(new Image("/assets/img/command.png"));
|
||||
launcherSettingsItem.setImage(newImage("/assets/img/command.png"));
|
||||
launcherSettingsItem.setTitle(i18n("settings.launcher"));
|
||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
|
||||
@@ -112,7 +111,7 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
private boolean checkedModpack = false;
|
||||
|
||||
private void onRefreshedVersions(HMCLGameRepository repository) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
if (!checkedModpack) {
|
||||
checkedModpack = true;
|
||||
|
||||
@@ -120,14 +119,12 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||
.thenResult(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenResult(modpack -> {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.installing")));
|
||||
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenApply(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor())
|
||||
.thenAccept(Schedulers.javafx(), executor -> {
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
||||
executor.start();
|
||||
return null;
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,11 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
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<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
|
||||
public abstract class ListPage<T extends Node> extends ListPageBase<T> {
|
||||
private final BooleanProperty refreshable = new SimpleBooleanProperty(this, "refreshable", false);
|
||||
|
||||
public abstract void add();
|
||||
@@ -42,30 +35,6 @@ public abstract class ListPage<T extends Node> extends Control {
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isRefreshable() {
|
||||
return refreshable.get();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
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.control.Control;
|
||||
|
||||
public class ListPageBase<T> extends Control {
|
||||
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,13 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXScrollPane;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -36,6 +37,9 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
super(skinnable);
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||
Pane placeholder = new Pane();
|
||||
VBox list = new VBox();
|
||||
|
||||
StackPane contentPane = new StackPane();
|
||||
{
|
||||
@@ -43,14 +47,15 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
{
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
VBox list = new VBox();
|
||||
list.maxWidthProperty().bind(scrollPane.widthProperty());
|
||||
list.setSpacing(10);
|
||||
list.setPadding(new Insets(10));
|
||||
|
||||
VBox content = new VBox();
|
||||
content.getChildren().setAll(list, placeholder);
|
||||
|
||||
Bindings.bindContent(list.getChildren(), skinnable.itemsProperty());
|
||||
|
||||
scrollPane.setContent(list);
|
||||
scrollPane.setContent(content);
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
@@ -64,7 +69,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
JFXButton btnAdd = new JFXButton();
|
||||
FXUtils.setLimitWidth(btnAdd, 40);
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.getStyleClass().setAll("jfx-button-raised-round");
|
||||
btnAdd.getStyleClass().add("jfx-button-raised-round");
|
||||
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1));
|
||||
btnAdd.setOnMouseClicked(e -> skinnable.add());
|
||||
@@ -72,7 +77,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
FXUtils.setLimitWidth(btnRefresh, 40);
|
||||
FXUtils.setLimitHeight(btnRefresh, 40);
|
||||
btnRefresh.getStyleClass().setAll("jfx-button-raised-round");
|
||||
btnRefresh.getStyleClass().add("jfx-button-raised-round");
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.whiteFillBinding(), -1, -1));
|
||||
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
|
||||
@@ -81,12 +86,23 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.refreshableProperty(),
|
||||
refreshable -> {
|
||||
if (refreshable) vBox.getChildren().setAll(btnRefresh, btnAdd);
|
||||
else vBox.getChildren().setAll(btnAdd);
|
||||
if (refreshable) {
|
||||
list.setPadding(new Insets(10, 10, 15 + 40 + 15 + 40 + 15, 10));
|
||||
vBox.getChildren().setAll(btnRefresh, btnAdd);
|
||||
} else {
|
||||
list.setPadding(new Insets(10, 10, 15 + 40 + 15, 10));
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, vBox);
|
||||
// Keep a blank space to prevent buttons from blocking up mod items.
|
||||
BorderPane group = new BorderPane();
|
||||
group.setPickOnBounds(false);
|
||||
group.setBottom(vBox);
|
||||
placeholder.minHeightProperty().bind(vBox.heightProperty());
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, group);
|
||||
}
|
||||
|
||||
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
|
||||
|
||||
@@ -25,7 +25,6 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
@@ -35,6 +34,7 @@ import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.w3c.dom.Document;
|
||||
@@ -44,6 +44,7 @@ import org.w3c.dom.Node;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ public final class LogWindow extends Stage {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
getIcons().add(newImage("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
public LogWindow(String text) {
|
||||
@@ -174,7 +175,7 @@ public final class LogWindow extends Stage {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/log.fxml");
|
||||
|
||||
engine = webView.getEngine();
|
||||
engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(getClass().getResourceAsStream("/assets/log-window-content.html")))
|
||||
engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(ResourceNotFoundError.getResourceAsStream("/assets/log-window-content.html")))
|
||||
.replace("${FONT}", config().getFontSize() + "px \"" + config().getFontFamily() + "\""));
|
||||
engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
@@ -201,11 +202,11 @@ public final class LogWindow extends Stage {
|
||||
if (!flag)
|
||||
cboLines.getSelectionModel().select(0);
|
||||
|
||||
btnFatals.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(fatal.get()) + " fatals", fatal));
|
||||
btnErrors.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(error.get()) + " errors", error));
|
||||
btnWarns.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(warn.get()) + " warns", warn));
|
||||
btnInfos.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(info.get()) + " infos", info));
|
||||
btnDebugs.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(debug.get()) + " debugs", debug));
|
||||
btnFatals.textProperty().bind(Bindings.concat(fatal, " fatals"));
|
||||
btnErrors.textProperty().bind(Bindings.concat(error, " errors"));
|
||||
btnWarns.textProperty().bind(Bindings.concat(warn, " warns"));
|
||||
btnInfos.textProperty().bind(Bindings.concat(info, " infos"));
|
||||
btnDebugs.textProperty().bind(Bindings.concat(debug, " debugs"));
|
||||
|
||||
btnFatals.selectedProperty().addListener(o -> specificChanged());
|
||||
btnErrors.selectedProperty().addListener(o -> specificChanged());
|
||||
|
||||
@@ -29,6 +29,10 @@ public final class SVG {
|
||||
private SVG() {
|
||||
}
|
||||
|
||||
public interface SVGIcon {
|
||||
Node createIcon(ObjectBinding<? extends Paint> fill, double width, double height);
|
||||
}
|
||||
|
||||
private static Node createSVGPath(String d, ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
SVGPath path = new SVGPath();
|
||||
path.getStyleClass().add("svg");
|
||||
@@ -162,4 +166,8 @@ public final class SVG {
|
||||
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);
|
||||
}
|
||||
|
||||
public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ 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.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
@@ -228,7 +228,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
try {
|
||||
Files.write(logFile, Logging.getRawLogs());
|
||||
} catch (IOException e) {
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageBox.ERROR_MESSAGE));
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageType.ERROR));
|
||||
LOG.log(Level.WARNING, "Failed to export logs", e);
|
||||
return;
|
||||
}
|
||||
@@ -247,4 +247,9 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
protected void onHelp() {
|
||||
FXUtils.openLink(Metadata.HELP_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSponsor() {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
@@ -71,10 +73,42 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
{
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setStyle("-fx-padding: 18;");
|
||||
rootPane.setPadding(new Insets(36, 18, 36, 18));
|
||||
{
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
{
|
||||
StackPane sponsorPane = new StackPane();
|
||||
sponsorPane.setCursor(Cursor.HAND);
|
||||
sponsorPane.setOnMouseClicked(e -> onSponsor());
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
|
||||
ColumnConstraints col = new ColumnConstraints();
|
||||
col.setHgrow(Priority.SOMETIMES);
|
||||
col.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col);
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row);
|
||||
|
||||
{
|
||||
Label label = new Label(i18n("sponsor.hmcl"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
|
||||
sponsorPane.getChildren().setAll(gridPane);
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
}
|
||||
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
@@ -83,7 +117,7 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
lblUpdateSub.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
@@ -92,7 +126,7 @@ public abstract class SettingsView extends StackPane {
|
||||
{
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.setOnMouseClicked(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().setAll("toggle-icon4");
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 20, 20));
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
@@ -124,7 +158,7 @@ public abstract class SettingsView extends StackPane {
|
||||
|
||||
Label help = new Label(i18n("help"));
|
||||
Label helpSubtitle = new Label(i18n("help.detail"));
|
||||
helpSubtitle.getStyleClass().setAll("subtitle-label");
|
||||
helpSubtitle.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(help, helpSubtitle);
|
||||
updatePane.setLeft(headerLeft);
|
||||
@@ -133,7 +167,7 @@ public abstract class SettingsView extends StackPane {
|
||||
{
|
||||
JFXButton btnExternal = new JFXButton();
|
||||
btnExternal.setOnMouseClicked(e -> onHelp());
|
||||
btnExternal.getStyleClass().setAll("toggle-icon4");
|
||||
btnExternal.getStyleClass().add("toggle-icon4");
|
||||
btnExternal.setGraphic(SVG.openInNew(Theme.blackFillBinding(), -1, -1));
|
||||
|
||||
updatePane.setRight(btnExternal);
|
||||
@@ -337,7 +371,7 @@ public abstract class SettingsView extends StackPane {
|
||||
{
|
||||
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnMouseClicked(e -> onExportLogs());
|
||||
logButton.getStyleClass().setAll("jfx-button-border");
|
||||
logButton.getStyleClass().add("jfx-button-border");
|
||||
|
||||
logPane.setHeaderRight(logButton);
|
||||
}
|
||||
@@ -491,4 +525,5 @@ public abstract class SettingsView extends StackPane {
|
||||
protected abstract void onUpdate();
|
||||
protected abstract void onHelp();
|
||||
protected abstract void onExportLogs();
|
||||
protected abstract void onSponsor();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXScrollPane;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
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.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>> extends SkinBase<T> {
|
||||
|
||||
public ToolbarListPageSkin(T skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
{
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
||||
JFXDepthManager.setDepth(toolbar, 1);
|
||||
toolbar.setPickOnBounds(false);
|
||||
toolbar.getChildren().setAll(initializeToolbar(skinnable));
|
||||
root.setTop(toolbar);
|
||||
}
|
||||
|
||||
{
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
VBox content = new VBox();
|
||||
content.setSpacing(10);
|
||||
content.setPadding(new Insets(10));
|
||||
|
||||
Bindings.bindContent(content.getChildren(), skinnable.itemsProperty());
|
||||
|
||||
scrollPane.setContent(content);
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
|
||||
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
|
||||
spinnerPane.setContent(root);
|
||||
|
||||
getChildren().setAll(spinnerPane);
|
||||
}
|
||||
|
||||
public static Node wrap(Node node) {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.setPadding(new Insets(0, 5, 0, 2));
|
||||
stackPane.getChildren().setAll(node);
|
||||
return stackPane;
|
||||
}
|
||||
|
||||
public static JFXButton createToolbarButton(String text, SVG.SVGIcon creator, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-tool-bar-button");
|
||||
ret.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
ret.setGraphic(wrap(creator.createIcon(Theme.foregroundFillBinding(), -1, -1)));
|
||||
ret.setText(text);
|
||||
ret.setOnMouseClicked(e -> onClick.run());
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected abstract List<Node> initializeToolbar(T skinnable);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
|
||||
import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class UpgradeDialog extends JFXDialogLayout {
|
||||
private final WebView webView = new WebView();
|
||||
|
||||
public UpgradeDialog(Runnable updateRunnable) {
|
||||
{
|
||||
setHeading(new Label(i18n("update.changelog")));
|
||||
}
|
||||
|
||||
{
|
||||
WebView webView = new WebView();
|
||||
WebEngine engine = webView.getEngine();
|
||||
engine.load(CHANGELOG_URL + config().getUpdateChannel().channelName);
|
||||
engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
|
||||
String url = engine.getLoadWorker().getMessage().trim();
|
||||
if (!url.startsWith(CHANGELOG_URL)) {
|
||||
engine.getLoadWorker().cancel();
|
||||
FXUtils.openLink(url);
|
||||
}
|
||||
});
|
||||
setBody(webView);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton updateButton = new JFXButton(i18n("update.accept"));
|
||||
updateButton.getStyleClass().add("dialog-accept");
|
||||
updateButton.setOnMouseClicked(e -> updateRunnable.run());
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
|
||||
setActions(updateButton, cancelButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,11 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
|
||||
public class WebStage extends Stage {
|
||||
private final WebView webView = new WebView();
|
||||
@@ -30,7 +30,7 @@ public class WebStage extends Stage {
|
||||
public WebStage() {
|
||||
setScene(new Scene(webView, 800, 480));
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
getIcons().add(newImage("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
public WebView getWebView() {
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.account;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
@@ -29,6 +28,7 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
@@ -42,7 +42,7 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
setTitle(i18n("account.missing"));
|
||||
setSubtitle(i18n("account.missing.add"));
|
||||
imageProperty().unbind();
|
||||
setImage(new Image("/assets/img/craft_table.png"));
|
||||
setImage(newImage("/assets/img/craft_table.png"));
|
||||
} else {
|
||||
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
|
||||
setSubtitle(accountSubtitle(account));
|
||||
|
||||
@@ -37,6 +37,7 @@ import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AccountListItem extends RadioButton {
|
||||
@@ -84,6 +85,8 @@ public class AccountListItem extends RadioButton {
|
||||
} catch (CredentialExpiredException e) {
|
||||
try {
|
||||
DialogController.logIn(account);
|
||||
} catch (CancellationException e1) {
|
||||
// ignore cancellation
|
||||
} catch (Exception e1) {
|
||||
LOG.log(Level.WARNING, "Failed to refresh " + account + " with password", e1);
|
||||
}
|
||||
|
||||
@@ -17,12 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
@@ -31,16 +28,16 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
|
||||
public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
|
||||
public AccountListItemSkin(AccountListItem skinnable) {
|
||||
@@ -75,7 +72,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
if (skinnable.getAccount() instanceof AuthlibInjectorAccount) {
|
||||
Tooltip tooltip = new Tooltip();
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer();
|
||||
tooltip.textProperty().bind(Bindings.createStringBinding(server::toString, server));
|
||||
tooltip.textProperty().bind(BindingMapping.of(server, AuthlibInjectorServer::toString));
|
||||
FXUtils.installSlowTooltip(subtitle, tooltip);
|
||||
}
|
||||
VBox item = new VBox(title, subtitle);
|
||||
@@ -91,7 +88,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
|
||||
right.getChildren().add(btnRefresh);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
@@ -99,7 +96,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class AccountLoginPane extends StackPane {
|
||||
private final Account oldAccount;
|
||||
@@ -59,11 +62,12 @@ public class AccountLoginPane extends StackPane {
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult(() -> oldAccount.logInWithPassword(password))
|
||||
.finalizedResult(Schedulers.javafx(), authInfo -> {
|
||||
.whenComplete(Schedulers.javafx(), authInfo -> {
|
||||
success.accept(authInfo);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
progressBar.setVisible(false);
|
||||
}, e -> {
|
||||
LOG.log(Level.INFO, "Failed to login with password: " + oldAccount, e);
|
||||
if (e instanceof NoSelectedCharacterException) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else {
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ListProperty;
|
||||
@@ -47,12 +48,13 @@ 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.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -126,7 +128,7 @@ public class AddAccountPane extends StackPane {
|
||||
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
|
||||
|
||||
// authlib-injector links
|
||||
links.bind(MultiStepBinding.of(cboServers.getSelectionModel().selectedItemProperty())
|
||||
links.bind(BindingMapping.of(cboServers.getSelectionModel().selectedItemProperty())
|
||||
.map(AddAccountPane::createHyperlinks)
|
||||
.map(FXCollections::observableList));
|
||||
Bindings.bindContent(linksContainer.getChildren(), links);
|
||||
@@ -196,7 +198,7 @@ public class AddAccountPane extends StackPane {
|
||||
Object additionalData = getAuthAdditionalData();
|
||||
|
||||
Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData))
|
||||
.finalizedResult(Schedulers.javafx(), account -> {
|
||||
.whenComplete(Schedulers.javafx(), account -> {
|
||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||
if (oldIndex == -1) {
|
||||
Accounts.getAccounts().add(account);
|
||||
@@ -293,7 +295,7 @@ public class AddAccountPane extends StackPane {
|
||||
} catch (InterruptedException ignore) {
|
||||
throw new NoSelectedCharacterException();
|
||||
} finally {
|
||||
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
|
||||
runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,11 +106,11 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.locateServer(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
}).whenComplete(Schedulers.javafx(), (isDependentSucceeded, exception) -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
if (isDependentSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
@@ -118,7 +118,6 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else {
|
||||
Exception exception = variables.get("lastException");
|
||||
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
@@ -35,21 +36,14 @@ import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
|
||||
public class AuthlibInjectorServersPage extends ListPage<AuthlibInjectorServerItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.injector.manage.title"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private VBox listPane;
|
||||
@FXML private StackPane contentPane;
|
||||
|
||||
private ObservableList<AuthlibInjectorServerItem> serverItems;
|
||||
private final ObservableList<AuthlibInjectorServerItem> serverItems;
|
||||
|
||||
public AuthlibInjectorServersPage() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
smoothScrolling(scrollPane);
|
||||
|
||||
serverItems = MappedObservableList.create(config().getAuthlibInjectorServers(), this::createServerItem);
|
||||
Bindings.bindContent(listPane.getChildren(), serverItems);
|
||||
Bindings.bindContent(itemsProperty(), serverItems);
|
||||
}
|
||||
|
||||
private AuthlibInjectorServerItem createServerItem(AuthlibInjectorServer server) {
|
||||
@@ -57,8 +51,8 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
item -> config().getAuthlibInjectorServers().remove(item.getServer()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
@Override
|
||||
public void add() {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER);
|
||||
right.setMouseTransparent(true);
|
||||
right.getStyleClass().setAll("toggle-icon4");
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(right, 40);
|
||||
FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(),
|
||||
newGraphic -> {
|
||||
@@ -99,7 +99,7 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
visible -> root.setRight(visible ? right : null));
|
||||
|
||||
stackPane.setStyle("-fx-padding: 10 16 10 16;");
|
||||
stackPane.getStyleClass().setAll("transparent");
|
||||
stackPane.getStyleClass().add("transparent");
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getChildren().setAll(root);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ 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 org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXListCell;
|
||||
@@ -49,7 +49,7 @@ public class FontComboBox extends JFXComboBox<String> {
|
||||
}
|
||||
});
|
||||
|
||||
itemsProperty().bind(MultiStepBinding.of(valueProperty())
|
||||
itemsProperty().bind(BindingMapping.of(valueProperty())
|
||||
.map(value -> value == null ? emptyObservableList() : singletonObservableList(value)));
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class IconedItem extends RipplerContainer {
|
||||
hBox.getChildren().add(icon);
|
||||
}
|
||||
|
||||
hBox.getStyleClass().setAll("iconed-item-container");
|
||||
hBox.getStyleClass().add("iconed-item-container");
|
||||
Label textLabel = new Label();
|
||||
textLabel.setId("label");
|
||||
textLabel.setMouseTransparent(true);
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.TreeTableCell;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class JFXCheckBoxTreeTableCell<S,T> extends TreeTableCell<S,T> {
|
||||
private final StackPane pane;
|
||||
private final CheckBox checkBox;
|
||||
private boolean showLabel;
|
||||
private ObservableValue<Boolean> booleanProperty;
|
||||
|
||||
public JFXCheckBoxTreeTableCell() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public JFXCheckBoxTreeTableCell(
|
||||
final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) {
|
||||
this(getSelectedProperty, null);
|
||||
}
|
||||
|
||||
public JFXCheckBoxTreeTableCell(
|
||||
final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty,
|
||||
final StringConverter<T> converter) {
|
||||
this.getStyleClass().add("check-box-tree-table-cell");
|
||||
this.checkBox = new JFXCheckBox();
|
||||
this.pane = new StackPane(checkBox);
|
||||
this.pane.setAlignment(Pos.CENTER);
|
||||
setGraphic(null);
|
||||
setSelectedStateCallback(getSelectedProperty);
|
||||
setConverter(converter);
|
||||
}
|
||||
|
||||
private ObjectProperty<StringConverter<T>> converter =
|
||||
new SimpleObjectProperty<StringConverter<T>>(this, "converter") {
|
||||
protected void invalidated() {
|
||||
updateShowLabel();
|
||||
}
|
||||
};
|
||||
|
||||
public final ObjectProperty<StringConverter<T>> converterProperty() {
|
||||
return converter;
|
||||
}
|
||||
public final void setConverter(StringConverter<T> value) {
|
||||
converterProperty().set(value);
|
||||
}
|
||||
public final StringConverter<T> getConverter() {
|
||||
return converterProperty().get();
|
||||
}
|
||||
|
||||
private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>>
|
||||
selectedStateCallback =
|
||||
new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>(
|
||||
this, "selectedStateCallback");
|
||||
|
||||
public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
|
||||
return selectedStateCallback;
|
||||
}
|
||||
|
||||
public final void setSelectedStateCallback(Callback<Integer, ObservableValue<Boolean>> value) {
|
||||
selectedStateCallbackProperty().set(value);
|
||||
}
|
||||
|
||||
public final Callback<Integer, ObservableValue<Boolean>> getSelectedStateCallback() {
|
||||
return selectedStateCallbackProperty().get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override public void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
StringConverter<T> c = getConverter();
|
||||
|
||||
if (showLabel) {
|
||||
setText(c.toString(item));
|
||||
}
|
||||
|
||||
setGraphic(pane);
|
||||
|
||||
if (booleanProperty instanceof BooleanProperty) {
|
||||
checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
|
||||
}
|
||||
ObservableValue<?> obsValue = getSelectedProperty();
|
||||
if (obsValue instanceof BooleanProperty) {
|
||||
booleanProperty = (ObservableValue<Boolean>) obsValue;
|
||||
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
|
||||
}
|
||||
|
||||
checkBox.disableProperty().bind(Bindings.not(
|
||||
getTreeTableView().editableProperty().and(
|
||||
getTableColumn().editableProperty()).and(
|
||||
editableProperty())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShowLabel() {
|
||||
this.showLabel = converter != null;
|
||||
this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER);
|
||||
}
|
||||
|
||||
private ObservableValue<?> getSelectedProperty() {
|
||||
return getSelectedStateCallback() != null ?
|
||||
getSelectedStateCallback().call(getIndex()) :
|
||||
getTableColumn().getCellObservableValue(getIndex());
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MessageBox {
|
||||
private MessageBox() {
|
||||
}
|
||||
|
||||
private static final String TITLE = i18n("message.info");
|
||||
|
||||
/**
|
||||
* User Operation: Yes
|
||||
*/
|
||||
public static final int YES_OPTION = 0;
|
||||
|
||||
/**
|
||||
* User Operation: No
|
||||
*/
|
||||
public static final int NO_OPTION = 1;
|
||||
|
||||
/**
|
||||
* User Operation: Cancel
|
||||
*/
|
||||
public static final int CANCEL_OPTION = 2;
|
||||
|
||||
/**
|
||||
* User Operation: OK
|
||||
*/
|
||||
public static final int OK_OPTION = 0;
|
||||
|
||||
/**
|
||||
* User Operation: Closed Message Box
|
||||
*/
|
||||
public static final int CLOSED_OPTION = -1;
|
||||
|
||||
/**
|
||||
* Buttons: Yes No
|
||||
*/
|
||||
public static final int YES_NO_OPTION = 10;
|
||||
/**
|
||||
* Buttons: Yes No Cancel
|
||||
*/
|
||||
public static final int YES_NO_CANCEL_OPTION = 11;
|
||||
/**
|
||||
* Buttons: OK Cancel
|
||||
*/
|
||||
public static final int OK_CANCEL_OPTION = 12;
|
||||
|
||||
/**
|
||||
* Message Box Type: Error
|
||||
*/
|
||||
public static final int ERROR_MESSAGE = 0;
|
||||
/**
|
||||
* Message Box Type: Info
|
||||
*/
|
||||
public static final int INFORMATION_MESSAGE = 1;
|
||||
/**
|
||||
* Message Box Type: Warning
|
||||
*/
|
||||
public static final int WARNING_MESSAGE = 2;
|
||||
/**
|
||||
* Message Box Type: Question
|
||||
*/
|
||||
public static final int QUESTION_MESSAGE = 3;
|
||||
/**
|
||||
* Message Box Type: Fine
|
||||
*/
|
||||
public static final int FINE_MESSAGE = 4;
|
||||
/**
|
||||
* Message Box Type: Plain
|
||||
*/
|
||||
public static final int PLAIN_MESSAGE = -1;
|
||||
|
||||
|
||||
public static void show(String message) {
|
||||
show(message, TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show MsgBox with title and options
|
||||
*
|
||||
* @param message The Message
|
||||
* @param title The title of MsgBox.
|
||||
* @return user operation.
|
||||
*/
|
||||
public static Optional<ButtonType> show(String message, String title) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
return alert.showAndWait();
|
||||
}
|
||||
|
||||
public static int confirm(String message, String title) {
|
||||
return confirm(message, title, -1);
|
||||
}
|
||||
|
||||
public static int confirm(String message, int option) {
|
||||
return confirm(message, TITLE, option);
|
||||
}
|
||||
|
||||
public static int confirm(String message, String title, int option) {
|
||||
return FXUtils.runInUIThread(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
switch (option) {
|
||||
case YES_NO_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||
break;
|
||||
case YES_NO_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||
break;
|
||||
case OK_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box option " + option);
|
||||
}
|
||||
Optional<ButtonType> buttonType = alert.showAndWait();
|
||||
if (!buttonType.isPresent()) return CLOSED_OPTION;
|
||||
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
|
||||
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
|
||||
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
|
||||
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
|
||||
else throw new IllegalStateException("Unexpected button type:" + buttonType.get());
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message) {
|
||||
return input(message, UIManager.getString("OptionPane.inputDialogTitle"));
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message, String title) {
|
||||
return input(message, title, "");
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message, String title, String initialValue) {
|
||||
TextInputDialog dialog = new TextInputDialog(initialValue);
|
||||
dialog.setTitle(title);
|
||||
dialog.setHeaderText(message);
|
||||
dialog.setContentText(message);
|
||||
|
||||
return dialog.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,15 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
|
||||
public static enum MessageType {
|
||||
ERROR,
|
||||
INFORMATION,
|
||||
WARNING,
|
||||
QUESTION,
|
||||
FINE,
|
||||
PLAIN,
|
||||
}
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@FXML
|
||||
@@ -45,7 +54,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
@FXML
|
||||
private HBox actions;
|
||||
|
||||
public MessageDialogPane(String text, String title, int type, Runnable onAccept) {
|
||||
public MessageDialogPane(String text, String title, MessageType type, Runnable onAccept) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
if (title != null)
|
||||
@@ -60,19 +69,19 @@ public final class MessageDialogPane extends StackPane {
|
||||
actions.getChildren().remove(cancelButton);
|
||||
|
||||
switch (type) {
|
||||
case MessageBox.INFORMATION_MESSAGE:
|
||||
case INFORMATION:
|
||||
graphic.setGraphic(SVG.infoCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.ERROR_MESSAGE:
|
||||
case ERROR:
|
||||
graphic.setGraphic(SVG.closeCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.FINE_MESSAGE:
|
||||
case FINE:
|
||||
graphic.setGraphic(SVG.checkCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.WARNING_MESSAGE:
|
||||
case WARNING:
|
||||
graphic.setGraphic(SVG.alert(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.QUESTION_MESSAGE:
|
||||
case QUESTION:
|
||||
graphic.setGraphic(SVG.helpCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
default:
|
||||
@@ -81,7 +90,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
this(text, title, MessageType.QUESTION, onAccept);
|
||||
|
||||
cancelButton.setVisible(true);
|
||||
cancelButton.setOnMouseClicked(e -> {
|
||||
|
||||
@@ -64,7 +64,7 @@ public class PopupMenu extends Control {
|
||||
public static Node wrapPopupMenuItem(Node node) {
|
||||
StackPane pane = new StackPane();
|
||||
pane.getChildren().setAll(node);
|
||||
pane.getStyleClass().setAll("menu-container");
|
||||
pane.getStyleClass().add("menu-container");
|
||||
node.setMouseTransparent(true);
|
||||
return new RipplerContainer(pane);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXRippler;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.NamedArg;
|
||||
@@ -64,39 +63,16 @@ public class RipplerContainer extends StackPane {
|
||||
mask.resize(buttonContainer.getWidth() - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(), buttonContainer.getHeight() - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset());
|
||||
return mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
this.ripplerPane.setOnMousePressed(event -> {
|
||||
if (releaseManualRippler != null)
|
||||
releaseManualRippler.run();
|
||||
releaseManualRippler = null;
|
||||
createRipple(event.getX(), event.getY());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private Transition clickedAnimation;
|
||||
private final CornerRadii defaultRadii = new CornerRadii(3);
|
||||
private Runnable releaseManualRippler;
|
||||
|
||||
public RipplerContainer(@NamedArg("container") Node container) {
|
||||
setContainer(container);
|
||||
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
buttonRippler.setPosition(JFXRippler.RipplerPos.BACK);
|
||||
buttonContainer.getChildren().add(buttonRippler);
|
||||
setOnMousePressed(event -> {
|
||||
if (clickedAnimation != null) {
|
||||
clickedAnimation.setRate(1);
|
||||
clickedAnimation.play();
|
||||
}
|
||||
});
|
||||
setOnMouseReleased(event -> {
|
||||
if (clickedAnimation != null) {
|
||||
clickedAnimation.setRate(-1);
|
||||
clickedAnimation.play();
|
||||
}
|
||||
});
|
||||
focusedProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue) {
|
||||
if (!isPressed())
|
||||
@@ -109,23 +85,7 @@ public class RipplerContainer extends StackPane {
|
||||
setPickOnBounds(false);
|
||||
|
||||
buttonContainer.setPickOnBounds(false);
|
||||
buttonContainer.shapeProperty().bind(shapeProperty());
|
||||
buttonContainer.borderProperty().bind(borderProperty());
|
||||
buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
if (getBackground() == null || isJavaDefaultBackground(getBackground()) || isJavaDefaultClickedBackground(getBackground()))
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
|
||||
|
||||
try {
|
||||
return new Background(new BackgroundFill(getBackground() != null ? getBackground().getFills().get(0).getFill() : Color.TRANSPARENT,
|
||||
getBackground() != null ? getBackground().getFills().get(0).getRadii() : defaultRadii, Insets.EMPTY));
|
||||
} catch (Exception e) {
|
||||
return getBackground();
|
||||
}
|
||||
}, backgroundProperty()));
|
||||
|
||||
ripplerFillProperty().addListener((a, b, newValue) -> buttonRippler.setRipplerFill(newValue));
|
||||
if (getBackground() == null || isJavaDefaultBackground(getBackground()))
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
|
||||
buttonRippler.ripplerFillProperty().bind(ripplerFillProperty());
|
||||
|
||||
containerProperty().addListener(o -> updateChildren());
|
||||
updateChildren();
|
||||
@@ -153,24 +113,6 @@ public class RipplerContainer extends StackPane {
|
||||
getChildren().get(i).setPickOnBounds(false);
|
||||
}
|
||||
|
||||
private boolean isJavaDefaultBackground(Background background) {
|
||||
try {
|
||||
String firstFill = background.getFills().get(0).getFill().toString();
|
||||
return "0xffffffba".equals(firstFill) || "0xffffffbf".equals(firstFill) || "0xffffffbd".equals(firstFill);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJavaDefaultClickedBackground(Background background) {
|
||||
try {
|
||||
String firstFill = background.getFills().get(0).getFill().toString();
|
||||
return "0x039ed3ff".equals(firstFill);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Node getContainer() {
|
||||
return container.get();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.application.Platform;
|
||||
@@ -34,6 +33,8 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
|
||||
public class TaskExecutorDialogPane extends StackPane {
|
||||
private TaskExecutor executor;
|
||||
private Consumer<Region> onCancel;
|
||||
@@ -121,6 +122,6 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
public void setCancel(Consumer<Region> onCancel) {
|
||||
this.onCancel = onCancel;
|
||||
|
||||
JFXUtilities.runInFX(() -> btnCancel.setDisable(onCancel == null));
|
||||
runInFX(() -> btnCancel.setDisable(onCancel == null));
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
@@ -45,16 +41,15 @@ import java.util.Map;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class TaskListPane extends StackPane {
|
||||
private final JFXListView<Task> listBox = new JFXListView<>();
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final Map<Task, ProgressListNode> nodes = new HashMap<>();
|
||||
private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper();
|
||||
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
|
||||
|
||||
public TaskListPane() {
|
||||
getChildren().setAll(listBox);
|
||||
listBox.setSpacing(0);
|
||||
|
||||
listBox.setPadding(Insets.EMPTY);
|
||||
listBox.setCellFactory(listView -> new ProgressListNode());
|
||||
getChildren().setAll(listBox);
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty finishedTasksProperty() {
|
||||
@@ -70,7 +65,7 @@ public final class TaskListPane extends StackPane {
|
||||
@Override
|
||||
public void onStart() {
|
||||
Platform.runLater(() -> {
|
||||
listBox.getItems().clear();
|
||||
listBox.clear();
|
||||
finishedTasks.set(0);
|
||||
totTasks.set(0);
|
||||
});
|
||||
@@ -112,66 +107,64 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(i18n("modpack.scan"));
|
||||
}
|
||||
|
||||
Platform.runLater(() -> listBox.getItems().add(task));
|
||||
ProgressListNode node = new ProgressListNode(task);
|
||||
nodes.put(task, node);
|
||||
Platform.runLater(() -> listBox.add(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(Task task) {
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
node.unbind();
|
||||
Platform.runLater(() -> {
|
||||
if (listBox.getItems().remove(task))
|
||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
||||
listBox.remove(node);
|
||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(Task task, Throwable throwable) {
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
Platform.runLater(() -> {
|
||||
node.setThrowable(throwable);
|
||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ProgressListNode extends ListCell<Task> {
|
||||
private final BorderPane borderPane = new BorderPane();
|
||||
private static class ProgressListNode extends BorderPane {
|
||||
private final JFXProgressBar bar = new JFXProgressBar();
|
||||
private final Label title = new Label();
|
||||
private final Label state = new Label();
|
||||
|
||||
{
|
||||
borderPane.setLeft(title);
|
||||
borderPane.setRight(state);
|
||||
borderPane.setBottom(bar);
|
||||
borderPane.setMinWidth(0);
|
||||
borderPane.setPrefWidth(1);
|
||||
public ProgressListNode(Task task) {
|
||||
bar.progressProperty().bind(task.progressProperty());
|
||||
title.setText(task.getName());
|
||||
state.textProperty().bind(task.messageProperty());
|
||||
|
||||
setPadding(Insets.EMPTY);
|
||||
setLeft(title);
|
||||
setRight(state);
|
||||
setBottom(bar);
|
||||
|
||||
bar.minWidthProperty().bind(widthProperty());
|
||||
bar.prefWidthProperty().bind(widthProperty());
|
||||
bar.maxWidthProperty().bind(widthProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Task item, boolean empty) {
|
||||
boolean wasEmpty = isEmpty();
|
||||
Task oldTask = getItem();
|
||||
public void unbind() {
|
||||
bar.progressProperty().unbind();
|
||||
state.textProperty().unbind();
|
||||
}
|
||||
|
||||
if (!wasEmpty && oldTask != null) {
|
||||
bar.progressProperty().unbind();
|
||||
state.textProperty().unbind();
|
||||
}
|
||||
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setGraphic(borderPane);
|
||||
bar.visibleProperty().bind(Bindings.createBooleanBinding(() -> item.progressProperty().get() != -1, item.progressProperty()));
|
||||
bar.progressProperty().bind(item.progressProperty());
|
||||
state.textProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
if (item.getState() == Task.TaskState.FAILED) {
|
||||
return item.getLastException().getLocalizedMessage();
|
||||
} else {
|
||||
return item.messageProperty().get();
|
||||
}
|
||||
}, item.messageProperty(), item.stateProperty()));
|
||||
title.setText(item.getName());
|
||||
}
|
||||
public void setThrowable(Throwable throwable) {
|
||||
unbind();
|
||||
state.setText(throwable.getLocalizedMessage());
|
||||
bar.setProgress(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,10 @@ import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
@@ -61,6 +64,7 @@ import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class DecoratorController {
|
||||
@@ -92,7 +96,7 @@ public class DecoratorController {
|
||||
decorator.onRefreshNavButtonActionProperty().set(e -> refresh());
|
||||
|
||||
welcomeView = new ImageView();
|
||||
welcomeView.setImage(new Image("/assets/img/welcome.png"));
|
||||
welcomeView.setImage(newImage("/assets/img/welcome.png"));
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
FXUtils.limitSize(welcomeView, 796, 517);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
@@ -152,7 +156,7 @@ public class DecoratorController {
|
||||
config().backgroundImageProperty()));
|
||||
}
|
||||
|
||||
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
||||
private Image defaultBackground = newImage("/assets/img/background.jpg");
|
||||
|
||||
/**
|
||||
* Load background image from bg/, background.png, background.jpg
|
||||
|
||||
@@ -73,7 +73,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
Decorator skinnable = getSkinnable();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().setAll("jfx-decorator", "resize-border");
|
||||
root.getStyleClass().addAll("jfx-decorator", "resize-border");
|
||||
root.setPrefHeight(519);
|
||||
root.setPrefWidth(800);
|
||||
root.setMaxHeight(Region.USE_PREF_SIZE);
|
||||
@@ -83,7 +83,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
StackPane drawerWrapper = new StackPane();
|
||||
skinnable.setDrawerWrapper(drawerWrapper);
|
||||
drawerWrapper.getStyleClass().setAll("jfx-decorator-drawer");
|
||||
drawerWrapper.getStyleClass().add("jfx-decorator-drawer");
|
||||
drawerWrapper.backgroundProperty().bind(skinnable.backgroundProperty());
|
||||
FXUtils.setOverflowHidden(drawerWrapper, true);
|
||||
{
|
||||
@@ -92,10 +92,10 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
{
|
||||
BorderPane leftRootPane = new BorderPane();
|
||||
FXUtils.setLimitWidth(leftRootPane, 200);
|
||||
leftRootPane.getStyleClass().setAll("jfx-decorator-content-container");
|
||||
leftRootPane.getStyleClass().add("jfx-decorator-content-container");
|
||||
|
||||
StackPane drawerContainer = new StackPane();
|
||||
drawerContainer.getStyleClass().setAll("gray-background");
|
||||
drawerContainer.getStyleClass().add("gray-background");
|
||||
Bindings.bindContent(drawerContainer.getChildren(), skinnable.drawerProperty());
|
||||
leftRootPane.setCenter(drawerContainer);
|
||||
|
||||
@@ -111,7 +111,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
{
|
||||
contentPlaceHolder = new StackPane();
|
||||
contentPlaceHolder.getStyleClass().setAll("jfx-decorator-content-container");
|
||||
contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container");
|
||||
contentPlaceHolder.backgroundProperty().bind(skinnable.contentBackgroundProperty());
|
||||
FXUtils.setOverflowHidden(contentPlaceHolder, true);
|
||||
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
||||
@@ -189,14 +189,14 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
{
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||
backNavButton.visibleProperty().bind(skinnable.canBackProperty());
|
||||
|
||||
closeNavButton = new JFXButton();
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
|
||||
@@ -219,7 +219,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
VBox navCenter = new VBox();
|
||||
navCenter.setAlignment(Pos.CENTER_LEFT);
|
||||
Label titleLabel = new Label();
|
||||
titleLabel.getStyleClass().setAll("jfx-decorator-title");
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
titleLabel.textProperty().bind(skinnable.drawerTitleProperty());
|
||||
navCenter.getChildren().setAll(titleLabel);
|
||||
navBar.setCenter(navCenter);
|
||||
@@ -228,7 +228,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||
refreshNavButton = new JFXButton();
|
||||
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||
refreshNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
refreshNavButton.visibleProperty().bind(skinnable.canRefreshProperty());
|
||||
@@ -251,12 +251,12 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
StackPane pane = new StackPane(minus);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
btnMin.setGraphic(pane);
|
||||
btnMin.getStyleClass().setAll("jfx-decorator-button");
|
||||
btnMin.getStyleClass().add("jfx-decorator-button");
|
||||
btnMin.setOnAction(e -> skinnable.minimize());
|
||||
|
||||
JFXButton btnClose = new JFXButton();
|
||||
btnClose.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnClose.getStyleClass().setAll("jfx-decorator-button");
|
||||
btnClose.getStyleClass().add("jfx-decorator-button");
|
||||
btnClose.setOnAction(e -> skinnable.close());
|
||||
|
||||
buttonsContainer.getChildren().setAll(separator, btnMin, btnClose);
|
||||
|
||||
@@ -52,7 +52,7 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
||||
wizardController.setProvider(provider);
|
||||
wizardController.onStart();
|
||||
|
||||
getStyleClass().setAll("white-background");
|
||||
getStyleClass().add("white-background");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,15 +21,27 @@ import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionMismatchException;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class InstallerWizardProvider implements WizardProvider {
|
||||
@@ -46,9 +58,9 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
this.version = version;
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
forge = analyzer.getForge().map(Library::getVersion).orElse(null);
|
||||
liteLoader = analyzer.getLiteLoader().map(Library::getVersion).orElse(null);
|
||||
optiFine = analyzer.getOptiFine().map(Library::getVersion).orElse(null);
|
||||
forge = analyzer.get(FORGE).map(Library::getVersion).orElse(null);
|
||||
liteLoader = analyzer.get(LITELOADER).map(Library::getVersion).orElse(null);
|
||||
optiFine = analyzer.get(OPTIFINE).map(Library::getVersion).orElse(null);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
@@ -82,18 +94,18 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
|
||||
Task ret = Task.ofResult("version", () -> version);
|
||||
TaskResult<Version> ret = Task.ofResult(() -> version);
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
|
||||
|
||||
return ret.then(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
@@ -114,4 +126,31 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void alertFailureMessage(Exception exception, Runnable next) {
|
||||
if (exception instanceof LibraryDownloadException) {
|
||||
Controllers.dialog(i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof DownloadException) {
|
||||
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||
Controllers.dialog(i18n("install.failed.downloading.timeout", ((DownloadException) exception).getUrl()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
} else if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode()) + ", " + ((DownloadException) exception).getUrl() + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", ((DownloadException) exception).getUrl()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
}
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", ((DownloadException) exception).getUrl()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
}
|
||||
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException) {
|
||||
Controllers.dialog(i18n("install.failed.optifine_conflict"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof UnsupportedOperationException) {
|
||||
Controllers.dialog(i18n("install.failed.install_online"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof VersionMismatchException) {
|
||||
VersionMismatchException e = ((VersionMismatchException) exception);
|
||||
Controllers.dialog(i18n("install.failed.version_mismatch", e.getExpect(), e.getActual()), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageType.ERROR, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-11
@@ -24,14 +24,12 @@ import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -86,11 +84,11 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
try {
|
||||
return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageType.ERROR);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageType.ERROR);
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageType.ERROR);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
@@ -107,14 +105,12 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
public void onFail(Map<String, Object> settings, Exception exception, Runnable next) {
|
||||
if (exception instanceof CurseCompletionException) {
|
||||
if (exception.getCause() instanceof FileNotFoundException) {
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageBox.ERROR_MESSAGE, next);
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(i18n("modpack.type.curse.tolerable_error"), i18n("install.success"), MessageBox.INFORMATION_MESSAGE, next);
|
||||
Controllers.dialog(i18n("modpack.type.curse.tolerable_error"), i18n("install.success"), MessageType.INFORMATION, next);
|
||||
}
|
||||
} else if (exception instanceof DownloadException) {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next);
|
||||
} else {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageBox.ERROR_MESSAGE, next);
|
||||
InstallerWizardProvider.alertFailureMessage(exception, next);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
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.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -110,8 +110,8 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
|
||||
spinnerPane.showSpinner();
|
||||
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
|
||||
.thenResult(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
|
||||
.finalizedResult(Schedulers.javafx(), manifest -> {
|
||||
.thenApply(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
|
||||
.whenComplete(Schedulers.javafx(), manifest -> {
|
||||
spinnerPane.hideSpinner();
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
@@ -129,7 +129,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
}
|
||||
}, e -> {
|
||||
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageType.ERROR);
|
||||
Platform.runLater(controller::onEnd);
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.download.InstallerWizardProvider.alertFailureMessage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final String libraryId;
|
||||
private final Library oldLibrary;
|
||||
|
||||
public UpdateInstallerWizardProvider(Profile profile, String gameVersion, Version version, String libraryId, Library oldLibrary) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.libraryId = libraryId;
|
||||
this.oldLibrary = oldLibrary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
|
||||
// We remove library but not save it,
|
||||
// so if installation failed will not break down current version.
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(oldLibrary);
|
||||
return new MaintainTask(version.setLibraries(newList))
|
||||
.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get(libraryId)))
|
||||
.then(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> {
|
||||
Controllers.confirmDialog(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibrary.getVersion(), ((RemoteVersion) settings.get(libraryId)).getSelfVersion()),
|
||||
i18n("install.change_version"), controller::onFinish, controller::onCancel);
|
||||
});
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelIfCannotGoBack() {
|
||||
// VersionsPage will call wizardController.onPrev(cleanUp = true) when list is empty.
|
||||
// So we cancel this wizard when VersionPage calls the method.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+2
-15
@@ -22,14 +22,10 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -63,23 +59,14 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version((RemoteVersion) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions())
|
||||
return builder.buildAsync().whenComplete((a, b) -> profile.getRepository().refreshVersions())
|
||||
.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_callback", new FailureCallback() {
|
||||
@Override
|
||||
public void onFail(Map<String, Object> settings, Exception exception, Runnable next) {
|
||||
if (exception instanceof DownloadException) {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next);
|
||||
} else {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageBox.ERROR_MESSAGE, next);
|
||||
}
|
||||
}
|
||||
});
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> InstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
|
||||
return finishVersionDownloadingAsync(settings);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -43,7 +44,7 @@ import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class VersionsPage extends StackPane implements WizardPage, Refreshable {
|
||||
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
|
||||
private final String gameVersion;
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final String libraryId;
|
||||
@@ -59,6 +60,8 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
@FXML
|
||||
private StackPane emptyPane;
|
||||
@FXML
|
||||
private StackPane root;
|
||||
@FXML
|
||||
private JFXCheckBox chkRelease;
|
||||
@FXML
|
||||
private JFXCheckBox chkSnapshot;
|
||||
@@ -69,7 +72,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
@FXML
|
||||
private VBox centrePane;
|
||||
|
||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||
private final TransitionHandler transitionHandler;
|
||||
private final VersionList<?> versionList;
|
||||
private TaskExecutor executor;
|
||||
|
||||
@@ -83,6 +86,8 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
|
||||
transitionHandler = new TransitionHandler(root);
|
||||
|
||||
if (versionList.hasType()) {
|
||||
centrePane.getChildren().setAll(checkPane, list);
|
||||
} else
|
||||
@@ -122,9 +127,9 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
getChildren().setAll(spinner);
|
||||
executor = versionList.refreshAsync(downloadProvider).finalized((variables, isDependentsSucceeded) -> {
|
||||
if (isDependentsSucceeded) {
|
||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
executor = versionList.refreshAsync(gameVersion, downloadProvider).whenComplete((isDependentSucceeded, exception) -> {
|
||||
if (isDependentSucceeded) {
|
||||
List<VersionsPageItem> items = loadVersions();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -142,7 +147,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.log(Level.WARNING, "Failed to fetch versions list", (Throwable) variables.get("lastException"));
|
||||
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
|
||||
Platform.runLater(() -> {
|
||||
transitionHandler.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
});
|
||||
@@ -169,4 +174,9 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
|
||||
@FXML
|
||||
private void onBack() { controller.onPrev(true); }
|
||||
|
||||
@FXML
|
||||
private void onSponsor() {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/bmclapi_sponsor");
|
||||
}
|
||||
}
|
||||
@@ -73,9 +73,9 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
|
||||
if (basePath.length() > "minecraft/".length()) {
|
||||
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory());
|
||||
if (file.isFile() && Objects.equals(FileUtils.getNameWithoutExtension(file), version))
|
||||
if (file.isFile() && Objects.equals(FileUtils.getNameWithoutExtension(file), version)) // Ignore <version>.json, <version>.jar
|
||||
state = ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (file.isDirectory() && Objects.equals(file.getName(), version + "-natives"))
|
||||
if (file.isDirectory() && Objects.equals(file.getName(), version + "-natives")) // Ignore <version>-natives
|
||||
state = ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (state == ModAdviser.ModSuggestion.HIDDEN)
|
||||
return null;
|
||||
|
||||
@@ -19,13 +19,14 @@ package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
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;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
|
||||
public class ProfileAdvancedListItem extends AdvancedListItem {
|
||||
private ObjectProperty<Profile> profile = new SimpleObjectProperty<Profile>() {
|
||||
|
||||
@@ -41,7 +42,7 @@ public class ProfileAdvancedListItem extends AdvancedListItem {
|
||||
};
|
||||
|
||||
public ProfileAdvancedListItem() {
|
||||
setImage(new Image("/assets/img/craft_table.png"));
|
||||
setImage(newImage("/assets/img/craft_table.png"));
|
||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -31,6 +30,8 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
|
||||
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
public ProfileListItemSkin(ProfileListItem skinnable) {
|
||||
@@ -54,7 +55,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
imageView.imageProperty().set(newImage("/assets/img/craft_table.png"));
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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.installFastTooltip(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());
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,16 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
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.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -32,16 +37,19 @@ 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.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DatapackListPage extends ListPage<DatapackListItem> implements DecoratorPage {
|
||||
public class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final Path worldDir;
|
||||
private final Datapack datapack;
|
||||
|
||||
private final ObservableList<DatapackListPageSkin.DatapackInfoObject> items;
|
||||
|
||||
public DatapackListPage(String worldName, Path worldDir) {
|
||||
this.worldDir = worldDir;
|
||||
|
||||
@@ -50,14 +58,7 @@ public class DatapackListPage extends ListPage<DatapackListItem> implements Deco
|
||||
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");
|
||||
}
|
||||
})));
|
||||
setItems(items = MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)), mods -> {
|
||||
mods.forEach(it -> {
|
||||
@@ -69,7 +70,19 @@ public class DatapackListPage extends ListPage<DatapackListItem> implements Deco
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
|
||||
}
|
||||
});
|
||||
}, datapack::loadFromDir);
|
||||
}, this::refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new DatapackListPageSkin(this);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
setLoading(true);
|
||||
Task.of(datapack::loadFromDir)
|
||||
.with(Task.of(Schedulers.javafx(), () -> setLoading(false)))
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,7 +90,6 @@ public class DatapackListPage extends ListPage<DatapackListItem> implements Deco
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("datapack.choose_datapack"));
|
||||
@@ -97,4 +109,32 @@ public class DatapackListPage extends ListPage<DatapackListItem> implements Deco
|
||||
|
||||
datapack.loadFromDir();
|
||||
}
|
||||
|
||||
void removeSelected(ObservableList<TreeItem<DatapackListPageSkin.DatapackInfoObject>> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(TreeItem::getValue)
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.forEach(pack -> {
|
||||
try {
|
||||
datapack.deletePack(pack);
|
||||
} catch (IOException e) {
|
||||
// Fail to remove mods if the game is running or the datapack is absent.
|
||||
Logging.LOG.warning("Failed to delete datapack " + pack);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void enableSelected(ObservableList<TreeItem<DatapackListPageSkin.DatapackInfoObject>> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(TreeItem::getValue)
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.forEach(info -> info.setActive(true));
|
||||
}
|
||||
|
||||
void disableSelected(ObservableList<TreeItem<DatapackListPageSkin.DatapackInfoObject>> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(TreeItem::getValue)
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.forEach(info -> info.setActive(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXTreeTableColumn;
|
||||
import com.jfoenix.controls.JFXTreeTableView;
|
||||
import com.jfoenix.controls.RecursiveTreeItem;
|
||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTreeTableCell;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.setupCellValueFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.wrapMargin;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
DatapackListPageSkin(DatapackListPage skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
JFXTreeTableView<DatapackInfoObject> tableView = new JFXTreeTableView<>();
|
||||
|
||||
{
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
||||
JFXDepthManager.setDepth(toolbar, 1);
|
||||
toolbar.setPickOnBounds(false);
|
||||
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG::plus, skinnable::add));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.remove"), SVG::delete, () ->
|
||||
skinnable.removeSelected(tableView.getSelectionModel().getSelectedItems())));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG::check, () ->
|
||||
skinnable.enableSelected(tableView.getSelectionModel().getSelectedItems())));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG::close, () ->
|
||||
skinnable.disableSelected(tableView.getSelectionModel().getSelectedItems())));
|
||||
root.setTop(toolbar);
|
||||
}
|
||||
|
||||
{
|
||||
SpinnerPane center = new SpinnerPane();
|
||||
center.getStyleClass().add("large-spinner-pane");
|
||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||
|
||||
tableView.getStyleClass().addAll("no-header");
|
||||
tableView.setShowRoot(false);
|
||||
tableView.setEditable(true);
|
||||
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
tableView.setRoot(new RecursiveTreeItem<>(skinnable.getItems(), RecursiveTreeObject::getChildren));
|
||||
|
||||
JFXTreeTableColumn<DatapackInfoObject, Boolean> activeColumn = new JFXTreeTableColumn<>();
|
||||
setupCellValueFactory(activeColumn, DatapackInfoObject::activeProperty);
|
||||
activeColumn.setCellFactory(c -> new JFXCheckBoxTreeTableCell<>());
|
||||
activeColumn.setEditable(true);
|
||||
activeColumn.setMaxWidth(40);
|
||||
activeColumn.setMinWidth(40);
|
||||
|
||||
JFXTreeTableColumn<DatapackInfoObject, Node> detailColumn = new JFXTreeTableColumn<>();
|
||||
setupCellValueFactory(detailColumn, DatapackInfoObject::nodeProperty);
|
||||
|
||||
tableView.getColumns().setAll(activeColumn, detailColumn);
|
||||
|
||||
tableView.setColumnResizePolicy(JFXTreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
center.setContent(tableView);
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
|
||||
static class DatapackInfoObject extends RecursiveTreeObject<DatapackInfoObject> {
|
||||
private final BooleanProperty active;
|
||||
private final Datapack.Pack packInfo;
|
||||
private final ObjectProperty<Node> node;
|
||||
|
||||
DatapackInfoObject(Datapack.Pack packInfo) {
|
||||
this.packInfo = packInfo;
|
||||
this.active = packInfo.activeProperty();
|
||||
this.node = new SimpleObjectProperty<>(wrapMargin(new TwoLineListItem(packInfo.getId(), StringUtils.parseColorEscapes(packInfo.getDescription())),
|
||||
new Insets(8, 0, 8, 0)));
|
||||
}
|
||||
|
||||
BooleanProperty activeProperty() {
|
||||
return active;
|
||||
}
|
||||
|
||||
ObjectProperty<Node> nodeProperty() {
|
||||
return node;
|
||||
}
|
||||
|
||||
Datapack.Pack getPackInfo() {
|
||||
return packInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
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 static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
@@ -38,7 +38,7 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
} else {
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
setImage(new Image("/assets/img/grass.png"));
|
||||
setImage(newImage("/assets/img/grass.png"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
@@ -25,11 +26,21 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
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;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GameItem extends Control {
|
||||
|
||||
private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 1, TimeUnit.SECONDS);
|
||||
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
@@ -40,16 +51,19 @@ public class GameItem extends Control {
|
||||
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", ""))));
|
||||
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
|
||||
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown"), POOL_VERSION_RESOLVE)
|
||||
.thenAcceptAsync(game -> {
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
|
||||
analyzer.get(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
|
||||
analyzer.get(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
|
||||
analyzer.get(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
|
||||
subtitle.set(libraries.toString());
|
||||
}, Platform::runLater)
|
||||
.exceptionally(handleUncaught);
|
||||
|
||||
title.set(id);
|
||||
subtitle.set(libraries.toString());
|
||||
image.set(profile.getRepository().getVersionIconImage(version));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
@@ -30,32 +28,31 @@ 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.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
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.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameList extends Control implements DecoratorPage {
|
||||
public class GameList extends ListPageBase<GameListItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(I18n.i18n("version.manage"));
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(true);
|
||||
private final ListProperty<GameListItem> items = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
|
||||
public GameList() {
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == Profiles.getSelectedProfile().getRepository())
|
||||
JFXUtilities.runInFX(() -> loading.set(true));
|
||||
runInFX(() -> setLoading(true));
|
||||
});
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
@@ -72,10 +69,10 @@ public class GameList extends Control implements DecoratorPage {
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(version -> new GameListItem(toggleGroup, profile, version.getId()))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
loading.set(false);
|
||||
items.setAll(children);
|
||||
setLoading(false);
|
||||
itemsProperty().setAll(children);
|
||||
children.forEach(GameListItem::checkSelection);
|
||||
|
||||
profile.selectedVersionProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||
@@ -97,8 +94,8 @@ public class GameList extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new GameListSkin(this);
|
||||
protected GameListSkin createDefaultSkin() {
|
||||
return new GameListSkin();
|
||||
}
|
||||
|
||||
void addNewGame() {
|
||||
@@ -128,11 +125,20 @@ public class GameList extends Control implements DecoratorPage {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
}
|
||||
private class GameListSkin extends ToolbarListPageSkin<GameList> {
|
||||
|
||||
public ListProperty<GameListItem> itemsProperty() {
|
||||
return items;
|
||||
public GameListSkin() {
|
||||
super(GameList.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(GameList skinnable) {
|
||||
return Arrays.asList(
|
||||
createToolbarButton(i18n("install.new_game"), SVG::plus, skinnable::addNewGame),
|
||||
createToolbarButton(i18n("install.modpack"), SVG::importIcon, skinnable::importModpack),
|
||||
createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh),
|
||||
createToolbarButton(i18n("settings.type.global.manage"), SVG::gear, skinnable::modifyGlobalGameSettings)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
@@ -33,6 +32,7 @@ import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MenuSeparator;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
@@ -73,7 +73,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
btnUpgrade.setOnMouseClicked(e -> skinnable.update());
|
||||
btnUpgrade.getStyleClass().add("toggle-icon4");
|
||||
btnUpgrade.setGraphic(SVG.update(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXScrollPane;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
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.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
public class GameListSkin extends SkinBase<GameList> {
|
||||
|
||||
private static Node wrap(Node node) {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.setPadding(new Insets(0, 5, 0, 2));
|
||||
stackPane.getChildren().setAll(node);
|
||||
return stackPane;
|
||||
}
|
||||
|
||||
public GameListSkin(GameList skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
{
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.getStyleClass().setAll("jfx-tool-bar-second");
|
||||
JFXDepthManager.setDepth(toolbar, 1);
|
||||
toolbar.setPickOnBounds(false);
|
||||
|
||||
JFXButton btnAddNewGame = new JFXButton();
|
||||
btnAddNewGame.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnAddNewGame.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
btnAddNewGame.setGraphic(wrap(SVG.plus(Theme.foregroundFillBinding(), -1, -1)));
|
||||
btnAddNewGame.setText(I18n.i18n("install.new_game"));
|
||||
btnAddNewGame.setOnMouseClicked(e -> skinnable.addNewGame());
|
||||
toolbar.getChildren().add(btnAddNewGame);
|
||||
|
||||
JFXButton btnImportModpack = new JFXButton();
|
||||
btnImportModpack.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnImportModpack.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
btnImportModpack.setGraphic(wrap(SVG.importIcon(Theme.foregroundFillBinding(), -1, -1)));
|
||||
btnImportModpack.setText(I18n.i18n("install.modpack"));
|
||||
btnImportModpack.setOnMouseClicked(e -> skinnable.importModpack());
|
||||
toolbar.getChildren().add(btnImportModpack);
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
btnRefresh.setGraphic(wrap(SVG.refresh(Theme.foregroundFillBinding(), -1, -1)));
|
||||
btnRefresh.setText(I18n.i18n("button.refresh"));
|
||||
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
|
||||
toolbar.getChildren().add(btnRefresh);
|
||||
|
||||
JFXButton btnModify = new JFXButton();
|
||||
btnModify.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnModify.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
btnModify.setGraphic(wrap(SVG.gear(Theme.foregroundFillBinding(), -1, -1)));
|
||||
btnModify.setText(I18n.i18n("settings.type.global.manage"));
|
||||
btnModify.setOnMouseClicked(e -> skinnable.modifyGlobalGameSettings());
|
||||
toolbar.getChildren().add(btnModify);
|
||||
|
||||
root.setTop(toolbar);
|
||||
}
|
||||
|
||||
{
|
||||
StackPane center = new StackPane();
|
||||
|
||||
JFXSpinner spinner = new JFXSpinner();
|
||||
spinner.getStyleClass().setAll("first-spinner");
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
VBox gameList = new VBox();
|
||||
gameList.maxWidthProperty().bind(scrollPane.widthProperty());
|
||||
gameList.setSpacing(10);
|
||||
gameList.setStyle("-fx-padding: 10 10 10 10;");
|
||||
|
||||
Bindings.bindContent(gameList.getChildren(), skinnable.itemsProperty());
|
||||
|
||||
scrollPane.setContent(gameList);
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.loadingProperty(),
|
||||
loading -> center.getChildren().setAll(loading ? spinner : scrollPane));
|
||||
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
@@ -26,53 +29,128 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallerListPage extends ListPage<InstallerItem> {
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private Version version;
|
||||
private String gameVersion;
|
||||
|
||||
{
|
||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "exe").contains(FileUtils.getExtension(it)), mods -> {
|
||||
if (!mods.isEmpty())
|
||||
doInstallOffline(mods.get(0));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new InstallerListPageSkin();
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
this.version = profile.getRepository().getResolvedVersion(versionId);
|
||||
this.gameVersion = null;
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
Task.ofResult(() -> {
|
||||
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
|
||||
|
||||
Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new MaintainTask(version.setLibraries(newList))
|
||||
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
.start();
|
||||
};
|
||||
return LibraryAnalyzer.analyze(version);
|
||||
}).thenAccept(Schedulers.javafx(), analyzer -> {
|
||||
Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new MaintainTask(version.setLibraries(newList))
|
||||
.then(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
.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))));
|
||||
itemsProperty().clear();
|
||||
analyzer.get(FORGE).ifPresent(library -> itemsProperty().add(
|
||||
new InstallerItem("Forge", library.getVersion(), () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", library));
|
||||
}, removeAction.apply(library))));
|
||||
analyzer.get(LITELOADER).ifPresent(library -> itemsProperty().add(
|
||||
new InstallerItem("LiteLoader", library.getVersion(), () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", library));
|
||||
}, removeAction.apply(library))));
|
||||
analyzer.get(OPTIFINE).ifPresent(library -> itemsProperty().add(
|
||||
new InstallerItem("OptiFine", library.getVersion(), () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", library));
|
||||
}, removeAction.apply(library))));
|
||||
analyzer.get(FABRIC).ifPresent(library -> itemsProperty().add(new InstallerItem("Fabric", library.getVersion(), null, null)));
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
|
||||
|
||||
if (!gameVersion.isPresent())
|
||||
public void installOnline() {
|
||||
if (gameVersion == null)
|
||||
Controllers.dialog(i18n("version.cannot_read"));
|
||||
else
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version));
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion, version));
|
||||
}
|
||||
|
||||
public void installOffline() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("install.installer.install_offline.extension"), "*.jar", "*.exe"));
|
||||
File file = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (file != null) doInstallOffline(file);
|
||||
}
|
||||
|
||||
private void doInstallOffline(File file) {
|
||||
Task task = profile.getDependency().installLibraryAsync(version, file.toPath())
|
||||
.then(profile.getRepository().refreshVersionsAsync());
|
||||
task.setName(i18n("install.installer.install_offline"));
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
runInFX(() -> {
|
||||
if (success) {
|
||||
loadVersion(profile, versionId);
|
||||
Controllers.dialog(i18n("install.success"));
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
InstallerWizardProvider.alertFailureMessage(executor.getLastException(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Controllers.taskDialog(executor, i18n("install.installer.install_offline"));
|
||||
executor.start();
|
||||
}
|
||||
|
||||
private class InstallerListPageSkin extends ToolbarListPageSkin<InstallerListPage> {
|
||||
|
||||
InstallerListPageSkin() {
|
||||
super(InstallerListPage.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
|
||||
return Arrays.asList(
|
||||
createToolbarButton(i18n("install.installer.install_online"), SVG::plus, skinnable::installOnline),
|
||||
createToolbarButton(i18n("install.installer.install_offline"), SVG::plus, skinnable::installOffline));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
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.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;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModItem extends BorderPane {
|
||||
|
||||
public ModItem(ModInfo info, Consumer<ModItem> 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();
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installFastTooltip(btnRemove, i18n("mods.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.getFileName());
|
||||
StringBuilder message = new StringBuilder(info.getName());
|
||||
if (StringUtils.isNotBlank(info.getVersion()))
|
||||
message.append(", ").append(i18n("archive.version")).append(": ").append(info.getVersion());
|
||||
if (StringUtils.isNotBlank(info.getGameVersion()))
|
||||
message.append(", ").append(i18n("archive.game_version")).append(": ").append(info.getGameVersion());
|
||||
if (StringUtils.isNotBlank(info.getAuthors()))
|
||||
message.append(", ").append(i18n("archive.author")).append(": ").append(info.getAuthors());
|
||||
modItem.setSubtitle(message.toString());
|
||||
chkEnabled.setSelected(info.isActive());
|
||||
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
|
||||
info.activeProperty().set(newValue));
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -27,7 +32,7 @@ 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.ListPageBase;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
@@ -36,17 +41,21 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModListPage extends ListPage<ModItem> {
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> {
|
||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||
|
||||
private JFXTabPane parentTab;
|
||||
private ModManager modManager;
|
||||
private LibraryAnalyzer libraryAnalyzer;
|
||||
|
||||
public ModListPage() {
|
||||
setRefreshable(true);
|
||||
|
||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||
mods.forEach(it -> {
|
||||
@@ -61,60 +70,38 @@ public final class ModListPage extends ListPage<ModItem> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ModListPageSkin(this);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
loadMods(modManager);
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
|
||||
modded.set(libraryAnalyzer.hasModLoader());
|
||||
loadMods(profile.getRepository().getModManager(id));
|
||||
}
|
||||
|
||||
public void loadMods(ModManager modManager) {
|
||||
private void loadMods(ModManager modManager) {
|
||||
this.modManager = modManager;
|
||||
Task.of(variables -> {
|
||||
Task.ofResult(() -> {
|
||||
synchronized (ModListPage.this) {
|
||||
JFXUtilities.runInFX(() -> loadingProperty().set(true));
|
||||
|
||||
runInFX(() -> loadingProperty().set(true));
|
||||
modManager.refreshMods();
|
||||
|
||||
// Surprisingly, if there are a great number of mods, this processing will cause a long UI pause,
|
||||
// constructing UI elements.
|
||||
// We must do this asynchronously.
|
||||
LinkedList<ModItem> list = new LinkedList<>();
|
||||
for (ModInfo modInfo : modManager.getMods()) {
|
||||
ModItem item = new ModItem(modInfo, i -> {
|
||||
try {
|
||||
modManager.removeMods(modInfo);
|
||||
} catch (IOException ignore) {
|
||||
// Fail to remove mods if the game is running or the mod is absent.
|
||||
}
|
||||
loadMods(modManager);
|
||||
});
|
||||
modInfo.activeProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue)
|
||||
item.getStyleClass().remove("disabled");
|
||||
else
|
||||
item.getStyleClass().add("disabled");
|
||||
});
|
||||
if (!modInfo.isActive())
|
||||
item.getStyleClass().add("disabled");
|
||||
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
variables.set("list", list);
|
||||
return new LinkedList<>(modManager.getMods());
|
||||
}
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
}).whenComplete(Schedulers.javafx(), (list, isDependentSucceeded, exception) -> {
|
||||
loadingProperty().set(false);
|
||||
if (isDependentsSucceeded)
|
||||
if (isDependentSucceeded)
|
||||
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
||||
itemsProperty().setAll(variables.<List<ModItem>>get("list"));
|
||||
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("mods.choose_mod"));
|
||||
@@ -125,7 +112,7 @@ public final class ModListPage extends ListPage<ModItem> {
|
||||
List<String> succeeded = new LinkedList<>();
|
||||
List<String> failed = new LinkedList<>();
|
||||
if (res == null) return;
|
||||
Task.of(variables -> {
|
||||
Task.of(() -> {
|
||||
for (File file : res) {
|
||||
try {
|
||||
modManager.addMod(file);
|
||||
@@ -137,7 +124,7 @@ public final class ModListPage extends ListPage<ModItem> {
|
||||
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
|
||||
}
|
||||
}
|
||||
}).with(Task.of(Schedulers.javafx(), variables -> {
|
||||
}).with(Task.of(Schedulers.javafx(), () -> {
|
||||
List<String> prompt = new LinkedList<>();
|
||||
if (!succeeded.isEmpty())
|
||||
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
@@ -151,4 +138,43 @@ public final class ModListPage extends ListPage<ModItem> {
|
||||
public void setParentTab(JFXTabPane parentTab) {
|
||||
this.parentTab = parentTab;
|
||||
}
|
||||
|
||||
public void removeSelected(ObservableList<TreeItem<ModListPageSkin.ModInfoObject>> selectedItems) {
|
||||
try {
|
||||
modManager.removeMods(selectedItems.stream()
|
||||
.map(TreeItem::getValue)
|
||||
.filter(Objects::nonNull)
|
||||
.map(ModListPageSkin.ModInfoObject::getModInfo)
|
||||
.toArray(ModInfo[]::new));
|
||||
loadMods(modManager);
|
||||
} catch (IOException ignore) {
|
||||
// Fail to remove mods if the game is running or the mod is absent.
|
||||
}
|
||||
}
|
||||
|
||||
public void enableSelected(ObservableList<TreeItem<ModListPageSkin.ModInfoObject>> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(TreeItem::getValue)
|
||||
.map(ModListPageSkin.ModInfoObject::getModInfo)
|
||||
.forEach(info -> info.setActive(true));
|
||||
}
|
||||
|
||||
public void disableSelected(ObservableList<TreeItem<ModListPageSkin.ModInfoObject>> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(TreeItem::getValue)
|
||||
.map(ModListPageSkin.ModInfoObject::getModInfo)
|
||||
.forEach(info -> info.setActive(false));
|
||||
}
|
||||
|
||||
public boolean isModded() {
|
||||
return modded.get();
|
||||
}
|
||||
|
||||
public BooleanProperty moddedProperty() {
|
||||
return modded;
|
||||
}
|
||||
|
||||
public void setModded(boolean modded) {
|
||||
this.modded.set(modded);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXTreeTableColumn;
|
||||
import com.jfoenix.controls.JFXTreeTableView;
|
||||
import com.jfoenix.controls.RecursiveTreeItem;
|
||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTreeTableCell;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.setupCellValueFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.wrapMargin;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.isNotBlank;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
|
||||
ModListPageSkin(ModListPage skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
pane.getStyleClass().addAll("notice-pane", "white-background");
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
JFXTreeTableView<ModInfoObject> tableView = new JFXTreeTableView<>();
|
||||
|
||||
{
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
||||
JFXDepthManager.setDepth(toolbar, 1);
|
||||
toolbar.setPickOnBounds(false);
|
||||
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.add"), SVG::plus, skinnable::add));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.remove"), SVG::delete, () ->
|
||||
skinnable.removeSelected(tableView.getSelectionModel().getSelectedItems())));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG::check, () ->
|
||||
skinnable.enableSelected(tableView.getSelectionModel().getSelectedItems())));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG::close, () ->
|
||||
skinnable.disableSelected(tableView.getSelectionModel().getSelectedItems())));
|
||||
root.setTop(toolbar);
|
||||
}
|
||||
|
||||
{
|
||||
SpinnerPane center = new SpinnerPane();
|
||||
center.getStyleClass().add("large-spinner-pane");
|
||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||
|
||||
tableView.getStyleClass().add("no-header");
|
||||
tableView.setShowRoot(false);
|
||||
tableView.setEditable(true);
|
||||
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
tableView.setRoot(new RecursiveTreeItem<>(skinnable.getItems(), RecursiveTreeObject::getChildren));
|
||||
|
||||
JFXTreeTableColumn<ModInfoObject, Boolean> activeColumn = new JFXTreeTableColumn<>();
|
||||
setupCellValueFactory(activeColumn, ModInfoObject::activeProperty);
|
||||
activeColumn.setCellFactory(c -> new JFXCheckBoxTreeTableCell<>());
|
||||
activeColumn.setEditable(true);
|
||||
activeColumn.setMaxWidth(40);
|
||||
activeColumn.setMinWidth(40);
|
||||
|
||||
JFXTreeTableColumn<ModInfoObject, Node> detailColumn = new JFXTreeTableColumn<>();
|
||||
setupCellValueFactory(detailColumn, ModInfoObject::nodeProperty);
|
||||
|
||||
tableView.getColumns().setAll(activeColumn, detailColumn);
|
||||
|
||||
tableView.setColumnResizePolicy(JFXTreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
center.setContent(tableView);
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
Label label = new Label(i18n("mods.not_modded"));
|
||||
label.prefWidthProperty().bind(pane.widthProperty().add(-100));
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.moddedProperty(), modded -> {
|
||||
if (modded) pane.getChildren().setAll(root);
|
||||
else pane.getChildren().setAll(label);
|
||||
});
|
||||
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
|
||||
static class ModInfoObject extends RecursiveTreeObject<ModInfoObject> {
|
||||
private final BooleanProperty active;
|
||||
private final ModInfo modInfo;
|
||||
private final ObjectProperty<Node> node;
|
||||
|
||||
ModInfoObject(ModInfo modInfo) {
|
||||
this.modInfo = modInfo;
|
||||
this.active = modInfo.activeProperty();
|
||||
StringBuilder message = new StringBuilder(modInfo.getName());
|
||||
if (isNotBlank(modInfo.getVersion()))
|
||||
message.append(", ").append(i18n("archive.version")).append(": ").append(modInfo.getVersion());
|
||||
if (isNotBlank(modInfo.getGameVersion()))
|
||||
message.append(", ").append(i18n("archive.game_version")).append(": ").append(modInfo.getGameVersion());
|
||||
if (isNotBlank(modInfo.getAuthors()))
|
||||
message.append(", ").append(i18n("archive.author")).append(": ").append(modInfo.getAuthors());
|
||||
this.node = new SimpleObjectProperty<>(wrapMargin(new TwoLineListItem(modInfo.getFileName(), message.toString()), new Insets(8, 0, 8, 0)));
|
||||
}
|
||||
|
||||
BooleanProperty activeProperty() {
|
||||
return active;
|
||||
}
|
||||
|
||||
ObjectProperty<Node> nodeProperty() {
|
||||
return node;
|
||||
}
|
||||
|
||||
ModInfo getModInfo() {
|
||||
return modInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -57,10 +53,11 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -71,7 +68,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private boolean javaItemsLoaded;
|
||||
private InvalidationListener specificSettingsListener;
|
||||
|
||||
@FXML private VBox rootPane;
|
||||
@FXML private ScrollPane scroll;
|
||||
@@ -100,6 +96,12 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
@FXML private JFXCheckBox chkEnableSpecificSettings;
|
||||
@FXML private BorderPane settingsTypePane;
|
||||
|
||||
private InvalidationListener specificSettingsListener = any -> {
|
||||
chkEnableSpecificSettings.setSelected(!lastVersionSetting.isUsesGlobal());
|
||||
};
|
||||
|
||||
private InvalidationListener javaListener = any -> initJavaSubtitle();
|
||||
|
||||
public VersionSettingsPage() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/version-settings.fxml");
|
||||
|
||||
@@ -113,15 +115,14 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
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() + i18n("settings.game.java_directory.bit", javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
javaItemsLoaded = true;
|
||||
initializeSelectedJava();
|
||||
});
|
||||
Task.ofResult(JavaVersion::getJavas).thenAccept(Schedulers.javafx(), list -> {
|
||||
javaItem.loadChildren(list.stream()
|
||||
.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();
|
||||
}).start();
|
||||
|
||||
javaItem.setSelectedData(null);
|
||||
javaItem.setFallbackData(JavaVersion.fromCurrentEnvironment());
|
||||
@@ -148,10 +149,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
});
|
||||
|
||||
specificSettingsListener = o -> {
|
||||
chkEnableSpecificSettings.setSelected(!lastVersionSetting.isUsesGlobal());
|
||||
};
|
||||
|
||||
componentList.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not());
|
||||
advancedSettingsPane.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not());
|
||||
}
|
||||
@@ -193,6 +190,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
|
||||
lastVersionSetting.javaDirProperty().removeListener(javaListener);
|
||||
lastVersionSetting.javaProperty().removeListener(javaListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().unbind();
|
||||
@@ -232,9 +231,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
});
|
||||
|
||||
versionSetting.javaDirProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
versionSetting.javaDirProperty().addListener(javaListener);
|
||||
versionSetting.javaProperty().addListener(javaListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(),
|
||||
@@ -243,6 +241,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initializeSelectedJava();
|
||||
initJavaSubtitle();
|
||||
|
||||
loadIcon();
|
||||
}
|
||||
@@ -267,11 +266,14 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
}
|
||||
|
||||
private void initJavaSubtitle(VersionSetting versionSetting) {
|
||||
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
|
||||
.subscribe(Task.of(Schedulers.javafx(),
|
||||
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java")
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Directory"))));
|
||||
private void initJavaSubtitle() {
|
||||
VersionSetting versionSetting = lastVersionSetting;
|
||||
if (versionSetting == null)
|
||||
return;
|
||||
Task.ofResult(versionSetting::getJavaVersion)
|
||||
.thenAccept(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
|
||||
.start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -311,7 +313,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
private void loadIcon() {
|
||||
if (versionId == null) {
|
||||
iconPickerItem.setImage(new Image("/assets/img/grass.png"));
|
||||
iconPickerItem.setImage(newImage("/assets/img/grass.png"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,7 +321,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
if (iconFile.exists())
|
||||
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
iconPickerItem.setImage(new Image("/assets/img/grass.png"));
|
||||
iconPickerItem.setImage(newImage("/assets/img/grass.png"));
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,7 @@ public class Versions {
|
||||
isMovingToTrashSupported ? i18n("version.manage.remove.confirm.trash", version, version + "_removed") :
|
||||
i18n("version.manage.remove.confirm", version);
|
||||
Controllers.confirmDialog(message, i18n("message.confirm"), () -> {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
profile.getRepository().removeVersionFromDisk(version);
|
||||
}, null);
|
||||
}
|
||||
|
||||
@@ -81,8 +79,7 @@ public class Versions {
|
||||
|
||||
public static void updateGameAssets(Profile profile, String version) {
|
||||
Version resolvedVersion = profile.getRepository().getResolvedVersion(version);
|
||||
TaskExecutor executor = new GameAssetIndexDownloadTask(profile.getDependency(), resolvedVersion)
|
||||
.then(new GameAssetDownloadTask(profile.getDependency(), resolvedVersion))
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), resolvedVersion, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
|
||||
.executor();
|
||||
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"));
|
||||
executor.start();
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItem extends Control {
|
||||
@@ -48,7 +49,7 @@ public class WorldListItem extends Control {
|
||||
this.world = world;
|
||||
this.simpleDateFormat = new SimpleDateFormat(i18n("world.time"));
|
||||
|
||||
title.set(world.getWorldName());
|
||||
title.set(parseColorEscapes(world.getWorldName()));
|
||||
subtitle.set(i18n("world.description", world.getFileName(), simpleDateFormat.format(new Date(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,49 +17,83 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
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.*;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
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> {
|
||||
public class WorldListPage extends ListPageBase<WorldListItem> {
|
||||
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
||||
|
||||
private Path savesDir;
|
||||
private List<World> worlds;
|
||||
private Profile profile;
|
||||
private String id;
|
||||
private String gameVersion;
|
||||
|
||||
public WorldListPage() {
|
||||
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
installWorld(modpacks.get(0));
|
||||
});
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||
|
||||
setLoading(true);
|
||||
Task.ofResult(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.finalizedResult(Schedulers.javafx(), (result, isDependentsSucceeded) -> {
|
||||
setLoading(false);
|
||||
if (isDependentsSucceeded)
|
||||
itemsProperty().setAll(result.stream().map(WorldListItem::new).collect(Collectors.toList()));
|
||||
}).start();
|
||||
showAll.addListener(e -> {
|
||||
if (worlds != null)
|
||||
itemsProperty().setAll(worlds.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ToolbarListPageSkin createDefaultSkin() {
|
||||
return new WorldListPageSkin();
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
this.id = id;
|
||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (profile == null || id == null)
|
||||
return;
|
||||
|
||||
setLoading(true);
|
||||
Task
|
||||
.of(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
|
||||
.thenSupply(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.whenComplete(Schedulers.javafx(), (result, isDependentSucceeded, exception) -> {
|
||||
worlds = result;
|
||||
setLoading(false);
|
||||
if (isDependentSucceeded)
|
||||
itemsProperty().setAll(result.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("world.import.choose"));
|
||||
@@ -74,10 +108,10 @@ public class WorldListPage extends ListPage<WorldListItem> {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
Task.ofResult(() -> new World(zipFile.toPath()))
|
||||
.finalizedResult(Schedulers.javafx(), world -> {
|
||||
.whenComplete(Schedulers.javafx(), world -> {
|
||||
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.of(() -> world.install(savesDir, name))
|
||||
.finalized(Schedulers.javafx(), var -> {
|
||||
.whenComplete(Schedulers.javafx(), () -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
@@ -92,4 +126,36 @@ public class WorldListPage extends ListPage<WorldListItem> {
|
||||
Controllers.dialog(i18n("world.import.invalid"));
|
||||
}).start();
|
||||
}
|
||||
|
||||
public boolean isShowAll() {
|
||||
return showAll.get();
|
||||
}
|
||||
|
||||
public BooleanProperty showAllProperty() {
|
||||
return showAll;
|
||||
}
|
||||
|
||||
public void setShowAll(boolean showAll) {
|
||||
this.showAll.set(showAll);
|
||||
}
|
||||
|
||||
private class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
|
||||
|
||||
WorldListPageSkin() {
|
||||
super(WorldListPage.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(WorldListPage skinnable) {
|
||||
JFXCheckBox chkShowAll = new JFXCheckBox();
|
||||
chkShowAll.getStyleClass().add("jfx-tool-bar-checkbox");
|
||||
chkShowAll.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
chkShowAll.setText(i18n("world.show_all"));
|
||||
chkShowAll.selectedProperty().bindBidirectional(skinnable.showAllProperty());
|
||||
|
||||
return Arrays.asList(chkShowAll,
|
||||
createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh),
|
||||
createToolbarButton(i18n("world.add"), SVG::plus, skinnable::add));
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-9
@@ -17,19 +17,19 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplayer {
|
||||
@@ -59,17 +59,16 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
pane.setSubtitle((String) subtitle);
|
||||
}
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
runInFX(() -> {
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
pane.fireEvent(new DialogCloseEvent());
|
||||
runInFX(() -> {
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageType.FINE, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
Controllers.dialog(i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd());
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
@@ -77,9 +76,9 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
|
||||
((WizardProvider.FailureCallback)settings.get("failure_callback")).onFail(settings, executor.getLastException(), () -> onEnd());
|
||||
else if (settings.get("failure_message") instanceof String)
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
Controllers.dialog(appendix, i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog(appendix, i18n("wizard.failed"), MessageType.ERROR, () -> onEnd());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -83,7 +83,12 @@ public class WizardController implements Navigation {
|
||||
@Override
|
||||
public void onPrev(boolean cleanUp) {
|
||||
if (!canPrev()) {
|
||||
throw new IllegalStateException("Cannot go backward since this is the back page. Pages: " + pages);
|
||||
if (provider.cancelIfCannotGoBack()) {
|
||||
onCancel();
|
||||
return;
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot go backward since this is the back page. Pages: " + pages);
|
||||
}
|
||||
}
|
||||
|
||||
Node page = pages.pop();
|
||||
|
||||
@@ -26,6 +26,9 @@ public interface WizardProvider {
|
||||
Object finish(Map<String, Object> settings);
|
||||
Node createPage(WizardController controller, int step, Map<String, Object> settings);
|
||||
boolean cancel();
|
||||
default boolean cancelIfCannotGoBack() {
|
||||
return false;
|
||||
}
|
||||
|
||||
interface FailureCallback {
|
||||
void onFail(Map<String, Object> settings, Exception exception, Runnable next);
|
||||
|
||||
@@ -70,6 +70,10 @@ public final class IntegrityChecker {
|
||||
for (ZipEntry entry : zip.stream().toArray(ZipEntry[]::new)) {
|
||||
String filename = entry.getName();
|
||||
try (InputStream in = zip.getInputStream(entry)) {
|
||||
if (in == null) {
|
||||
throw new IOException("entry is null");
|
||||
}
|
||||
|
||||
if (SIGNATURE_FILE.equals(filename)) {
|
||||
signature = IOUtils.readFullyAsByteArray(in);
|
||||
} else {
|
||||
|
||||
@@ -26,7 +26,8 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.UpgradeDialog;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
@@ -91,41 +92,42 @@ public final class UpdateHandler {
|
||||
public static void updateFrom(RemoteVersion version) {
|
||||
checkFxUserThread();
|
||||
|
||||
Path downloaded;
|
||||
try {
|
||||
downloaded = Files.createTempFile("hmcl-update-", ".jar");
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to create temp file", e);
|
||||
return;
|
||||
}
|
||||
Controllers.dialog(new UpgradeDialog(() -> {
|
||||
Path downloaded;
|
||||
try {
|
||||
downloaded = Files.createTempFile("hmcl-update-", ".jar");
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to create temp file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Task task = new HMCLDownloadTask(version, downloaded);
|
||||
Task task = new HMCLDownloadTask(version, downloaded);
|
||||
|
||||
TaskExecutor executor = task.executor();
|
||||
Region dialog = Controllers.taskDialog(executor, i18n("message.downloading"));
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
TaskExecutor executor = task.executor();
|
||||
Controllers.taskDialog(executor, i18n("message.downloading"));
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
|
||||
if (success) {
|
||||
try {
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
throw new IOException("Current JAR is not verified");
|
||||
if (success) {
|
||||
try {
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
throw new IOException("Current JAR is not verified");
|
||||
}
|
||||
|
||||
requestUpdate(downloaded, getCurrentLocation());
|
||||
System.exit(0);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageType.ERROR));
|
||||
}
|
||||
|
||||
requestUpdate(downloaded, getCurrentLocation());
|
||||
System.exit(0);
|
||||
} catch (IOException e) {
|
||||
} else {
|
||||
Throwable e = executor.getLastException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
return;
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
|
||||
}
|
||||
|
||||
} else {
|
||||
Throwable e = task.getLastException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private static void applyUpdate(Path target) throws IOException {
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
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;
|
||||
@@ -50,6 +51,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
put("Location is not set", i18n("crash.NoClassDefFound"));
|
||||
put("UnsatisfiedLinkError", i18n("crash.user_fault"));
|
||||
put("java.lang.NoClassDefFoundError", i18n("crash.NoClassDefFound"));
|
||||
put("org.jackhuang.hmcl.util.ResourceNotFoundError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.VerifyError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchFieldError", i18n("crash.NoClassDefFound"));
|
||||
@@ -69,7 +71,10 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
String info = entry.getValue();
|
||||
LOG.severe(info);
|
||||
try {
|
||||
MessageBox.show(info);
|
||||
Alert alert = new Alert(AlertType.INFORMATION, info);
|
||||
alert.setTitle(i18n("message.info"));
|
||||
alert.setHeaderText(i18n("message.info"));
|
||||
alert.showAndWait();
|
||||
} catch (Throwable t) {
|
||||
LOG.log(Level.SEVERE, "Unable to show message", t);
|
||||
}
|
||||
@@ -103,7 +108,10 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n";
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
|
||||
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
|
||||
" JVM Total Memory: " + Runtime.getRuntime().totalMemory() + "\n" +
|
||||
" JVM Free Memory: " + Runtime.getRuntime().freeMemory() + "\n";
|
||||
|
||||
LOG.log(Level.SEVERE, text);
|
||||
|
||||
|
||||
+14
-16
@@ -15,29 +15,27 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
* Suppress the throwable when we make sure the resource cannot miss.
|
||||
* @see CrashReporter
|
||||
*/
|
||||
public final class SafeIntStringConverter extends StringConverter<Number> {
|
||||
public static final SafeIntStringConverter INSTANCE = new SafeIntStringConverter();
|
||||
|
||||
private SafeIntStringConverter() {
|
||||
public class ResourceNotFoundError extends Error {
|
||||
public ResourceNotFoundError(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
return Optional.ofNullable(string).map(Lang::toIntOrNull).orElse(null);
|
||||
public ResourceNotFoundError(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Number object) {
|
||||
return Optional.ofNullable(object).map(Object::toString).orElse("");
|
||||
public static InputStream getResourceAsStream(String url) {
|
||||
InputStream stream = ResourceNotFoundError.class.getResourceAsStream(url);
|
||||
if (stream == null)
|
||||
throw new ResourceNotFoundError("Resource not found: " + url);
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
@@ -57,4 +57,12 @@ public final class I18n {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasKey(String key) {
|
||||
try {
|
||||
getResourceBundle().getString(key);
|
||||
return true;
|
||||
} catch (MissingResourceException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,6 @@ public final class Locales {
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH);
|
||||
|
||||
public static SupportedLocale getLocale(int index) {
|
||||
return Lang.get(LOCALES, index).orElse(DEFAULT);
|
||||
}
|
||||
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null) return DEFAULT;
|
||||
switch (name.toLowerCase()) {
|
||||
|
||||
Binary file not shown.
@@ -57,8 +57,11 @@
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
}
|
||||
|
||||
.rippler-container= .label {
|
||||
-fx-text-fill: white;
|
||||
.notice-pane > .label {
|
||||
-fx-text-fill: #0079FF;
|
||||
-fx-font-size: 20;
|
||||
-fx-wrap-text: true;
|
||||
-fx-text-alignment: CENTER;
|
||||
}
|
||||
|
||||
.class-title {
|
||||
@@ -132,6 +135,16 @@
|
||||
-fx-fill: -fx-base-darker-color;
|
||||
}
|
||||
|
||||
.sponsor-pane {
|
||||
-fx-padding: 16;
|
||||
-fx-background-color: white;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.sponsor-pane .label {
|
||||
-fx-wrap-text: true;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Tab Pane *
|
||||
@@ -342,12 +355,11 @@
|
||||
|
||||
.jfx-tool-bar HBox {
|
||||
-fx-alignment: center;
|
||||
/* -fx-spacing: 25.0;*/
|
||||
-fx-padding: 0.0 5.0;
|
||||
}
|
||||
|
||||
.jfx-tool-bar .jfx-decorator-button {
|
||||
-fx-cursor: head;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.jfx-tool-bar Label {
|
||||
@@ -370,6 +382,8 @@
|
||||
-fx-pref-height: 42;
|
||||
-fx-padding: 2 2 2 2;
|
||||
-fx-background-color: -fx-base-check-color;
|
||||
-fx-alignment: CENTER-LEFT;
|
||||
-fx-spacing: 8;
|
||||
}
|
||||
|
||||
.jfx-tool-bar-second .jfx-rippler {
|
||||
@@ -447,33 +461,6 @@
|
||||
-fx-fill: -fx-base-check-color;
|
||||
}
|
||||
|
||||
.custom-jfx-radio-button {
|
||||
-fx-font-size: 16.0px;
|
||||
}
|
||||
|
||||
.custom-jfx-radio-button .radio {
|
||||
-fx-stroke-width: 2.0px;
|
||||
-fx-fill: transparent;
|
||||
}
|
||||
|
||||
.custom-jfx-radio-button-blue {
|
||||
-fx-text-fill: -fx-base-color;
|
||||
-jfx-selected-color: -fx-base-color;
|
||||
-jfx-unselected-color: #212121;
|
||||
}
|
||||
|
||||
.custom-jfx-radio-button-red {
|
||||
-fx-text-fill: #f44336;
|
||||
-jfx-selected-color: #f44336;
|
||||
-jfx-unselected-color: #b71c1c;
|
||||
}
|
||||
|
||||
.custom-jfx-radio-button-green {
|
||||
-fx-text-fill: #4caf50;
|
||||
-jfx-selected-color: #4caf50;
|
||||
-jfx-unselected-color: #1b5e20;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Slider *
|
||||
@@ -514,18 +501,12 @@
|
||||
-fx-font-size: 10.0;
|
||||
}
|
||||
|
||||
/******************************************************/
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Rippler *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
/*.jfx-rippler {
|
||||
-fx-rippler-fill: -fx-base-color;
|
||||
-fx-mask-type: RECT;
|
||||
}*/
|
||||
.jfx-rippler:hover {
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
@@ -536,62 +517,29 @@
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
.custom-jfx-button-raised .jfx-rippler {
|
||||
-jfx-rippler-fill: YELLOW;
|
||||
}
|
||||
|
||||
.custom-jfx-button-raised {
|
||||
-fx-padding: 0.7em 0.57em;
|
||||
-fx-font-size: 14.0px;
|
||||
-jfx-button-type: RAISED;
|
||||
-fx-background-color: rgb(102.0, 153.0, 102.0);
|
||||
-fx-pref-width: 200.0;
|
||||
-fx-text-fill: WHITE;
|
||||
}
|
||||
|
||||
.circle-jfx-button-raised .jfx-rippler {
|
||||
-jfx-rippler-fill: YELLOW;
|
||||
}
|
||||
|
||||
.circle-jfx-button-raised {
|
||||
-fx-padding: 0.7em 0.57em;
|
||||
-fx-font-size: 14.0px;
|
||||
-jfx-button-type: RAISED;
|
||||
-fx-background-color: rgb(102.0, 153.0, 102.0);
|
||||
-fx-pref-width: 200.0;
|
||||
-fx-text-fill: WHITE;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
.jfx-button {
|
||||
-jfx-disable-visual-focus: true;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.jfx-button-raised {
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: -fx-base-color;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.jfx-button-raised .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
}
|
||||
|
||||
.jfx-button-raised .label {
|
||||
-fx-text-fill: white;
|
||||
.jfx-button-raised, .jfx-button-raised * {
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.jfx-button-border {
|
||||
-fx-text-fill: -fx-base-color;
|
||||
-fx-border-color: gray;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-border-width: 0.2px;
|
||||
-fx-padding: 8px;
|
||||
}
|
||||
|
||||
.jfx-button-border .jfx-rippler {
|
||||
-jfx-rippler-fill: -fx-base-check-color;
|
||||
}
|
||||
|
||||
.jfx-button-border .label {
|
||||
-fx-text-fill: -fx-base-color;
|
||||
.jfx-button-border, .jfx-button-border * {
|
||||
-fx-text-fill: -fx-base-darker-color;
|
||||
}
|
||||
|
||||
.jfx-button-raised-round {
|
||||
@@ -610,20 +558,6 @@
|
||||
-jfx-checked-color: -fx-base-check-color;
|
||||
}
|
||||
|
||||
.custom-jfx-check-box {
|
||||
-jfx-checked-color: RED;
|
||||
}
|
||||
|
||||
.custom-jfx-check-box-all-colored {
|
||||
-jfx-checked-color: -fx-base-color;
|
||||
-jfx-unchecked-color: -fx-base-color;
|
||||
-fx-text-fill: -fx-base-color;
|
||||
}
|
||||
|
||||
.custom-jfx-check-box-text-colored {
|
||||
-fx-text-fill: rgb(153.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Progress Bar *
|
||||
@@ -647,15 +581,6 @@
|
||||
-fx-background-color: -fx-base-check-color;
|
||||
}
|
||||
|
||||
.custom-jfx-progress-bar > .bar {
|
||||
-fx-background-color: rgb(255.0, 128.0, 0.0);
|
||||
}
|
||||
|
||||
.custom-jfx-progress-bar-stroke > .bar {
|
||||
-fx-background-color: -fx-base-color;
|
||||
-fx-padding: 6;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Textfield *
|
||||
@@ -708,47 +633,6 @@
|
||||
-jfx-expanded: false;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view .jfx-list-cell:odd:selected > .jfx-rippler > StackPane,
|
||||
.custom-jfx-list-view .jfx-list-cell:even:selected > .jfx-rippler > StackPane {
|
||||
-fx-background-color: rgba(255, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.custom-jfx-list-view {
|
||||
-fx-background-insets: 0.0;
|
||||
-jfx-cell-horizontal-margin: 0.0;
|
||||
-jfx-cell-vertical-margin: 5.0;
|
||||
-jfx-expanded: false;
|
||||
-fx-max-width: 200.0px;
|
||||
/* important to hide the list change of height */
|
||||
-fx-background-color: TRANSPARENT;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view .jfx-rippler {
|
||||
-jfx-rippler-fill: RED;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view1 {
|
||||
-jfx-vertical-gap: 10.0;
|
||||
-fx-pref-width: 150px;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view-icon,
|
||||
.jfx-list-cell:selected .label .custom-jfx-list-view-icon {
|
||||
-fx-fill: -fx-base-color;
|
||||
-fx-padding: 0.0 10.0 0.0 5.0;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view-icon-container {
|
||||
-fx-pref-width: 40px;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view .sublist-item {
|
||||
-fx-border-color: #e0e0e0;
|
||||
-fx-border-width: 1 0 1 0;
|
||||
}
|
||||
|
||||
.options-list {
|
||||
-fx-background-color: transparent;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
|
||||
@@ -798,20 +682,6 @@
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view .jfx-list-cell .sublist-header > .drop-icon {
|
||||
-fx-background-color: GRAY;
|
||||
}
|
||||
|
||||
.custom-jfx-list-view .jfx-list-cell:filled:hover .sublist-header > .drop-icon {
|
||||
-fx-background-color: BLACK;
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX SUBLIST IMPORTANT *
|
||||
@@ -851,10 +721,6 @@
|
||||
-fx-padding: 0 0 0 12;
|
||||
}
|
||||
|
||||
/*.custom-jfx-list-view .sublist-container {
|
||||
-fx-padding : 0 0 5 0;
|
||||
}*/
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Toggle Button *
|
||||
@@ -865,14 +731,6 @@
|
||||
-jfx-toggle-color: -fx-base-check-color;
|
||||
}
|
||||
|
||||
.custom-jfx-toggle-button {
|
||||
-jfx-toggle-color: #4285F4;
|
||||
}
|
||||
|
||||
.custom-jfx-toggle-button-red {
|
||||
-jfx-toggle-color: red;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
-fx-font-size: 14.0px;
|
||||
}
|
||||
@@ -1038,6 +896,14 @@
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.large-spinner-pane .jfx-spinner {
|
||||
-jfx-radius: 20;
|
||||
}
|
||||
|
||||
.large-spinner-pane .jfx-spinner {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.second-spinner {
|
||||
-jfx-radius: 30;
|
||||
}
|
||||
@@ -1131,7 +997,8 @@
|
||||
}
|
||||
|
||||
.jfx-decorator-title {
|
||||
-fx-text-fill: -fx-base-text-fill; -fx-font-size: 15;
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
-fx-font-size: 15;
|
||||
}
|
||||
|
||||
.resize-border {
|
||||
@@ -1149,7 +1016,7 @@
|
||||
}
|
||||
|
||||
.white-background {
|
||||
-fx-background-color: rgba(255, 255, 255);
|
||||
-fx-background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -1158,6 +1025,12 @@
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
.no-header .column-header-background {
|
||||
-fx-max-height: 0;
|
||||
-fx-pref-height: 0;
|
||||
-fx-min-height: 0;
|
||||
}
|
||||
|
||||
.tree-table-view {
|
||||
-fx-tree-table-color: rgba(82, 100, 174, 0.4);
|
||||
-fx-tree-table-rippler-color: rgba(82, 100, 174, 0.6);
|
||||
@@ -1190,7 +1063,7 @@
|
||||
|
||||
.tree-table-view .column-header .label {
|
||||
-fx-text-fill: #949494;
|
||||
-fx-padding: 16 0 16 0;
|
||||
-fx-padding: 8 0 8 0;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header .arrow, .tree-table-view .column-header .sort-order-dot {
|
||||
@@ -1208,8 +1081,8 @@
|
||||
|
||||
.tree-table-view .tree-table-cell {
|
||||
-fx-border-width: 0 0 0 0;
|
||||
-fx-padding: 16 0 16 0;
|
||||
-fx-alignment: top-center;
|
||||
-fx-padding: 0 0 0 0;
|
||||
/*-fx-alignment: top-center;*/
|
||||
}
|
||||
|
||||
.tree-table-view .column-overlay {
|
||||
@@ -1254,7 +1127,7 @@
|
||||
}
|
||||
|
||||
.tree-table-row-group {
|
||||
-fx-background-color: rgba(230, 230, 230);
|
||||
-fx-background-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.tree-table-view .menu-item:focused {
|
||||
@@ -1362,12 +1235,3 @@
|
||||
.fit-width {
|
||||
-fx-pref-width: 100%;
|
||||
}
|
||||
/*
|
||||
.jfx-scroll-pane .main-header {
|
||||
-fx-background-image: url("../bg1.jpg");
|
||||
}
|
||||
|
||||
.jfx-scroll-pane .condensed-header {
|
||||
-fx-background-image: url("../bg4.jpg");
|
||||
}
|
||||
*/
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
|
||||
<VBox fx:id="listPane" spacing="10" style="-fx-padding: 20 20 70 20;">
|
||||
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
|
||||
<JFXButton prefWidth="40" prefHeight="40" buttonType="RAISED" onMouseClicked="#onAdd" styleClass="jfx-button-raised-round">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -19,7 +19,7 @@
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblForge">Install Forge</Label>
|
||||
<Label fx:id="lblForge"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
@@ -31,7 +31,7 @@
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblLiteLoader">Install LiteLoader</Label>
|
||||
<Label fx:id="lblLiteLoader"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
@@ -43,7 +43,7 @@
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblOptiFine">Install OptiFine</Label>
|
||||
<Label fx:id="lblOptiFine"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblForge">Install Forge</Label>
|
||||
<Label fx:id="lblForge"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
@@ -31,7 +31,7 @@
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblLiteLoader">Install LiteLoader</Label>
|
||||
<Label fx:id="lblLiteLoader"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
@@ -43,7 +43,7 @@
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblOptiFine">Install OptiFine</Label>
|
||||
<Label fx:id="lblOptiFine"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<fx:define>
|
||||
<Insets fx:id="insets" bottom="12" />
|
||||
</fx:define>
|
||||
<SpinnerPane fx:id="spinnerPane">
|
||||
<SpinnerPane fx:id="spinnerPane" styleClass="large-spinner-pane">
|
||||
<VBox fx:id="borderPane" alignment="CENTER" FXUtils.limitWidth="500">
|
||||
<HBox style="-fx-padding: 0 0 16 5;"><Label text="%modpack.task.install" /></HBox>
|
||||
<ComponentList>
|
||||
|
||||
@@ -5,22 +5,31 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane"
|
||||
type="BorderPane"
|
||||
prefHeight="400.0" prefWidth="600.0">
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<VBox fx:id="centrePane">
|
||||
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
||||
<JFXCheckBox fx:id="chkRelease" text="%version.game.release" selected="true" />
|
||||
<JFXCheckBox fx:id="chkSnapshot" text="%version.game.snapshot" />
|
||||
<JFXCheckBox fx:id="chkOld" text="%version.game.old" />
|
||||
</HBox>
|
||||
<JFXListView fx:id="list" styleClass="jfx-list-view" VBox.vgrow="ALWAYS">
|
||||
</JFXListView>
|
||||
</VBox>
|
||||
<StackPane fx:id="failedPane">
|
||||
<Label onMouseClicked="#onRefresh" style="-fx-text-fill: #0079FF; -fx-font-size: 20;" text="%download.failed.refresh" />
|
||||
</StackPane>
|
||||
<StackPane fx:id="emptyPane">
|
||||
<Label onMouseClicked="#onBack" style="-fx-text-fill: #0079FF; -fx-font-size: 20;" text="%download.failed.empty" />
|
||||
</StackPane>
|
||||
<top>
|
||||
<StackPane styleClass="sponsor-pane" onMouseClicked="#onSponsor">
|
||||
<Label text="%sponsor.bmclapi" />
|
||||
</StackPane>
|
||||
</top>
|
||||
<center>
|
||||
<StackPane fx:id="root">
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<VBox fx:id="centrePane">
|
||||
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
||||
<JFXCheckBox fx:id="chkRelease" text="%version.game.release" selected="true" />
|
||||
<JFXCheckBox fx:id="chkSnapshot" text="%version.game.snapshot" />
|
||||
<JFXCheckBox fx:id="chkOld" text="%version.game.old" />
|
||||
</HBox>
|
||||
<JFXListView fx:id="list" styleClass="jfx-list-view" VBox.vgrow="ALWAYS">
|
||||
</JFXListView>
|
||||
</VBox>
|
||||
<StackPane fx:id="failedPane" styleClass="notice-pane">
|
||||
<Label onMouseClicked="#onRefresh" text="%download.failed.refresh" />
|
||||
</StackPane>
|
||||
<StackPane fx:id="emptyPane" styleClass="notice-pane">
|
||||
<Label onMouseClicked="#onBack" text="%download.failed.empty" />
|
||||
</StackPane>
|
||||
</StackPane>
|
||||
</center>
|
||||
</fx:root>
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# Contributors: dxNeil
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.copyright.statement=Copyright (c) 2019 huangyuhui.
|
||||
about.author=Author
|
||||
about.author.statement=huanghongxun ([email protected])
|
||||
about.thanks_to=Thanks to
|
||||
@@ -66,6 +66,7 @@ account.username=Name
|
||||
|
||||
archive.author=Authors
|
||||
archive.game_version=Game
|
||||
archive.name=Name
|
||||
archive.version=Version
|
||||
|
||||
assets.download=Download assets
|
||||
@@ -90,11 +91,12 @@ crash.NoClassDefFound=Please check "HMCL" software is complete.
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
||||
|
||||
download=Download
|
||||
download.code.404=File does not found in remote server
|
||||
download.failed=Failed to download
|
||||
download.failed.empty=No candidates. Click here to return.
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=Mojang
|
||||
download.provider.mojang=Mojang (Forge and OptiFine installation being downloaded from BMCLAPI)
|
||||
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
@@ -123,12 +125,24 @@ input.not_empty=Required field
|
||||
input.url=Must be a valid URL.
|
||||
|
||||
install=Install New Game
|
||||
install.change_version=Change version
|
||||
install.change_version.confirm=Sure to update %s from verison %s to %s?
|
||||
install.failed=Failed to install
|
||||
install.failed.downloading=Failed to install due to some files not downloaded successfully
|
||||
install.failed.downloading.detail=Failed to download file: %s
|
||||
install.failed.downloading.timeout=Download timed out: %s
|
||||
install.failed.install_online=Unable to recognize what you provided installer file is
|
||||
install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13
|
||||
install.failed.version_mismatch=The library requires game version %s, but actual version is %s.
|
||||
install.installer.choose=Choose a %s version
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Game
|
||||
install.installer.install=
|
||||
install.installer.install=Install %s
|
||||
install.installer.install_offline=Install/Upgrade from local file
|
||||
install.installer.install_offline.extension=Forge/OptiFine installer
|
||||
install.installer.install_offline.tooltip=Support importing Forge/OptiFine installer jar file
|
||||
install.installer.install_online=Install Online
|
||||
install.installer.install_online.tooltip=Support Forge, OptiFine, LiteLoader installation.
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=%s not Installed
|
||||
install.installer.optifine=OptiFine
|
||||
@@ -252,7 +266,11 @@ mods.add=Add mods
|
||||
mods.add.failed=Failed to add mods %s.
|
||||
mods.add.success=Successfully added mods %s.
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.enable=Enable
|
||||
mods.disable=Disable
|
||||
mods.name=Name
|
||||
mods.remove=Remove
|
||||
mods.not_modded=You should install a mod loader first (Forge, or LiteLoader)
|
||||
|
||||
datapack=Data packs
|
||||
datapack.add=Add data pack
|
||||
@@ -262,6 +280,7 @@ datapack.title=World %s - Datapacks
|
||||
datapack.remove=Remove
|
||||
|
||||
world=Worlds/Datapacks
|
||||
world.add=Add world(.zip)
|
||||
world.datapack=Manage data packs
|
||||
world.datapack.1_13=Only Minecraft 1.13 and later versions support data packs.
|
||||
world.description=%s. Last played time: %s. Game version: %s.
|
||||
@@ -278,6 +297,7 @@ world.import.invalid=Not a valid world zip
|
||||
world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.reveal=Reveal in Explorer
|
||||
world.show_all=Show all
|
||||
world.time=EEE, MMM d, yyyy HH:mm:ss
|
||||
|
||||
profile=Game Directories
|
||||
@@ -364,7 +384,12 @@ settings.type.global.manage=Global Game Settings
|
||||
settings.type.global.edit=Configure global game settings
|
||||
settings.type.special.enable=Enable specialized settings for this game
|
||||
|
||||
sponsor.bmclapi=To obtain a stable download service, please consider support BMCLAPI. Click here for more information
|
||||
sponsor.hmcl=HMCL is a free, open source Minecraft launcher the enables users to easily manage multiple, separate installations of Minecraft. We're using Afdian so that we can continue to pay for our hosting and project development. Click here for more information.
|
||||
|
||||
update=Update
|
||||
update.accept=Update
|
||||
update.changelog=Changelog
|
||||
update.channel.dev=Update to development version
|
||||
update.channel.stable=Update to stable version
|
||||
update.checking=Checking for updates
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
|
||||
about.copyright=版權
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.copyright.statement=Copyright (c) 2019 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun ([email protected])
|
||||
about.thanks_to=鳴謝
|
||||
@@ -65,6 +65,7 @@ account.username=使用者名稱
|
||||
|
||||
archive.author=作者
|
||||
archive.game_version=遊戲版本
|
||||
archive.name=名稱
|
||||
archive.version=版本
|
||||
|
||||
assets.download=下載資源
|
||||
@@ -89,11 +90,12 @@ crash.NoClassDefFound=請確認 HMCL 本體是否完整,或更新您的 Java
|
||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
||||
|
||||
download=下載
|
||||
download.code.404=遠程伺服器不包含需要下載的文件
|
||||
download.failed=下載失敗
|
||||
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
|
||||
download.failed.refresh=載入版本列表失敗,點擊此處重試。
|
||||
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=官方伺服器(Mojang)
|
||||
download.provider.mojang=官方伺服器(Forge 和 OptiFine 自動安裝的下載來源是 BMCLAPI)
|
||||
|
||||
extension.bat=Windows 腳本
|
||||
extension.mod=模組檔案
|
||||
@@ -122,12 +124,24 @@ input.not_empty=必填
|
||||
input.url=必須為有效連結
|
||||
|
||||
install=新增遊戲
|
||||
install.change_version=更換版本
|
||||
install.change_version.confirm=你確定要將 %s 從 %s 更新到 %s 嗎?
|
||||
install.failed=安裝失敗
|
||||
install.failed.downloading=安裝失敗,部分文件未能完成下載
|
||||
install.failed.downloading.detail=未能下載檔案:%s
|
||||
install.failed.downloading.timeout=下載超時:%s
|
||||
install.failed.install_online=無法識別要安裝的軟體
|
||||
install.failed.optifine_conflict=暫不支持 OptiFine 與 Forge 同時安裝在 Minecraft 1.13 上
|
||||
install.failed.version_mismatch=該軟體需要的遊戲版本為 %s,但實際的遊戲版本為 %s。
|
||||
install.installer.choose=選擇 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=遊戲
|
||||
install.installer.install=安裝 %s
|
||||
install.installer.install_offline=從本地檔案安裝/升級
|
||||
install.installer.install_offline.extension=Forge/OptiFine 安裝器
|
||||
install.installer.install_offline.tooltip=支持導入已經下載好的 Forge/OptiFine 安裝器
|
||||
install.installer.install_online=在線安裝
|
||||
install.installer.install_online.tooltip=支持安裝 Forge、OptiFine、LiteLoader
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=暫時不安裝 %s,可以點擊此處安裝
|
||||
install.installer.optifine=OptiFine
|
||||
@@ -251,7 +265,10 @@ mods.add=新增模組
|
||||
mods.add.failed=新增模組 %s 失敗。
|
||||
mods.add.success=成功新增模組 %s。
|
||||
mods.choose_mod=選擇模組
|
||||
mods.enable=啟用
|
||||
mods.disable=禁用
|
||||
mods.remove=刪除
|
||||
mods.not_modded=你需要先在自動安裝頁面安裝 Forge 或 LiteLoader 才能進行模組管理。
|
||||
|
||||
datapack=資料包
|
||||
datapack.add=添加資料包
|
||||
@@ -261,6 +278,7 @@ datapack.title=世界 %s - 資料包
|
||||
datapack.remove=刪除
|
||||
|
||||
world=世界/資料包
|
||||
world.add=添加世界
|
||||
world.datapack=管理資料包
|
||||
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支持資料包
|
||||
world.description=%s. 上一次遊戲時間: %s. 遊戲版本: %s
|
||||
@@ -277,6 +295,7 @@ world.game_version=遊戲版本
|
||||
world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
world.reveal=打開資料夾
|
||||
world.show_all=顯示全部
|
||||
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
profile=遊戲目錄
|
||||
@@ -363,7 +382,12 @@ settings.type.global.manage=全域遊戲設定
|
||||
settings.type.global.edit=編輯全域遊戲設定
|
||||
settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本)
|
||||
|
||||
sponsor.bmclapi=請贊助 BMCLAPI 從而獲得穩定高速的下載服務,點擊此處查閱詳細訊息。
|
||||
sponsor.hmcl=HMCL 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、運行遊戲。您的贊助將幫助 HMCL 獲得更好的發展、支持穩定高速的遊戲安裝與文件下載服務。點擊此處查閱更多詳細訊息。
|
||||
|
||||
update=啟動器更新
|
||||
update.accept=更新
|
||||
update.changelog=更新日誌
|
||||
update.channel.dev=更新到開發版
|
||||
update.channel.stable=更新到推薦版本
|
||||
update.checking=正在檢查更新
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
|
||||
about.copyright=版权
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.copyright.statement=Copyright (c) 2019 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun ([email protected])
|
||||
about.thanks_to=鸣谢
|
||||
@@ -65,6 +65,7 @@ account.username=用户名
|
||||
|
||||
archive.author=作者
|
||||
archive.game_version=游戏版本
|
||||
archive.name=名称
|
||||
archive.version=版本
|
||||
|
||||
assets.download=下载资源
|
||||
@@ -89,11 +90,12 @@ crash.NoClassDefFound=请确认 HMCL 本体是否完整,或更新您的 Java
|
||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||
|
||||
download=下载
|
||||
download.code.404=远程服务器不包含需要下载的文件
|
||||
download.failed=下载失败
|
||||
download.failed.empty=没有可供安装的版本,点击此处返回。
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=官方(Mojang)
|
||||
download.provider.mojang=官方(Forge 和 OptiFine 自动安装使用 BMCLAPI 下载源)
|
||||
|
||||
extension.bat=Windows 脚本
|
||||
extension.mod=模组文件
|
||||
@@ -122,12 +124,24 @@ input.not_empty=必填项
|
||||
input.url=必须是合法的链接
|
||||
|
||||
install=添加游戏
|
||||
install.change_version=更换版本
|
||||
install.change_version.confirm=你确定要将 %s 从 %s 更新到 %s 吗?
|
||||
install.failed=安装失败
|
||||
install.failed.downloading=安装失败,部分文件未能完成下载
|
||||
install.failed.downloading.detail=未能下载文件:%s
|
||||
install.failed.downloading.timeout=下载超时:%s
|
||||
install.failed.install_online=无法识别要安装的软件
|
||||
install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装在 Minecraft 1.13 上
|
||||
install.failed.version_mismatch=该软件需要的游戏版本为 %s,但实际的游戏版本为 %s。
|
||||
install.installer.choose=选择 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=游戏
|
||||
install.installer.install=安装 %s
|
||||
install.installer.install_offline=从本地文件安装/升级
|
||||
install.installer.install_offline.extension=Forge/OptiFine 安装器
|
||||
install.installer.install_offline.tooltip=支持导入已经下载好的 Forge/OptiFine 安装器
|
||||
install.installer.install_online=在线安装
|
||||
install.installer.install_online.tooltip=支持安装 Forge、OptiFine、LiteLoader
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=暂不安装 %s,可以点击此处安装
|
||||
install.installer.optifine=OptiFine
|
||||
@@ -251,7 +265,10 @@ mods.add=添加模组
|
||||
mods.add.failed=添加模组 %s 失败。
|
||||
mods.add.success=成功添加模组 %s。
|
||||
mods.choose_mod=选择模组
|
||||
mods.enable=启用
|
||||
mods.disable=禁用
|
||||
mods.remove=删除
|
||||
mods.not_modded=你需要先在自动安装页面安装 Forge 或 LiteLoader 才能进行模组管理。
|
||||
|
||||
datapack=数据包
|
||||
datapack.add=添加数据包
|
||||
@@ -261,6 +278,7 @@ datapack.title=世界 %s - 数据包
|
||||
datapack.remove=删除
|
||||
|
||||
world=世界/数据包
|
||||
world.add=添加世界
|
||||
world.datapack=管理数据包
|
||||
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
|
||||
world.description=%s. 上一次游戏时间: %s. 游戏版本: %s
|
||||
@@ -277,6 +295,7 @@ world.import.invalid=无法识别的存档压缩包
|
||||
world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
world.reveal=打开文件夹
|
||||
world.show_all=显示全部
|
||||
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
profile=游戏目录
|
||||
@@ -363,7 +382,12 @@ settings.type.global.manage=全局游戏设置
|
||||
settings.type.global.edit=编辑全局版本设置
|
||||
settings.type.special.enable=启用游戏特定设置(不影响其他游戏版本)
|
||||
|
||||
sponsor.bmclapi=请赞助 BMCLAPI 从而获得稳定高速的下载服务,点击此处查阅详细信息。
|
||||
sponsor.hmcl=HMCL 是一个免费、开源的 Minecraft 启动器,允许玩家方便快捷地安装、管理、运行游戏。您的赞助将帮助 HMCL 获得更好的发展、支持稳定高速的游戏安装与文件下载服务。点击此处查阅更多详细信息。
|
||||
|
||||
update=启动器更新
|
||||
update.accept=更新
|
||||
update.changelog=更新日志
|
||||
update.channel.dev=更新到开发版
|
||||
update.channel.stable=更新到推荐版本
|
||||
update.checking=正在检查更新
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
||||
"-Dauthlibinjector.side=client",
|
||||
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
|
||||
"-Dauthlibinjector.yggdrasil.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
-1
@@ -23,8 +23,11 @@ import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
@@ -70,7 +73,9 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> properties = it;
|
||||
GameProfile selected = session.getSelectedProfile();
|
||||
server.getYggdrasilService().getProfileRepository().put(selected.getId(), new CompleteGameProfile(selected, properties));
|
||||
ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository = server.getYggdrasilService().getProfileRepository();
|
||||
profileRepository.put(selected.getId(), new CompleteGameProfile(selected, properties));
|
||||
profileRepository.invalidate(selected.getId());
|
||||
});
|
||||
|
||||
return new AuthlibInjectorAccount(server, downloader, username, session);
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
}
|
||||
}
|
||||
|
||||
private class AuthlibInjectorVersionInfo {
|
||||
private static class AuthlibInjectorVersionInfo {
|
||||
@SerializedName("build_number")
|
||||
public int buildNumber;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -36,10 +37,12 @@ public final class Texture {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonParseException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
@@ -36,13 +37,14 @@ public final class User implements Validation {
|
||||
private final String id;
|
||||
|
||||
@Nullable
|
||||
@JsonAdapter(PropertyMapSerializer.class)
|
||||
private final Map<String, String> properties;
|
||||
|
||||
public User(String id) {
|
||||
this(id, null);
|
||||
}
|
||||
|
||||
public User(String id, Map<String, String> properties) {
|
||||
public User(String id, @Nullable Map<String, String> properties) {
|
||||
this.id = id;
|
||||
this.properties = properties;
|
||||
}
|
||||
@@ -51,6 +53,7 @@ public final class User implements Validation {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, String> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.jackhuang.hmcl.auth.NoCharacterException;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
|
||||
public class YggdrasilAccount extends Account {
|
||||
|
||||
private final YggdrasilService service;
|
||||
@@ -48,6 +50,8 @@ public class YggdrasilAccount extends Account {
|
||||
this.username = requireNonNull(username);
|
||||
this.characterUUID = requireNonNull(session.getSelectedProfile().getId());
|
||||
this.session = requireNonNull(session);
|
||||
|
||||
addProfilePropertiesListener();
|
||||
}
|
||||
|
||||
protected YggdrasilAccount(YggdrasilService service, String username, String password, CharacterSelector selector) throws AuthenticationException {
|
||||
@@ -73,6 +77,17 @@ public class YggdrasilAccount extends Account {
|
||||
|
||||
characterUUID = session.getSelectedProfile().getId();
|
||||
authenticated = true;
|
||||
|
||||
addProfilePropertiesListener();
|
||||
}
|
||||
|
||||
private ObjectBinding<Optional<CompleteGameProfile>> profilePropertiesBinding;
|
||||
private void addProfilePropertiesListener() {
|
||||
// binding() is thread-safe
|
||||
// hold the binding so that it won't be garbage-collected
|
||||
profilePropertiesBinding = service.getProfileRepository().binding(characterUUID, true);
|
||||
// and it's safe to add a listener to an ObjectBinding which does not have any listener attached before (maybe tricky)
|
||||
profilePropertiesBinding.addListener((a, b, c) -> this.invalidate());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-1
@@ -20,9 +20,11 @@ package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
@@ -63,7 +65,9 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> properties = it;
|
||||
GameProfile selected = session.getSelectedProfile();
|
||||
service.getProfileRepository().put(selected.getId(), new CompleteGameProfile(selected, properties));
|
||||
ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository = service.getProfileRepository();
|
||||
profileRepository.put(selected.getId(), new CompleteGameProfile(selected, properties));
|
||||
profileRepository.invalidate(selected.getId());
|
||||
});
|
||||
|
||||
return new YggdrasilAccount(service, username, session);
|
||||
|
||||
@@ -165,7 +165,13 @@ public class YggdrasilService {
|
||||
String encodedTextures = profile.getProperties().get("textures");
|
||||
|
||||
if (encodedTextures != null) {
|
||||
TextureResponse texturePayload = fromJson(new String(Base64.getDecoder().decode(encodedTextures), UTF_8), TextureResponse.class);
|
||||
byte[] decodedBinary;
|
||||
try {
|
||||
decodedBinary = Base64.getDecoder().decode(encodedTextures);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ServerResponseMalformedException(e);
|
||||
}
|
||||
TextureResponse texturePayload = fromJson(new String(decodedBinary, UTF_8), TextureResponse.class);
|
||||
return Optional.ofNullable(texturePayload.textures);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
|
||||
Loaded 100 of 191 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user