Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ab8a6769 | ||
|
|
b1cd7057db | ||
|
|
d426952d47 | ||
|
|
67b6a60382 | ||
|
|
9d5da5a39d | ||
|
|
efb14572d6 | ||
|
|
bc3ae5cc7d | ||
|
|
9752dea0e1 | ||
|
|
9afdc99157 | ||
|
|
07febc18d4 | ||
|
|
68fdfb36fd | ||
|
|
09287f9c72 | ||
|
|
4a5ac1b899 | ||
|
|
afad520669 | ||
|
|
9950bef260 | ||
|
|
1753b4d27e | ||
|
|
2bfc85b18f | ||
|
|
24f3dec453 | ||
|
|
4e6237c062 | ||
|
|
5cbde9cc33 | ||
|
|
25d79a94f8 | ||
|
|
a5997be9c4 | ||
|
|
c679667079 | ||
|
|
6b311c679d | ||
|
|
0f1f881367 | ||
|
|
090665528f | ||
|
|
387beb8484 | ||
|
|
daa1a38a63 | ||
|
|
d8a34aac1c | ||
|
|
ce9d882c9f | ||
|
|
84c18c8693 | ||
|
|
9360854150 | ||
|
|
ada63f1452 | ||
|
|
c3cd0dd8a0 | ||
|
|
60143d5299 | ||
|
|
1019c891e2 | ||
|
|
1392ad413a | ||
|
|
61aee90c8d | ||
|
|
19725012fb | ||
|
|
94a71dc88f | ||
|
|
d8cb93c36a | ||
|
|
151097a884 | ||
|
|
e97bad88c6 | ||
|
|
9f494a35ec | ||
|
|
d9d20c969a | ||
|
|
7e08cfedd6 | ||
|
|
1145e31162 | ||
|
|
e4e521c2ef | ||
|
|
d01c9a425a | ||
|
|
a9be348ddf | ||
|
|
07079ee295 | ||
|
|
eb1bab453b | ||
|
|
fd3c496a3d | ||
|
|
35e1e0bdd4 | ||
|
|
28aa137dc1 | ||
|
|
16ee0a040a | ||
|
|
437391a860 | ||
|
|
f61181438f | ||
|
|
d74ab35279 | ||
|
|
bbc2b251dd | ||
|
|
222e39dc48 | ||
|
|
f5b310d959 | ||
|
|
5fb0035b2f | ||
|
|
b8793f7575 | ||
|
|
5c5f091769 | ||
|
|
cf6485aea8 | ||
|
|
f2f0c96e62 | ||
|
|
64e7cc74b2 | ||
|
|
531fd87ff3 | ||
|
|
efa280b839 | ||
|
|
b3d84c4f99 | ||
|
|
015a1e3482 | ||
|
|
5efc8d6fa9 | ||
|
|
bb1654828f | ||
|
|
93cabde9f4 | ||
|
|
aeb5eb9129 | ||
|
|
e9e8b8a1ad | ||
|
|
08564e5ea2 | ||
|
|
1695619ef5 | ||
|
|
8f2f57c543 | ||
|
|
60454c6af3 | ||
|
|
40c7c72973 | ||
|
|
f9127272b1 | ||
|
|
ec295664d9 | ||
|
|
ec2b9b19be | ||
|
|
1566e069ed | ||
|
|
9c149f3529 | ||
|
|
2f35baef39 | ||
|
|
7c77c317df | ||
|
|
ed3a04d531 | ||
|
|
c3ec7741dc | ||
|
|
f4f89fac1f | ||
|
|
5262150d3c | ||
|
|
b5f56eb2b5 | ||
|
|
9a375fd0d1 | ||
|
|
6ca4aeb5a2 | ||
|
|
8ed4a9a5f2 | ||
|
|
a4578405cf | ||
|
|
73f147b7e2 | ||
|
|
5c3d735315 | ||
|
|
200621996d | ||
|
|
a912a0a07e | ||
|
|
4146ceb048 | ||
|
|
ad568d2b9f | ||
|
|
c7bfb9a11d | ||
|
|
c9391181bd | ||
|
|
b08eb519bf | ||
|
|
47c4fc2f7e | ||
|
|
dfd177433a | ||
|
|
27381e9696 | ||
|
|
1c7827c53c | ||
|
|
9512bdb5d6 | ||
|
|
33d1f1d692 |
No files matched your search
+14
-3
@@ -4,7 +4,6 @@
|
||||
hs_err_pid*
|
||||
|
||||
.gradle
|
||||
.idea
|
||||
|
||||
*.lck
|
||||
*.1
|
||||
@@ -19,7 +18,19 @@ NVIDIA
|
||||
/HMCL/build/
|
||||
/HMCLCore/build/
|
||||
|
||||
# idea output
|
||||
# idea
|
||||
.idea
|
||||
/out/
|
||||
/HMCL/out/
|
||||
/HMCLCore/out/
|
||||
/HMCLCore/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# netbeans
|
||||
.nb-gradle
|
||||
+3
-1
@@ -18,7 +18,9 @@ import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.XZOutputStream
|
||||
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") ?: "SNAPSHOT"
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.1"
|
||||
if (System.getenv("BUILD_NUMBER") != null && System.getenv("BUILD_NUMBER_OFFSET") != null)
|
||||
buildnumber = (Integer.parseInt(System.getenv("BUILD_NUMBER")) - Integer.parseInt(System.getenv("BUILD_NUMBER_OFFSET"))).toString()
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.2"
|
||||
version = versionroot + '.' + buildnumber
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -17,15 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -42,7 +37,9 @@ import java.nio.file.Paths;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
|
||||
@@ -50,6 +47,12 @@ public final class Launcher extends Application {
|
||||
public void start(Stage primaryStage) {
|
||||
Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
} catch (IOException e) {
|
||||
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
try {
|
||||
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
|
||||
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
|
||||
@@ -58,24 +61,7 @@ public final class Launcher extends Application {
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.setScene(Controllers.getScene());
|
||||
|
||||
UpdateChecker.updateChannelProperty().addListener(observable -> {
|
||||
thread(() -> {
|
||||
try {
|
||||
UpdateChecker.checkUpdate();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to check for update", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
UpdateChecker.updateChannelProperty().bind(Bindings.createStringBinding(() -> {
|
||||
switch (ConfigHolder.config().getUpdateChannel()) {
|
||||
case DEVELOPMENT:
|
||||
return UpdateChecker.CHANNEL_DEV;
|
||||
default:
|
||||
return UpdateChecker.CHANNEL_STABLE;
|
||||
}
|
||||
}, ConfigHolder.config().updateChannelProperty()));
|
||||
UpdateChecker.init();
|
||||
|
||||
primaryStage.show();
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
@@ -29,13 +33,8 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Main {
|
||||
|
||||
@@ -50,12 +49,6 @@ public final class Main {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
} catch (IOException e) {
|
||||
showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
@@ -101,7 +94,7 @@ public final class Main {
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
private static void showErrorAndExit(String message) {
|
||||
static void showErrorAndExit(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("A fatal error has occurred, forcibly exiting.");
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
@@ -111,7 +104,7 @@ public final class Main {
|
||||
/**
|
||||
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
|
||||
*/
|
||||
private static void showWarningAndContinue(String message) {
|
||||
static void showWarningAndContinue(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("Potential issues have been detected.");
|
||||
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
@@ -30,6 +30,7 @@ public final class Metadata {
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
|
||||
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://www.huangyuhui.net/hmcl.php";
|
||||
public static final String CONTACT_URL = "https://hmcl.huangyuhui.net/contact";
|
||||
public static final String HELP_URL = "https://hmcl.huangyuhui.net/help";
|
||||
public static final String PUBLISH_URL = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* This event gets fired when the selected profile changed.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ProfileChangedEvent extends Event {
|
||||
private final Profile profile;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
* @param profile the new profile.
|
||||
*/
|
||||
public ProfileChangedEvent(Object source, Profile profile) {
|
||||
super(source);
|
||||
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("profile", profile)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This event gets fired when loading profiles.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ProfileLoadingEvent extends Event {
|
||||
|
||||
private final Collection<Profile> profiles;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public ProfileLoadingEvent(Object source, Collection<Profile> profiles) {
|
||||
super(source);
|
||||
|
||||
this.profiles = Collections.unmodifiableCollection(profiles);
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("profiles", profiles)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +101,6 @@ public final class AccountHelper {
|
||||
private final boolean refresh;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
public SkinLoadTask(YggdrasilAccount account) {
|
||||
this(account, false);
|
||||
}
|
||||
|
||||
public SkinLoadTask(YggdrasilAccount account, boolean refresh) {
|
||||
this.account = account;
|
||||
this.refresh = refresh;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.jackhuang.hmcl.download.DefaultCacheRepository;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class HMCLCacheRepository extends DefaultCacheRepository {
|
||||
|
||||
private final StringProperty directory = new SimpleStringProperty();
|
||||
|
||||
public HMCLCacheRepository() {
|
||||
directory.addListener((a, b, t) -> changeDirectory(Paths.get(t)));
|
||||
}
|
||||
|
||||
public String getDirectory() {
|
||||
return directory.get();
|
||||
}
|
||||
|
||||
public StringProperty directoryProperty() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(String directory) {
|
||||
this.directory.set(directory);
|
||||
}
|
||||
|
||||
public static final HMCLCacheRepository REPOSITORY = new HMCLCacheRepository();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class HMCLDependencyManager extends DefaultDependencyManager {
|
||||
private final Profile profile;
|
||||
|
||||
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider) {
|
||||
super(profile.getRepository(), downloadProvider);
|
||||
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameBuilder gameBuilder() {
|
||||
return new HMCLGameBuilder(profile);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class HMCLGameDownloadTask extends Task {
|
||||
private final Profile profile;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
public HMCLGameDownloadTask(Profile profile, String gameVersion, Version version) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
File jar = profile.getRepository().getVersionJar(version);
|
||||
|
||||
// Force using common directory will not affect the behaviour that repository acts
|
||||
// Since we always copy the downloaded jar to .minecraft/versions/<version>/
|
||||
File cache = new File(Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(Settings.getDefaultCommonDirectory()),
|
||||
"jars/" + gameVersion + ".jar");
|
||||
if (cache.exists())
|
||||
try {
|
||||
FileUtils.copyFile(cache, jar);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to copy cached Minecraft jar from " + cache + " to " + jar, e);
|
||||
}
|
||||
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
cache,
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,9 +21,7 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.util.CommandBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,8 +34,6 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
||||
@@ -52,18 +50,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return profile;
|
||||
}
|
||||
|
||||
private boolean useSelf(String assetId) {
|
||||
return new File(getBaseDirectory(), "assets/indexes/" + assetId + ".json").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAssetDirectory(String version, String assetId) {
|
||||
if (Settings.instance().isCommonDirectoryDisabled() || useSelf(assetId))
|
||||
return super.getAssetDirectory(version, assetId);
|
||||
else
|
||||
return new File(Settings.instance().getCommonDirectory(), "assets");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRunDirectory(String id) {
|
||||
if (beingModpackVersions.contains(id) || isModpack(id))
|
||||
@@ -79,17 +65,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getLibraryFile(Version version, Library lib) {
|
||||
VersionSetting vs = profile.getVersionSetting(version.getId());
|
||||
File self = super.getLibraryFile(version, lib);
|
||||
if (Settings.instance().isCommonDirectoryDisabled() || self.exists())
|
||||
return self;
|
||||
else
|
||||
return new File(Settings.instance().getCommonDirectory(), "libraries/" + lib.getPath());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void refreshVersionsImpl() {
|
||||
versionSettings.clear();
|
||||
@@ -189,6 +164,27 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make version use self version settings instead of the global one.
|
||||
* @param id the version id.
|
||||
* @return specialized version setting, null if given version does not exist.
|
||||
*/
|
||||
public VersionSetting specializeVersionSetting(String id) {
|
||||
VersionSetting vs = getVersionSetting(id);
|
||||
if (vs == null)
|
||||
vs = createVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public void globalizeVersionSetting(String id) {
|
||||
VersionSetting vs = getVersionSetting(id);
|
||||
if (vs != null)
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public boolean forbidsVersion(String id) {
|
||||
return FORBIDDEN.contains(id);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task {
|
||||
private final File zipFile;
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -50,6 +49,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -214,6 +214,8 @@ public final class LauncherHelper {
|
||||
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
|
||||
boolean flag = false;
|
||||
boolean java8required = false;
|
||||
boolean newJavaRequired = false;
|
||||
|
||||
// Without onAccept called, the launching operation will be terminated.
|
||||
|
||||
@@ -227,36 +229,90 @@ public final class LauncherHelper {
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
|
||||
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);
|
||||
// Game later than 1.7.2 accepts Java 8.
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
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);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
|
||||
// LaunchWrapper 1.12 will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9
|
||||
&& version.getMainClass().contains("launchwrapper")
|
||||
&& version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
|
||||
if (java8.isPresent()) {
|
||||
java8required = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0.51");
|
||||
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
final JavaVersion java32 = java;
|
||||
|
||||
// First find if same java version but whose platform is 64-bit installed.
|
||||
Optional<JavaVersion> java64 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.Platform.PLATFORM)
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
|
||||
if (!java64.isPresent()) {
|
||||
final boolean java8requiredFinal = java8required, newJavaRequiredFinal = newJavaRequired;
|
||||
|
||||
// Then find if other java version which satisfies requirements installed.
|
||||
java64 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.Platform.PLATFORM)
|
||||
.filter(javaVersion -> {
|
||||
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;
|
||||
if (newJavaRequiredFinal) return javaVersion.getParsedVersion() >= JavaVersion.JAVA_8;
|
||||
return true;
|
||||
})
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
}
|
||||
|
||||
if (java64.isPresent()) {
|
||||
setting.setJavaVersion(java64.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
@@ -264,6 +320,7 @@ public final class LauncherHelper {
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
@@ -386,7 +443,7 @@ public final class LauncherHelper {
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.instance().getLogLines())
|
||||
if (logs.size() > config().getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
if (setting.isShowLogs()) {
|
||||
|
||||
@@ -87,9 +87,10 @@ public final class ModpackHelper {
|
||||
|
||||
FinalizedCallback finalizeTask = (variables, isDependentsSucceeded) -> {
|
||||
if (isDependentsSucceeded) {
|
||||
profile.getRepository().refreshVersions();
|
||||
VersionSetting vs = profile.specializeVersionSetting(name);
|
||||
profile.getRepository().undoMark(name);
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.refreshVersions();
|
||||
VersionSetting vs = repository.specializeVersionSetting(name);
|
||||
repository.undoMark(name);
|
||||
if (vs != null)
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public final class MultiMCInstallVersionSettingTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VersionSetting vs = Objects.requireNonNull(profile.specializeVersionSetting(version));
|
||||
VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));
|
||||
ModpackHelper.toVersionSetting(manifest, vs);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyListProperty;
|
||||
import javafx.beans.property.ReadOnlyListWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
@@ -30,13 +36,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyListProperty;
|
||||
import javafx.beans.property.ReadOnlyListWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -60,7 +59,7 @@ public final class Accounts {
|
||||
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
|
||||
public static final YggdrasilAccountFactory FACTORY_YGGDRASIL = new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE);
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.instance().getDownloadProvider())::getArtifactInfo,
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), DownloadProviders::getDownloadProvider)::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer);
|
||||
|
||||
private static final String TYPE_OFFLINE = "offline";
|
||||
@@ -92,7 +91,7 @@ public final class Accounts {
|
||||
}
|
||||
|
||||
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static ReadOnlyListProperty<Account> accountsWrapper = new ReadOnlyListWrapper<>(accounts);
|
||||
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(accounts);
|
||||
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
@@ -194,7 +193,7 @@ public final class Accounts {
|
||||
}
|
||||
|
||||
public static ReadOnlyListProperty<Account> accountsProperty() {
|
||||
return accountsWrapper;
|
||||
return accountsWrapper.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static Account getSelectedAccount() {
|
||||
|
||||
@@ -17,46 +17,36 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.util.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class Config implements Cloneable, Observable {
|
||||
|
||||
@@ -125,8 +115,8 @@ public final class Config implements Cloneable, Observable {
|
||||
@SerializedName("localization")
|
||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
private IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
@SerializedName("downloadType")
|
||||
private StringProperty downloadType = new SimpleStringProperty("bmclapi");
|
||||
|
||||
@SerializedName("configurations")
|
||||
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
@@ -150,7 +140,10 @@ public final class Config implements Cloneable, Observable {
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("updateChannel")
|
||||
private ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<>(EnumUpdateChannel.STABLE);
|
||||
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
|
||||
|
||||
@SerializedName("enableMainPageGameList")
|
||||
private BooleanProperty enableMainPageGameList = new SimpleBooleanProperty(false);
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
@@ -366,15 +359,15 @@ public final class Config implements Cloneable, Observable {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
public String getDownloadType() {
|
||||
return downloadType.get();
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
public void setDownloadType(String downloadType) {
|
||||
this.downloadType.set(downloadType);
|
||||
}
|
||||
|
||||
public IntegerProperty downloadTypeProperty() {
|
||||
public StringProperty downloadTypeProperty() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
@@ -438,15 +431,27 @@ public final class Config implements Cloneable, Observable {
|
||||
return authlibInjectorServers;
|
||||
}
|
||||
|
||||
public EnumUpdateChannel getUpdateChannel() {
|
||||
public UpdateChannel getUpdateChannel() {
|
||||
return updateChannel.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumUpdateChannel> updateChannelProperty() {
|
||||
public ObjectProperty<UpdateChannel> updateChannelProperty() {
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
public void setUpdateChannel(EnumUpdateChannel updateChannel) {
|
||||
public void setUpdateChannel(UpdateChannel updateChannel) {
|
||||
this.updateChannel.set(updateChannel);
|
||||
}
|
||||
|
||||
public boolean isEnableMainPageGameList() {
|
||||
return enableMainPageGameList.get();
|
||||
}
|
||||
|
||||
public BooleanProperty enableMainPageGameListProperty() {
|
||||
return enableMainPageGameList;
|
||||
}
|
||||
|
||||
public void setEnableMainPageGameList(boolean enableMainPageGameList) {
|
||||
this.enableMainPageGameList.set(enableMainPageGameList);
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -27,10 +28,8 @@ import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class ConfigHolder {
|
||||
|
||||
|
||||
@@ -86,5 +86,17 @@ final class ConfigUpgrader {
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
|
||||
if (!rawJson.containsKey("downloadType")) {
|
||||
tryCast(rawJson.get("downloadtype"), Number.class)
|
||||
.map(Number::intValue)
|
||||
.ifPresent(id -> {
|
||||
if (id == 0) {
|
||||
deserialized.setDownloadType("mojang");
|
||||
} else if (id == 1) {
|
||||
deserialized.setDownloadType("bmclapi");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,44 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.CurseCDNDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
|
||||
public final class DownloadProviders {
|
||||
private DownloadProviders() {}
|
||||
|
||||
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
|
||||
public static final Map<String, DownloadProvider> providersById = mapOf(
|
||||
pair("mojang", new MojangDownloadProvider()),
|
||||
pair("bmclapi", new BMCLAPIDownloadProvider()));
|
||||
|
||||
public static DownloadProvider getDownloadProvider(int index) {
|
||||
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
|
||||
public static final String DEFAULT_PROVIDER_ID = "bmclapi";
|
||||
|
||||
private static ObjectBinding<DownloadProvider> downloadProviderProperty;
|
||||
|
||||
static void init() {
|
||||
downloadProviderProperty = Bindings.createObjectBinding(
|
||||
() -> Optional.ofNullable(providersById.get(config().getDownloadType()))
|
||||
.orElse(providersById.get(DEFAULT_PROVIDER_ID)),
|
||||
config().downloadTypeProperty());
|
||||
}
|
||||
|
||||
public static DownloadProvider getDownloadProvider() {
|
||||
return downloadProviderProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
|
||||
return downloadProviderProperty;
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,6 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
public enum EnumCommonDirectory {
|
||||
/**
|
||||
* same to game directory
|
||||
*/
|
||||
DISABLED,
|
||||
/**
|
||||
* %appdata%/.minecraft or ~/.minecraft
|
||||
*/
|
||||
|
||||
@@ -18,86 +18,99 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import org.jackhuang.hmcl.game.HMCLDependencyManager;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.util.ImmediateObjectProperty;
|
||||
import org.jackhuang.hmcl.util.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Profile {
|
||||
|
||||
public final class Profile implements Observable {
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
private final HMCLGameRepository repository;
|
||||
private final ModManager modManager;
|
||||
|
||||
private final ImmediateObjectProperty<File> gameDirProperty;
|
||||
private final StringProperty selectedVersion = new SimpleStringProperty();
|
||||
|
||||
public ImmediateObjectProperty<File> gameDirProperty() {
|
||||
return gameDirProperty;
|
||||
public StringProperty selectedVersionProperty() {
|
||||
return selectedVersion;
|
||||
}
|
||||
|
||||
public String getSelectedVersion() {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
public void setSelectedVersion(String selectedVersion) {
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
}
|
||||
|
||||
private final ObjectProperty<File> gameDir;
|
||||
|
||||
public ObjectProperty<File> gameDirProperty() {
|
||||
return gameDir;
|
||||
}
|
||||
|
||||
public File getGameDir() {
|
||||
return gameDirProperty.get();
|
||||
return gameDir.get();
|
||||
}
|
||||
|
||||
public void setGameDir(File gameDir) {
|
||||
gameDirProperty.set(gameDir);
|
||||
this.gameDir.set(gameDir);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<VersionSetting> globalProperty = new ImmediateObjectProperty<>(this, "global", new VersionSetting());
|
||||
private final ReadOnlyObjectWrapper<VersionSetting> global = new ReadOnlyObjectWrapper<>(this, "global");
|
||||
|
||||
public ImmediateObjectProperty<VersionSetting> globalProperty() {
|
||||
return globalProperty;
|
||||
public ReadOnlyObjectProperty<VersionSetting> globalProperty() {
|
||||
return global.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public VersionSetting getGlobal() {
|
||||
return globalProperty.get();
|
||||
return global.get();
|
||||
}
|
||||
|
||||
private void setGlobal(VersionSetting global) {
|
||||
if (global == null)
|
||||
global = new VersionSetting();
|
||||
globalProperty.set(global);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty nameProperty;
|
||||
private final ImmediateStringProperty name;
|
||||
|
||||
public ImmediateStringProperty nameProperty() {
|
||||
return nameProperty;
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return nameProperty.get();
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
nameProperty.set(name);
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
private BooleanProperty useRelativePathProperty = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
private BooleanProperty useRelativePath = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
|
||||
public BooleanProperty useRelativePathProperty() {
|
||||
return useRelativePathProperty();
|
||||
return useRelativePath;
|
||||
}
|
||||
|
||||
public boolean isUseRelativePath() {
|
||||
return useRelativePathProperty.get();
|
||||
return useRelativePath.get();
|
||||
}
|
||||
|
||||
public void setUseRelativePath(boolean useRelativePath) {
|
||||
useRelativePathProperty.set(useRelativePath);
|
||||
this.useRelativePath.set(useRelativePath);
|
||||
}
|
||||
|
||||
public Profile(String name) {
|
||||
@@ -105,12 +118,39 @@ public final class Profile {
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir) {
|
||||
nameProperty = new ImmediateStringProperty(this, "name", name);
|
||||
gameDirProperty = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
this(name, initialGameDir, new VersionSetting());
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global) {
|
||||
this(name, initialGameDir, global, null, false);
|
||||
}
|
||||
|
||||
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);
|
||||
repository = new HMCLGameRepository(this, initialGameDir);
|
||||
modManager = new ModManager(repository);
|
||||
this.global.set(global == null ? new VersionSetting() : global);
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
this.useRelativePath.set(useRelativePath);
|
||||
|
||||
gameDirProperty.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
|
||||
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
|
||||
this.selectedVersion.addListener(o -> checkSelectedVersion());
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion()));
|
||||
|
||||
addPropertyChangedListener(onInvalidating(this::invalidate));
|
||||
}
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
|
||||
if (version.isPresent())
|
||||
selectedVersion.setValue(version.get());
|
||||
else if (newValue != null)
|
||||
selectedVersion.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
@@ -121,8 +161,8 @@ public final class Profile {
|
||||
return modManager;
|
||||
}
|
||||
|
||||
public HMCLDependencyManager getDependency() {
|
||||
return new HMCLDependencyManager(this, Settings.instance().getDownloadProvider());
|
||||
public DefaultDependencyManager getDependency() {
|
||||
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
@@ -135,32 +175,6 @@ public final class Profile {
|
||||
return vs;
|
||||
}
|
||||
|
||||
public boolean isVersionGlobal(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
return vs == null || vs.isUsesGlobal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make version use self version settings instead of the global one.
|
||||
* @param id the version id.
|
||||
* @return specialized version setting, null if given version does not exist.
|
||||
*/
|
||||
public VersionSetting specializeVersionSetting(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
if (vs == null)
|
||||
vs = repository.createVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public void globalizeVersionSetting(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
if (vs != null)
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
@@ -170,12 +184,29 @@ public final class Profile {
|
||||
.toString();
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
nameProperty.addListener(listener);
|
||||
globalProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
useRelativePathProperty.addListener(listener);
|
||||
globalProperty.get().addPropertyChangedListener(listener);
|
||||
private void addPropertyChangedListener(InvalidationListener listener) {
|
||||
name.addListener(listener);
|
||||
global.addListener(listener);
|
||||
gameDir.addListener(listener);
|
||||
useRelativePath.addListener(listener);
|
||||
global.get().addPropertyChangedListener(listener);
|
||||
selectedVersion.addListener(listener);
|
||||
}
|
||||
|
||||
private ObservableHelper observableHelper = new ObservableHelper(this);
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
observableHelper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
observableHelper.removeListener(listener);
|
||||
}
|
||||
|
||||
private void invalidate() {
|
||||
Platform.runLater(observableHelper::invalidate);
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
@@ -193,6 +224,7 @@ public final class Profile {
|
||||
jsonObject.add("global", context.serialize(src.getGlobal()));
|
||||
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
||||
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
|
||||
jsonObject.addProperty("selectedMinecraftVersion", src.getSelectedVersion());
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
@@ -203,11 +235,11 @@ public final class Profile {
|
||||
JsonObject obj = (JsonObject) json;
|
||||
String gameDir = Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse("");
|
||||
|
||||
Profile profile = new Profile("Default", new File(gameDir));
|
||||
profile.setGlobal(context.deserialize(obj.get("global"), VersionSetting.class));
|
||||
|
||||
profile.setUseRelativePath(Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
return profile;
|
||||
return new Profile("Default",
|
||||
new File(gameDir),
|
||||
context.deserialize(obj.get("global"), VersionSetting.class),
|
||||
Optional.ofNullable(obj.get("selectedMinecraftVersion")).map(JsonElement::getAsString).orElse(""),
|
||||
Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,25 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Profiles {
|
||||
@@ -24,6 +43,8 @@ public final class Profiles {
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
private static InvalidationListener listener = o -> loadVersion();
|
||||
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
@@ -37,4 +58,153 @@ public final class Profiles {
|
||||
return profile.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private static final ObservableList<Profile> profiles = observableArrayList(profile -> new Observable[] { profile });
|
||||
private static final ReadOnlyListWrapper<Profile> profilesWrapper = new ReadOnlyListWrapper<>(profiles);
|
||||
|
||||
private static ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
{
|
||||
profiles.addListener(onInvalidating(this::invalidated));
|
||||
|
||||
this.addListener(this::change);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Profile profile = get();
|
||||
|
||||
if (get() != null)
|
||||
get().removeListener(listener);
|
||||
|
||||
if (profiles.isEmpty()) {
|
||||
if (profile != null) {
|
||||
set(null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!profiles.contains(profile)) {
|
||||
set(profiles.get(0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
config().setSelectedProfile(profile == null ? "" : profile.getName());
|
||||
loadVersion();
|
||||
}
|
||||
|
||||
private void change(ObservableValue<? extends Profile> observableValue, Profile oldProfile, Profile newProfile) {
|
||||
if (oldProfile != null)
|
||||
oldProfile.selectedVersionProperty().removeListener(listener);
|
||||
if (newProfile != null)
|
||||
newProfile.selectedVersionProperty().addListener(listener);
|
||||
}
|
||||
};
|
||||
|
||||
private static void checkProfiles() {
|
||||
if (profiles.isEmpty()) {
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE, new File(".minecraft"), new VersionSetting(), null, true);
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY);
|
||||
Platform.runLater(() -> profiles.addAll(current, home));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if {@link #init()} hasn't been called.
|
||||
*/
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
profiles.addListener(onInvalidating(Profiles::updateProfileStorages));
|
||||
profiles.addListener(onInvalidating(Profiles::checkProfiles));
|
||||
|
||||
selectedProfile.addListener((a, b, newValue) -> {
|
||||
if (newValue != null)
|
||||
newValue.getRepository().refreshVersionsAsync().start();
|
||||
});
|
||||
}
|
||||
|
||||
private static void updateProfileStorages() {
|
||||
// don't update the underlying storage before data loading is completed
|
||||
// otherwise it might cause data loss
|
||||
if (!initialized)
|
||||
return;
|
||||
// update storage
|
||||
config().getConfigurations().clear();
|
||||
config().getConfigurations().putAll(profiles.stream().collect(Collectors.toMap(Profile::getName, it -> it)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it's ready to load profiles from {@link ConfigHolder#config()}.
|
||||
*/
|
||||
static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
HashSet<String> names = new HashSet<>();
|
||||
config().getConfigurations().forEach((name, profile) -> {
|
||||
if (!names.add(name)) return;
|
||||
profiles.add(profile);
|
||||
profile.setName(name);
|
||||
});
|
||||
checkProfiles();
|
||||
|
||||
// Platform.runLater is necessary or profiles will be empty
|
||||
// since checkProfiles adds 2 base profile later.
|
||||
Platform.runLater(() -> {
|
||||
selectedProfile.set(
|
||||
profiles.stream()
|
||||
.filter(it -> it.getName().equals(config().getSelectedProfile()))
|
||||
.findFirst()
|
||||
.orElse(profiles.get(0)));
|
||||
|
||||
initialized = true;
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (selectedProfile.get() != null && selectedProfile.get().getRepository() == event.getSource())
|
||||
loadVersion();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static ObservableList<Profile> getProfiles() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public static ReadOnlyListProperty<Profile> profilesProperty() {
|
||||
return profilesWrapper.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static Profile getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
}
|
||||
|
||||
public static void setSelectedProfile(Profile profile) {
|
||||
selectedProfile.set(profile);
|
||||
}
|
||||
|
||||
public static ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper();
|
||||
|
||||
public static ReadOnlyStringProperty selectedVersionProperty() {
|
||||
return selectedVersion.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static String getSelectedVersion() {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
private static void loadVersion() {
|
||||
Profile profile = selectedProfile.get();
|
||||
if (profile == null || !profile.getRepository().isLoaded()) return;
|
||||
JFXUtilities.runInFX(() ->
|
||||
selectedVersion.set(profile.getSelectedVersion()));
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -25,31 +29,13 @@ import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public final class ProxyManager {
|
||||
private ProxyManager() {
|
||||
}
|
||||
|
||||
private static ObjectBinding<Proxy> proxyProperty = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
String host = config().getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(config().getProxyPort());
|
||||
if (!config().hasProxy() || StringUtils.isBlank(host) || port == null || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
} else {
|
||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||
}
|
||||
},
|
||||
config().proxyTypeProperty(),
|
||||
config().proxyHostProperty(),
|
||||
config().proxyPortProperty(),
|
||||
config().hasProxyProperty());
|
||||
private static ObjectBinding<Proxy> proxyProperty;
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxyProperty.get();
|
||||
@@ -60,12 +46,25 @@ public final class ProxyManager {
|
||||
}
|
||||
|
||||
static void init() {
|
||||
proxyProperty.addListener(observable -> updateSystemProxy());
|
||||
proxyProperty = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
String host = config().getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(config().getProxyPort());
|
||||
if (!config().hasProxy() || StringUtils.isBlank(host) || port == null || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
} else {
|
||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||
}
|
||||
},
|
||||
config().proxyTypeProperty(),
|
||||
config().proxyHostProperty(),
|
||||
config().proxyPortProperty(),
|
||||
config().hasProxyProperty());
|
||||
|
||||
proxyProperty.addListener(any -> updateSystemProxy());
|
||||
updateSystemProxy();
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -17,17 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
@@ -50,16 +43,13 @@ public class Settings {
|
||||
}
|
||||
|
||||
private Settings() {
|
||||
DownloadProviders.init();
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
Profiles.init();
|
||||
|
||||
checkProfileMap();
|
||||
|
||||
for (Map.Entry<String, Profile> profileEntry : getProfileMap().entrySet()) {
|
||||
profileEntry.getValue().setName(profileEntry.getKey());
|
||||
profileEntry.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
profileEntry.getValue().addPropertyChangedListener(e -> ConfigHolder.markConfigDirty());
|
||||
}
|
||||
CacheRepository.setInstance(HMCLCacheRepository.REPOSITORY);
|
||||
HMCLCacheRepository.REPOSITORY.directoryProperty().bind(config().commonDirectoryProperty());
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
@@ -71,26 +61,12 @@ public class Settings {
|
||||
config().setFontSize(font.getSize());
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(config().getLogLines(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
config().setLogLines(logLines);
|
||||
}
|
||||
|
||||
public boolean isCommonDirectoryDisabled() {
|
||||
return config().getCommonDirType() == EnumCommonDirectory.DISABLED;
|
||||
}
|
||||
|
||||
public static String getDefaultCommonDirectory() {
|
||||
return Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
switch (config().getCommonDirType()) {
|
||||
case DISABLED:
|
||||
return null;
|
||||
case DEFAULT:
|
||||
return getDefaultCommonDirectory();
|
||||
case CUSTOM:
|
||||
@@ -99,110 +75,4 @@ public class Settings {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* DOWNLOAD PROVIDERS *
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return DownloadProviders.getDownloadProvider(config().getDownloadType());
|
||||
}
|
||||
|
||||
public void setDownloadProvider(DownloadProvider downloadProvider) {
|
||||
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
|
||||
if (index == -1)
|
||||
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
|
||||
config().setDownloadType(index);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* PROFILES *
|
||||
****************************************/
|
||||
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(config().getSelectedProfile())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
|
||||
config().setSelectedProfile(selectedProfile);
|
||||
});
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
return getProfile(config().getSelectedProfile());
|
||||
}
|
||||
|
||||
public void setSelectedProfile(Profile selectedProfile) {
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), config().getSelectedProfile())) {
|
||||
config().setSelectedProfile(selectedProfile.getName());
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public Profile getProfile(String name) {
|
||||
checkProfileMap();
|
||||
|
||||
Optional<Profile> p = name == null ? getProfileMap().values().stream().findFirst() : Optional.ofNullable(getProfileMap().get(name));
|
||||
return p.orElse(null);
|
||||
}
|
||||
|
||||
public boolean hasProfile(String name) {
|
||||
return getProfileMap().containsKey(name);
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return config().getConfigurations();
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
return getProfileMap().values().stream().filter(t -> StringUtils.isNotBlank(t.getName())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void putProfile(Profile ver) {
|
||||
if (StringUtils.isBlank(ver.getName()))
|
||||
throw new IllegalArgumentException("Profile's name is empty");
|
||||
|
||||
getProfileMap().put(ver.getName(), ver);
|
||||
Schedulers.computation().schedule(this::onProfileLoading);
|
||||
|
||||
ver.nameProperty().setChangedListener(this::profileNameChanged);
|
||||
}
|
||||
|
||||
public void deleteProfile(Profile profile) {
|
||||
deleteProfile(profile.getName());
|
||||
}
|
||||
|
||||
public void deleteProfile(String profileName) {
|
||||
getProfileMap().remove(profileName);
|
||||
checkProfileMap();
|
||||
Schedulers.computation().schedule(this::onProfileLoading);
|
||||
}
|
||||
|
||||
private void checkProfileMap() {
|
||||
if (getProfileMap().isEmpty()) {
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE);
|
||||
current.setUseRelativePath(true);
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, current);
|
||||
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY);
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, home);
|
||||
}
|
||||
}
|
||||
|
||||
private void onProfileChanged() {
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(this, getSelectedProfile()));
|
||||
getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private void profileNameChanged(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
|
||||
getProfileMap().put(newValue, getProfileMap().remove(oldValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiles loading process.
|
||||
* Invoked by loading GUI phase.
|
||||
*/
|
||||
public void onProfileLoading() {
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(this, getProfiles()));
|
||||
onProfileChanged();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -24,16 +26,13 @@ import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class Theme {
|
||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import javafx.beans.InvalidationListener;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.installTooltip;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title;
|
||||
private final ObjectProperty<Runnable> onDelete = new SimpleObjectProperty<>(this, "onDelete");
|
||||
private final AdvancedListItem item;
|
||||
private final Account account;
|
||||
|
||||
@FXML
|
||||
private Label lblType;
|
||||
@FXML
|
||||
private Label lblServer;
|
||||
@FXML
|
||||
private Label lblCharacter;
|
||||
@FXML
|
||||
private Label lblEmail;
|
||||
@FXML
|
||||
private BorderPane paneServer;
|
||||
@FXML
|
||||
private BorderPane paneEmail;
|
||||
@FXML
|
||||
private ComponentList componentList;
|
||||
@FXML
|
||||
private JFXButton btnDelete;
|
||||
@FXML
|
||||
private JFXButton btnRefresh;
|
||||
@FXML
|
||||
private JFXProgressBar progressBar;
|
||||
|
||||
public AccountPage(Account account, AdvancedListItem item) {
|
||||
this.account = account;
|
||||
this.item = item;
|
||||
|
||||
title = new SimpleStringProperty(this, "title", i18n("account") + " - " + account.getCharacter());
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
|
||||
lblServer.setText(server.getName());
|
||||
installTooltip(lblServer, server.getUrl());
|
||||
} else {
|
||||
componentList.removeChildren(paneServer);
|
||||
|
||||
if (account instanceof OfflineAccount) {
|
||||
componentList.removeChildren(paneEmail);
|
||||
}
|
||||
}
|
||||
|
||||
btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15));
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
lblCharacter.setText(account.getCharacter());
|
||||
lblType.setText(Accounts.getAccountTypeName(account));
|
||||
lblEmail.setText(account.getUsername());
|
||||
|
||||
btnRefresh.setVisible(account instanceof YggdrasilAccount);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
Accounts.getAccounts().remove(account);
|
||||
Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onRefresh() {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
progressBar.setVisible(true);
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
progressBar.setVisible(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public Runnable getOnDelete() {
|
||||
return onDelete.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onDeleteProperty() {
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
public void setOnDelete(Runnable onDelete) {
|
||||
this.onDelete.set(onDelete);
|
||||
}
|
||||
}
|
||||
@@ -24,20 +24,27 @@ import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
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.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public final class Controllers {
|
||||
|
||||
private static Scene scene;
|
||||
@@ -45,9 +52,12 @@ public final class Controllers {
|
||||
private static MainPage mainPage = null;
|
||||
private static SettingsPage settingsPage = null;
|
||||
private static VersionPage versionPage = null;
|
||||
private static GameList gameListPage = null;
|
||||
private static AccountList accountListPage = null;
|
||||
private static ProfileList profileListPage = null;
|
||||
private static AuthlibInjectorServersPage serversPage = null;
|
||||
private static LeftPaneController leftPaneController;
|
||||
private static Decorator decorator;
|
||||
private static DecoratorController decorator;
|
||||
|
||||
public static Scene getScene() {
|
||||
return scene;
|
||||
@@ -64,6 +74,33 @@ public final class Controllers {
|
||||
return settingsPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static GameList getGameListPage() {
|
||||
if (gameListPage == null)
|
||||
gameListPage = new GameList();
|
||||
return gameListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static AccountList getAccountListPage() {
|
||||
if (accountListPage == null) {
|
||||
accountListPage = new AccountList();
|
||||
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
|
||||
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
||||
}
|
||||
return accountListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static ProfileList getProfileListPage() {
|
||||
if (profileListPage == null) {
|
||||
profileListPage = new ProfileList();
|
||||
profileListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
profileListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
}
|
||||
return profileListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static VersionPage getVersionPage() {
|
||||
if (versionPage == null)
|
||||
@@ -79,7 +116,7 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static Decorator getDecorator() {
|
||||
public static DecoratorController getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
@@ -98,21 +135,14 @@ public final class Controllers {
|
||||
|
||||
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
||||
|
||||
decorator = new Decorator(stage, getMainPage(), Metadata.TITLE, false, true);
|
||||
decorator.showPage(null);
|
||||
leftPaneController = new LeftPaneController(decorator.getLeftPane());
|
||||
decorator = new DecoratorController(stage, getMainPage());
|
||||
leftPaneController = new LeftPaneController();
|
||||
decorator.getDecorator().drawerProperty().setAll(leftPaneController);
|
||||
|
||||
Settings.instance().onProfileLoading();
|
||||
Task.of(JavaVersion::initialize).start();
|
||||
|
||||
decorator.setCustomMaximize(false);
|
||||
|
||||
scene = new Scene(decorator, 804, 521);
|
||||
scene = new Scene(decorator.getDecorator(), 800, 519);
|
||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||
stage.setMinWidth(804);
|
||||
stage.setMaxWidth(804);
|
||||
stage.setMinHeight(521);
|
||||
stage.setMaxHeight(521);
|
||||
|
||||
stage.getIcons().add(new Image("/assets/img/icon.png"));
|
||||
stage.setTitle(Metadata.TITLE);
|
||||
@@ -163,10 +193,7 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
if (decorator.getNowPage() == node)
|
||||
decorator.showPage(null);
|
||||
else
|
||||
decorator.showPage(node);
|
||||
decorator.getNavigator().navigate(node);
|
||||
}
|
||||
|
||||
public static boolean isStopped() {
|
||||
@@ -181,5 +208,8 @@ public final class Controllers {
|
||||
decorator = null;
|
||||
stage = null;
|
||||
scene = null;
|
||||
gameListPage = null;
|
||||
accountListPage = null;
|
||||
profileListPage = null;
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,11 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
|
||||
@@ -1,732 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXDrawer;
|
||||
import com.jfoenix.controls.JFXHamburger;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.BoundingBox;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
private static final SVGGlyph resizeMax = Lang.apply(new SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
private static final SVGGlyph resizeMin = Lang.apply(new SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Decorator.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction;
|
||||
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
|
||||
|
||||
private final Stage primaryStage;
|
||||
private final Node mainPage;
|
||||
private final boolean max, min;
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging, maximized;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
private final TransitionHandler animationHandler;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
|
||||
@FXML
|
||||
private StackPane contentPlaceHolder;
|
||||
@FXML
|
||||
private StackPane drawerWrapper;
|
||||
@FXML
|
||||
private BorderPane titleContainer;
|
||||
@FXML
|
||||
private BorderPane leftRootPane;
|
||||
@FXML
|
||||
private HBox buttonsContainer;
|
||||
@FXML
|
||||
private JFXButton backNavButton;
|
||||
@FXML
|
||||
private JFXButton refreshNavButton;
|
||||
@FXML
|
||||
private JFXButton closeNavButton;
|
||||
@FXML
|
||||
private JFXButton refreshMenuButton;
|
||||
@FXML
|
||||
private Label titleLabel;
|
||||
@FXML
|
||||
private Label lblTitle;
|
||||
@FXML
|
||||
private AdvancedListBox leftPane;
|
||||
@FXML
|
||||
private JFXDrawer drawer;
|
||||
@FXML
|
||||
private StackPane titleBurgerContainer;
|
||||
@FXML
|
||||
private JFXHamburger titleBurger;
|
||||
@FXML
|
||||
private JFXButton btnMin;
|
||||
@FXML
|
||||
private JFXButton btnMax;
|
||||
@FXML
|
||||
private JFXButton btnClose;
|
||||
@FXML
|
||||
private HBox navLeft;
|
||||
@FXML
|
||||
private ImageView welcomeView;
|
||||
@FXML
|
||||
private Rectangle separator;
|
||||
|
||||
public Decorator(Stage primaryStage, Node mainPage, String title) {
|
||||
this(primaryStage, mainPage, title, true, true);
|
||||
}
|
||||
|
||||
public Decorator(Stage primaryStage, Node mainPage, String title, boolean max, boolean min) {
|
||||
this.primaryStage = primaryStage;
|
||||
this.mainPage = mainPage;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/decorator.fxml");
|
||||
|
||||
onCloseButtonAction = new SimpleObjectProperty<>(this, "onCloseButtonAction", Launcher::stopApplication);
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
btnClose.setGraphic(close);
|
||||
btnMin.setGraphic(minus);
|
||||
btnMax.setGraphic(resizeMax);
|
||||
|
||||
close.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
resizeMax.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
resizeMin.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
|
||||
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), 15, 15));
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), 15, 15));
|
||||
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), 15, 15));
|
||||
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
|
||||
lblTitle.setText(title);
|
||||
|
||||
buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (event.getClickCount() == 2)
|
||||
btnMax.fire();
|
||||
});
|
||||
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(welcomeView.opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), new KeyValue(welcomeView.opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), e2 -> drawerWrapper.getChildren().remove(welcomeView))
|
||||
);
|
||||
nowAnimation.play();
|
||||
});
|
||||
if (!ConfigHolder.isNewlyCreated() || config().getLocalization().getLocale() != Locale.CHINA)
|
||||
drawerWrapper.getChildren().remove(welcomeView);
|
||||
|
||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
||||
if (!max) buttonsContainer.getChildren().remove(btnMax);
|
||||
|
||||
//JFXDepthManager.setDepth(titleContainer, 1);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
||||
if (!isDragging) allowMove = false;
|
||||
});
|
||||
Rectangle rectangle = new Rectangle(0, 0, 0, 0);
|
||||
rectangle.widthProperty().bind(titleContainer.widthProperty());
|
||||
rectangle.heightProperty().bind(Bindings.createDoubleBinding(() -> titleContainer.getHeight() + 100, titleContainer.heightProperty()));
|
||||
titleContainer.setClip(rectangle);
|
||||
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
setupBackground();
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
// ==== Background ====
|
||||
private void setupBackground() {
|
||||
drawerWrapper.backgroundProperty().bind(
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
Image image = null;
|
||||
if (config().getBackgroundImageType() == EnumBackgroundImage.CUSTOM && config().getBackgroundImage() != null) {
|
||||
image = tryLoadImage(Paths.get(config().getBackgroundImage()))
|
||||
.orElse(null);
|
||||
}
|
||||
if (image == null) {
|
||||
image = loadDefaultBackgroundImage();
|
||||
}
|
||||
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
|
||||
},
|
||||
config().backgroundImageTypeProperty(),
|
||||
config().backgroundImageProperty()));
|
||||
}
|
||||
|
||||
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
||||
|
||||
/**
|
||||
* Load background image from bg/, background.png, background.jpg
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.png"));
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.jpg"));
|
||||
}
|
||||
return image.orElse(defaultBackground);
|
||||
}
|
||||
|
||||
private Optional<Image> randomImageIn(Path imageDir) {
|
||||
if (!Files.isDirectory(imageDir)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
try {
|
||||
candidates = Files.list(imageDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(it -> {
|
||||
String filename = it.getFileName().toString();
|
||||
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
while (candidates.size() > 0) {
|
||||
int selected = rnd.nextInt(candidates.size());
|
||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded.isPresent()) {
|
||||
return Optional.of(loaded.get());
|
||||
} else {
|
||||
candidates.remove(selected);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private boolean isMaximized() {
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case OSX:
|
||||
Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
|
||||
return primaryStage.getWidth() >= bounds.getWidth() && primaryStage.getHeight() >= bounds.getHeight();
|
||||
default:
|
||||
return primaryStage.isMaximized();
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
@FXML
|
||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!isMaximized() && !primaryStage.isFullScreen() && !maximized) {
|
||||
if (!primaryStage.isResizable())
|
||||
updateInitMouseValues(mouseEvent);
|
||||
else {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = getBoundsInParent();
|
||||
if (getBorder() != null && getBorder().getStrokes().size() > 0) {
|
||||
double borderWidth = this.contentPlaceHolder.snappedLeftInset();
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > this.getHeight() - borderWidth) {
|
||||
setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > this.getHeight() - borderWidth) {
|
||||
setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
setCursor(Cursor.N_RESIZE);
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
setCursor(Cursor.S_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
this.updateInitMouseValues(mouseEvent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMouseReleased() {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMouseDragged(MouseEvent mouseEvent) {
|
||||
this.isDragging = true;
|
||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress() && !isMaximized() && !this.maximized) {
|
||||
this.newX = mouseEvent.getScreenX();
|
||||
this.newY = mouseEvent.getScreenY();
|
||||
double deltaX = this.newX - this.initX;
|
||||
double deltaY = this.newY - this.initY;
|
||||
Cursor cursor = this.getCursor();
|
||||
if (Cursor.E_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NE_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SE_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.S_RESIZE == cursor) {
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.W_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.N_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (this.allowMove) {
|
||||
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
||||
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMin() {
|
||||
primaryStage.setIconified(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMax() {
|
||||
if (!max) return;
|
||||
if (!this.isCustomMaximize()) {
|
||||
this.primaryStage.setMaximized(!this.primaryStage.isMaximized());
|
||||
this.maximized = this.primaryStage.isMaximized();
|
||||
if (this.primaryStage.isMaximized()) {
|
||||
this.btnMax.setGraphic(resizeMin);
|
||||
this.btnMax.setTooltip(new Tooltip("Restore Down"));
|
||||
} else {
|
||||
this.btnMax.setGraphic(resizeMax);
|
||||
this.btnMax.setTooltip(new Tooltip("Maximize"));
|
||||
}
|
||||
} else {
|
||||
if (!this.maximized) {
|
||||
this.originalBox = new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
|
||||
Screen screen = Screen.getScreensForRectangle(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()).get(0);
|
||||
Rectangle2D bounds = screen.getVisualBounds();
|
||||
this.maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
|
||||
primaryStage.setX(this.maximizedBox.getMinX());
|
||||
primaryStage.setY(this.maximizedBox.getMinY());
|
||||
primaryStage.setWidth(this.maximizedBox.getWidth());
|
||||
primaryStage.setHeight(this.maximizedBox.getHeight());
|
||||
this.btnMax.setGraphic(resizeMin);
|
||||
this.btnMax.setTooltip(new Tooltip("Restore Down"));
|
||||
} else {
|
||||
primaryStage.setX(this.originalBox.getMinX());
|
||||
primaryStage.setY(this.originalBox.getMinY());
|
||||
primaryStage.setWidth(this.originalBox.getWidth());
|
||||
primaryStage.setHeight(this.originalBox.getHeight());
|
||||
this.originalBox = null;
|
||||
this.btnMax.setGraphic(resizeMax);
|
||||
this.btnMax.setTooltip(new Tooltip("Maximize"));
|
||||
}
|
||||
|
||||
this.maximized = !this.maximized;
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onClose() {
|
||||
onCloseButtonAction.get().run();
|
||||
}
|
||||
|
||||
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
||||
initX = mouseEvent.getScreenX();
|
||||
initY = mouseEvent.getScreenY();
|
||||
xOffset = mouseEvent.getSceneX();
|
||||
yOffset = mouseEvent.getSceneY();
|
||||
}
|
||||
|
||||
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x < getWidth() && x > getWidth() - contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
|
||||
return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
|
||||
return y < getHeight() && y > getHeight() - contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean setStageWidth(double width) {
|
||||
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
||||
primaryStage.setWidth(width);
|
||||
initX = newX;
|
||||
return true;
|
||||
} else {
|
||||
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
||||
primaryStage.setWidth(titleContainer.getMinWidth());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setStageHeight(double height) {
|
||||
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
||||
primaryStage.setHeight(height);
|
||||
initY = newY;
|
||||
return true;
|
||||
} else {
|
||||
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
||||
primaryStage.setHeight(titleContainer.getHeight());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMaximized(boolean maximized) {
|
||||
if (this.maximized != maximized) {
|
||||
Platform.runLater(btnMax::fire);
|
||||
}
|
||||
}
|
||||
|
||||
private void showCloseNavButton() {
|
||||
navLeft.getChildren().add(closeNavButton);
|
||||
}
|
||||
|
||||
private void hideCloseNavButton() {
|
||||
navLeft.getChildren().remove(closeNavButton);
|
||||
}
|
||||
|
||||
private void setContent(Node content, AnimationProducer animation) {
|
||||
isWizardPageNow = false;
|
||||
animationHandler.setContent(content, animation);
|
||||
|
||||
if (content instanceof Region) {
|
||||
((Region) content).setMinSize(0, 0);
|
||||
FXUtils.setOverflowHidden((Region) content, true);
|
||||
}
|
||||
|
||||
refreshNavButton.setVisible(content instanceof Refreshable);
|
||||
backNavButton.setVisible(content != mainPage);
|
||||
|
||||
String prefix = category == null ? "" : category + " - ";
|
||||
|
||||
titleLabel.textProperty().unbind();
|
||||
|
||||
if (content instanceof WizardPage)
|
||||
titleLabel.setText(prefix + ((WizardPage) content).getTitle());
|
||||
|
||||
if (content instanceof DecoratorPage)
|
||||
titleLabel.textProperty().bind(((DecoratorPage) content).titleProperty());
|
||||
}
|
||||
|
||||
private String category;
|
||||
private Node nowPage;
|
||||
private boolean isWizardPageNow;
|
||||
|
||||
public Node getNowPage() {
|
||||
return nowPage;
|
||||
}
|
||||
|
||||
public void showPage(Node content) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
if (content != null)
|
||||
contentPlaceHolder.getStyleClass().add("gray-background");
|
||||
|
||||
Node c = content == null ? mainPage : content;
|
||||
onEnd();
|
||||
if (nowPage instanceof DecoratorPage)
|
||||
((DecoratorPage) nowPage).onClose();
|
||||
nowPage = content;
|
||||
|
||||
setContent(c, ContainerAnimations.FADE.getAnimationProducer());
|
||||
|
||||
if (c instanceof Region) {
|
||||
// Let root pane fix window size.
|
||||
StackPane parent = (StackPane) c.getParent();
|
||||
((Region) c).prefWidthProperty().bind(parent.widthProperty());
|
||||
((Region) c).prefHeightProperty().bind(parent.heightProperty());
|
||||
}
|
||||
}
|
||||
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialog == null) {
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
dialogPane.push(node);
|
||||
|
||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
if (dialog != null) {
|
||||
dialogPane.pop(node);
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
startWizard(wizardProvider, null);
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
this.category = category;
|
||||
wizardController.setProvider(wizardProvider);
|
||||
wizardController.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
backNavButton.setVisible(true);
|
||||
backNavButton.setDisable(false);
|
||||
showCloseNavButton();
|
||||
refreshNavButton.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd() {
|
||||
backNavButton.setVisible(false);
|
||||
hideCloseNavButton();
|
||||
refreshNavButton.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
contentPlaceHolder.getStyleClass().add("white-background");
|
||||
setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
isWizardPageNow = true;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onRefresh() {
|
||||
((Refreshable) contentPlaceHolder.getChildren().get(0)).refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCloseNav() {
|
||||
wizardController.onCancel();
|
||||
showPage(null);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBack() {
|
||||
if (isWizardPageNow && wizardController.canPrev())
|
||||
wizardController.onPrev(true);
|
||||
else
|
||||
onCloseNav();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queue<Object> getCancelQueue() {
|
||||
return cancelQueue;
|
||||
}
|
||||
|
||||
public Runnable getOnCloseButtonAction() {
|
||||
return onCloseButtonAction.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
||||
return onCloseButtonAction;
|
||||
}
|
||||
|
||||
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
||||
this.onCloseButtonAction.set(onCloseButtonAction);
|
||||
}
|
||||
|
||||
public boolean isCustomMaximize() {
|
||||
return customMaximize.get();
|
||||
}
|
||||
|
||||
public BooleanProperty customMaximizeProperty() {
|
||||
return customMaximize;
|
||||
}
|
||||
|
||||
public void setCustomMaximize(boolean customMaximize) {
|
||||
this.customMaximize.set(customMaximize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WizardController getWizardController() {
|
||||
return wizardController;
|
||||
}
|
||||
|
||||
public AdvancedListBox getLeftPane() {
|
||||
return leftPane;
|
||||
}
|
||||
|
||||
private void setupAuthlibInjectorDnD() {
|
||||
addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
|
||||
addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
|
||||
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
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.CountDownLatch;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
@@ -31,11 +32,7 @@ import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
@@ -44,7 +41,6 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
@@ -60,6 +56,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public final class FXUtils {
|
||||
@@ -214,27 +211,29 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
try {
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
try {
|
||||
// Java 9
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
try {
|
||||
// Java 9
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void openFolder(File file) {
|
||||
@@ -254,11 +253,15 @@ public final class FXUtils {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
thread(() -> {
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,17 +273,21 @@ public final class FXUtils {
|
||||
public static void openLink(String link) {
|
||||
if (link == null)
|
||||
return;
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
thread(() -> {
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[] { "/usr/bin/open", link });
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", link});
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
|
||||
}
|
||||
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
|
||||
}
|
||||
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
@@ -321,8 +328,10 @@ public final class FXUtils {
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @deprecated Use {@link SelectionModelSelectedItemProperty#selectedItemPropertyFor(ComboBox)}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
|
||||
unbindEnum(comboBox);
|
||||
ChangeListener<Number> listener = (a, b, newValue) ->
|
||||
@@ -339,6 +348,7 @@ public final class FXUtils {
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
||||
if (listener == null) return;
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameVersionListPage extends StackPane {
|
||||
private final JFXSpinner spinner;
|
||||
private final StackPane contentPane;
|
||||
private final JFXMasonryPane masonryPane;
|
||||
|
||||
private Profile profile;
|
||||
|
||||
public GameVersionListPage() {
|
||||
spinner = new JFXSpinner();
|
||||
spinner.getStyleClass().setAll("first-spinner");
|
||||
|
||||
contentPane = new StackPane();
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
masonryPane = new JFXMasonryPane();
|
||||
masonryPane.setHSpacing(3);
|
||||
masonryPane.setVSpacing(3);
|
||||
masonryPane.setCellWidth(182);
|
||||
masonryPane.setCellHeight(153);
|
||||
|
||||
scrollPane.setContent(masonryPane);
|
||||
|
||||
VBox vBox = new VBox();
|
||||
vBox.setPadding(new Insets(15));
|
||||
vBox.setPickOnBounds(false);
|
||||
vBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
vBox.setSpacing(15);
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
btnRefresh.setPrefWidth(40);
|
||||
btnRefresh.setPrefHeight(40);
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.getStyleClass().setAll("jfx-button-raised-round");
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnRefresh.setOnMouseClicked(e -> profile.getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
|
||||
|
||||
vBox.getChildren().setAll(btnRefresh);
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, vBox);
|
||||
|
||||
getChildren().setAll(spinner);
|
||||
|
||||
Profiles.selectedProfileProperty().addListener((o, a, b) -> this.profile = b);
|
||||
profile = Profiles.getSelectedProfile();
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
loadVersions((HMCLGameRepository) event.getSource());
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
// This will occupy 0.5s. Too slow!
|
||||
JFXUtilities.runInFX(this::loadingVersions);
|
||||
});
|
||||
if (profile.getRepository().isLoaded())
|
||||
loadVersions(profile.getRepository());
|
||||
else
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, Callable<String> gameCallable) {
|
||||
Profile profile = repository.getProfile();
|
||||
String id = version.getId();
|
||||
VersionItem item = new VersionItem();
|
||||
item.setUpdate(repository.isModpack(id));
|
||||
Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> {
|
||||
String game = vars.get("game");
|
||||
item.setGameVersion(game);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n"));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n"));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n"));
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
});
|
||||
item.setVersionName(id);
|
||||
item.setOnLaunchButtonClicked(e -> Versions.launch(profile, id));
|
||||
item.setOnScriptButtonClicked(e -> Versions.generateLaunchScript(profile, id));
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
Controllers.getVersionPage().load(id, profile);
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
});
|
||||
item.setOnUpdateButtonClicked(event -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
} catch (UnsupportedModpackException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
item.setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
JFXListView<String> versionList = new JFXListView<>();
|
||||
JFXPopup versionPopup = new JFXPopup(versionList);
|
||||
versionList.getStyleClass().add("option-list-view");
|
||||
FXUtils.setLimitWidth(versionList, 150);
|
||||
versionList.getItems().setAll(Lang.immutableListOf(
|
||||
i18n("version.manage.rename"),
|
||||
i18n("version.manage.remove"),
|
||||
i18n("modpack.export"),
|
||||
i18n("folder.game")
|
||||
));
|
||||
versionList.setOnMouseClicked(e -> {
|
||||
versionPopup.hide();
|
||||
switch (versionList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
Versions.renameVersion(profile, id);
|
||||
break;
|
||||
case 1:
|
||||
Versions.deleteVersion(profile, id);
|
||||
break;
|
||||
case 2:
|
||||
Versions.exportVersion(profile, id);
|
||||
break;
|
||||
case 3:
|
||||
FXUtils.openFolder(repository.getRunDirectory(id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
Versions.launch(profile, id);
|
||||
}
|
||||
});
|
||||
File iconFile = repository.getVersionIcon(id);
|
||||
if (iconFile.exists())
|
||||
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
return item;
|
||||
}
|
||||
|
||||
private void loadingVersions() {
|
||||
getChildren().setAll(spinner);
|
||||
masonryPane.getChildren().clear();
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == repository.getProfile()) {
|
||||
masonryPane.getChildren().setAll(children);
|
||||
getChildren().setAll(contentPane);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class InstallerController {
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private Version version;
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
@FXML private VBox contentPane;
|
||||
private String forge;
|
||||
private String liteLoader;
|
||||
private String optiFine;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
this.version = profile.getRepository().getResolvedVersion(versionId);
|
||||
|
||||
contentPane.getChildren().clear();
|
||||
forge = liteLoader = optiFine = null;
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
Consumer<InstallerItem> removeAction = 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();
|
||||
};
|
||||
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
contentPane.getChildren().add(new InstallerItem("Forge", library.getVersion(), removeAction));
|
||||
forge = library.getVersion();
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
contentPane.getChildren().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction));
|
||||
liteLoader = library.getVersion();
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
contentPane.getChildren().add(new InstallerItem("OptiFine", library.getVersion(), removeAction));
|
||||
optiFine = library.getVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
|
||||
|
||||
if (!gameVersion.isPresent())
|
||||
Controllers.dialog(i18n("version.cannot_read"));
|
||||
else
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,10 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
|
||||
@@ -18,78 +18,50 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
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.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.account.AddAccountPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static javafx.collections.FXCollections.singletonObservableList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LeftPaneController {
|
||||
private final AdvancedListBox leftPane;
|
||||
private final VBox profilePane = new VBox();
|
||||
private final VBox accountPane = new VBox();
|
||||
private final IconedItem launcherSettingsItem;
|
||||
public final class LeftPaneController extends AdvancedListBox {
|
||||
|
||||
private ListProperty<RipplerContainer> accountItems = new SimpleListProperty<>();
|
||||
private ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
accountItems.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
public LeftPaneController() {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account selected = get();
|
||||
accountItems.forEach(item -> item.setSelected(
|
||||
getAccountFromItem(item)
|
||||
.map(it -> it == selected)
|
||||
.orElse(false)));
|
||||
}
|
||||
};
|
||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
|
||||
gameListItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
|
||||
profileListItem.setOnAction(e -> Controllers.navigate(Controllers.getProfileListPage()));
|
||||
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
||||
|
||||
public LeftPaneController(AdvancedListBox leftPane) {
|
||||
this.leftPane = leftPane;
|
||||
|
||||
launcherSettingsItem = new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20));
|
||||
IconedItem launcherSettingsItem = new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), "iconed-item");
|
||||
|
||||
launcherSettingsItem.getLabel().textProperty().bind(
|
||||
new When(UpdateChecker.outdatedProperty())
|
||||
@@ -101,105 +73,27 @@ public final class LeftPaneController {
|
||||
.then(Color.RED)
|
||||
.otherwise(Color.BLACK));
|
||||
|
||||
launcherSettingsItem.prefWidthProperty().bind(leftPane.widthProperty());
|
||||
launcherSettingsItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
|
||||
leftPane
|
||||
.add(new ClassTitle(i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> {
|
||||
button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
|
||||
button.getStyleClass().add("toggle-icon-tiny");
|
||||
button.setOnMouseClicked(e -> addNewAccount());
|
||||
})))
|
||||
.add(accountPane)
|
||||
this
|
||||
.startCategory(i18n("account").toUpperCase())
|
||||
.add(accountListItem)
|
||||
.startCategory(i18n("version").toUpperCase())
|
||||
.add(gameListItem)
|
||||
.startCategory(i18n("profile.title").toUpperCase())
|
||||
.add(profileListItem)
|
||||
.startCategory(i18n("launcher").toUpperCase())
|
||||
.add(launcherSettingsItem)
|
||||
.add(new ClassTitle(i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> {
|
||||
button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
|
||||
button.getStyleClass().add("toggle-icon-tiny");
|
||||
button.setOnMouseClicked(e ->
|
||||
Controllers.getDecorator().showPage(new ProfilePage(null)));
|
||||
})))
|
||||
.add(profilePane);
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
// ==== Accounts ====
|
||||
// Missing account item
|
||||
AdvancedListItem missingAccountItem = new AdvancedListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
RipplerContainer missingAccountRippler = new RipplerContainer(missingAccountItem);
|
||||
missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount());
|
||||
missingAccountRippler.setOnMouseClicked(e -> addNewAccount());
|
||||
|
||||
accountItems.bind(
|
||||
new When(Accounts.accountsProperty().emptyProperty())
|
||||
.then(singletonObservableList(missingAccountRippler))
|
||||
.otherwise(MappedObservableList.create(Accounts.getAccounts(), this::createAccountItem)));
|
||||
Bindings.bindContent(accountPane.getChildren(), accountItems);
|
||||
|
||||
selectedAccount.bindBidirectional(Accounts.selectedAccountProperty());
|
||||
// ====
|
||||
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(this::onRefreshedVersions);
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
||||
if (Profiles.selectedProfileProperty().get().getRepository().isLoaded())
|
||||
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
||||
}
|
||||
|
||||
// ==== Accounts ====
|
||||
private Optional<Account> getAccountFromItem(RipplerContainer accountItem) {
|
||||
return Optional.ofNullable(accountItem.getProperties().get("account"))
|
||||
.map(Account.class::cast);
|
||||
}
|
||||
|
||||
private static String accountSubtitle(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return account.getUsername();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
private RipplerContainer createAccountItem(Account account) {
|
||||
AdvancedListItem item = new AdvancedListItem(account.getCharacter(), accountSubtitle(account));
|
||||
RipplerContainer rippler = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
JFXPopup popup = new JFXPopup(accountPage);
|
||||
accountPage.setOnDelete(popup::hide);
|
||||
popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
});
|
||||
rippler.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
selectedAccount.set(account);
|
||||
}
|
||||
});
|
||||
rippler.getProperties().put("account", account);
|
||||
rippler.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else {
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
}
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(rippler, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
// update skin
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
return rippler;
|
||||
}
|
||||
|
||||
public void checkAccount() {
|
||||
if (Accounts.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
Platform.runLater(this::addNewAccount);
|
||||
}
|
||||
|
||||
private void addNewAccount() {
|
||||
@@ -207,40 +101,11 @@ public final class LeftPaneController {
|
||||
}
|
||||
// ====
|
||||
|
||||
private void onProfileChanged(ProfileChangedEvent event) {
|
||||
Profile profile = event.getProfile();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
for (Node node : profilePane.getChildren()) {
|
||||
if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof String) {
|
||||
boolean current = Objects.equals(node.getProperties().get("profile"), profile.getName());
|
||||
((RipplerContainer) node).setSelected(current);
|
||||
((AdvancedListItem) ((RipplerContainer) node).getContainer()).setSubtitle(current ? i18n("profile.selected") : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onProfilesLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
for (Profile profile : Settings.instance().getProfiles()) {
|
||||
AdvancedListItem item = new AdvancedListItem(Profiles.getProfileDisplayName(profile));
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.instance().setSelectedProfile(profile));
|
||||
ripplerContainer.getProperties().put("profile", profile.getName());
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
list.add(ripplerContainer);
|
||||
}
|
||||
Platform.runLater(() -> profilePane.getChildren().setAll(list));
|
||||
}
|
||||
|
||||
private boolean checkedModpack = false;
|
||||
private static boolean showNewAccount = true;
|
||||
|
||||
private void onRefreshedVersions(RefreshedVersionsEvent event) {
|
||||
private void onRefreshedVersions(HMCLGameRepository repository) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
HMCLGameRepository repository = (HMCLGameRepository) event.getSource();
|
||||
if (!checkedModpack) {
|
||||
checkedModpack = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
public abstract class ListPage<T extends Node> extends Control {
|
||||
private final ListProperty<T> items = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
|
||||
public abstract void add();
|
||||
|
||||
public ListProperty<T> itemsProperty() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ListPageSkin(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXScrollPane;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
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.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
public class ListPageSkin extends SkinBase<ListPage> {
|
||||
|
||||
public ListPageSkin(ListPage<?> skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane rootPane = new StackPane();
|
||||
|
||||
JFXSpinner spinner = new JFXSpinner();
|
||||
spinner.setRadius(16);
|
||||
spinner.getStyleClass().setAll("materialDesign-purple", "first-spinner");
|
||||
|
||||
StackPane contentPane = new StackPane();
|
||||
{
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
{
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
VBox accountList = new VBox();
|
||||
accountList.maxWidthProperty().bind(scrollPane.widthProperty());
|
||||
accountList.setSpacing(10);
|
||||
accountList.setStyle("-fx-padding: 10 10 10 10;");
|
||||
|
||||
Bindings.bindContent(accountList.getChildren(), skinnable.itemsProperty());
|
||||
|
||||
scrollPane.setContent(accountList);
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
VBox vBox = new VBox();
|
||||
{
|
||||
vBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
vBox.setPickOnBounds(false);
|
||||
vBox.setPadding(new Insets(15));
|
||||
vBox.setSpacing(15);
|
||||
|
||||
JFXButton btnAdd = new JFXButton();
|
||||
FXUtils.setLimitWidth(btnAdd, 40);
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.getStyleClass().setAll("jfx-button-raised-round");
|
||||
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1));
|
||||
btnAdd.setOnMouseClicked(e -> skinnable.add());
|
||||
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
}
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, vBox);
|
||||
}
|
||||
|
||||
rootPane.getChildren().setAll(contentPane);
|
||||
|
||||
skinnable.loadingProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue) rootPane.getChildren().setAll(spinner);
|
||||
else rootPane.getChildren().setAll(contentPane);
|
||||
});
|
||||
|
||||
getChildren().setAll(rootPane);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
@@ -43,11 +42,11 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -182,7 +181,7 @@ public final class LogWindow extends Stage {
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
document = engine.getDocument();
|
||||
body = document.getElementsByTagName("body").item(0);
|
||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
||||
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||
latch.countDown();
|
||||
onDone.fireEvent(new Event(LogWindow.this));
|
||||
}
|
||||
@@ -190,14 +189,14 @@ public final class LogWindow extends Stage {
|
||||
|
||||
boolean flag = false;
|
||||
for (String i : cboLines.getItems())
|
||||
if (Integer.toString(Settings.instance().getLogLines()).equals(i)) {
|
||||
if (Integer.toString(config().getLogLines()).equals(i)) {
|
||||
cboLines.getSelectionModel().select(i);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
Settings.instance().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
||||
config().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
|
||||
@@ -18,242 +18,129 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page"));
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("main_page"));
|
||||
private final VBox menu = new VBox();
|
||||
private final JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
@FXML
|
||||
private StackPane main;
|
||||
@FXML
|
||||
private JFXButton btnLaunch;
|
||||
@FXML
|
||||
private JFXButton btnMenu;
|
||||
@FXML
|
||||
private Label lblCurrentGame;
|
||||
|
||||
private Profile profile;
|
||||
|
||||
@FXML
|
||||
private JFXButton btnRefresh;
|
||||
@FXML
|
||||
private StackPane contentPane;
|
||||
@FXML
|
||||
private JFXButton btnAdd;
|
||||
@FXML
|
||||
private JFXSpinner spinner;
|
||||
@FXML
|
||||
private JFXMasonryPane masonryPane;
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
|
||||
loadingVersions();
|
||||
FXUtils.onChangeAndOperate(ConfigHolder.config().enableMainPageGameListProperty(), newValue -> {
|
||||
if (newValue)
|
||||
getChildren().setAll(new GameVersionListPage());
|
||||
else
|
||||
getChildren().setAll(main);
|
||||
});
|
||||
|
||||
btnLaunch.setClip(new Rectangle(-100, -100, 280, 200));
|
||||
btnMenu.setClip(new Rectangle(180, -100, 100, 200));
|
||||
menu.setMinWidth(200);
|
||||
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(11);
|
||||
btnMenu.setGraphic(graphic);
|
||||
|
||||
Profiles.selectedVersionProperty().addListener((o, a, version) -> {
|
||||
if (version != null) {
|
||||
lblCurrentGame.setText(version);
|
||||
} else {
|
||||
lblCurrentGame.setText(i18n("version.empty"));
|
||||
}
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
loadVersions((HMCLGameRepository) event.getSource());
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
// This will occupy 0.5s. Too slow!
|
||||
JFXUtilities.runInFX(this::loadingVersions);
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(event -> {
|
||||
this.profile = event.getProfile();
|
||||
});
|
||||
Profiles.selectedProfileProperty().addListener((a, b, newValue) -> profile = newValue);
|
||||
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), i18n("install")));
|
||||
FXUtils.installTooltip(btnAdd, i18n("install"));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.instance().getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
|
||||
}
|
||||
|
||||
private String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, Callable<String> gameCallable) {
|
||||
Profile profile = repository.getProfile();
|
||||
String id = version.getId();
|
||||
VersionItem item = new VersionItem();
|
||||
item.setUpdate(repository.isModpack(id));
|
||||
Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> {
|
||||
String game = vars.get("game");
|
||||
item.setGameVersion(game);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
});
|
||||
item.setVersionName(id);
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
profile = Profiles.getSelectedProfile();
|
||||
if (profile != null) {
|
||||
if (profile.getRepository().isLoaded())
|
||||
loadVersions(profile.getRepository());
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
});
|
||||
item.setOnScriptButtonClicked(e -> {
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
if (repository.getRunDirectory(id).isDirectory())
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
chooser.setTitle(i18n("version.launch_script.save"));
|
||||
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat")
|
||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, file);
|
||||
}
|
||||
});
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
Controllers.getDecorator().showPage(Controllers.getVersionPage());
|
||||
Controllers.getVersionPage().load(id, profile);
|
||||
});
|
||||
item.setOnUpdateButtonClicked(event -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
} catch (UnsupportedModpackException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
item.setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
JFXListView<String> versionList = new JFXListView<>();
|
||||
JFXPopup versionPopup = new JFXPopup(versionList);
|
||||
versionList.getStyleClass().add("option-list-view");
|
||||
FXUtils.setLimitWidth(versionList, 150);
|
||||
versionList.getItems().setAll(Lang.immutableListOf(
|
||||
i18n("version.manage.rename"),
|
||||
i18n("version.manage.remove"),
|
||||
i18n("modpack.export"),
|
||||
i18n("folder.game")
|
||||
));
|
||||
versionList.setOnMouseClicked(e -> {
|
||||
versionPopup.hide();
|
||||
switch (versionList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
VersionPage.renameVersion(profile, id);
|
||||
break;
|
||||
case 1:
|
||||
VersionPage.deleteVersion(profile, id);
|
||||
break;
|
||||
case 2:
|
||||
VersionPage.exportVersion(profile, id);
|
||||
break;
|
||||
case 3:
|
||||
FXUtils.openFolder(repository.getRunDirectory(id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
}
|
||||
});
|
||||
File iconFile = repository.getVersionIcon(id);
|
||||
if (iconFile.exists())
|
||||
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
return item;
|
||||
}
|
||||
|
||||
private void loadingVersions() {
|
||||
getChildren().setAll(spinner);
|
||||
masonryPane.getChildren().clear();
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
List<IconedMenuItem> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.map(version -> new IconedMenuItem(null, version.getId(), () -> {
|
||||
repository.getProfile().setSelectedVersion(version.getId());
|
||||
Versions.launch(repository.getProfile(), version.getId());
|
||||
popup.hide();
|
||||
}))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == repository.getProfile()) {
|
||||
masonryPane.getChildren().setAll(children);
|
||||
getChildren().setAll(contentPane);
|
||||
}
|
||||
if (profile == repository.getProfile())
|
||||
menu.getChildren().setAll(children);
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void launch() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, profile.getSelectedVersion());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMenu() {
|
||||
popup.show(btnMenu, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -btnMenu.getHeight());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
|
||||
@@ -33,6 +35,12 @@ public final class SVG {
|
||||
path.setContent(d);
|
||||
path.fillProperty().bind(fill);
|
||||
|
||||
if (width < 0 || height < 0) {
|
||||
StackPane pane = new StackPane(path);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
return pane;
|
||||
}
|
||||
|
||||
Group svg = new Group(path);
|
||||
double scale = Math.min(width / svg.getBoundsInParent().getWidth(), height / svg.getBoundsInParent().getHeight());
|
||||
svg.setScaleX(scale);
|
||||
@@ -83,6 +91,10 @@ public final class SVG {
|
||||
return createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node launch2(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node script(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z", fill, width, height);
|
||||
}
|
||||
@@ -127,4 +139,19 @@ public final class SVG {
|
||||
return createSVGPath("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node importIcon(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node export(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node openInNew(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node triangle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M1,21H23L12,2", fill, width, height);
|
||||
}
|
||||
}
|
||||
@@ -17,117 +17,56 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.controls.JFXColorPicker;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.FontComboBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("settings.launcher"));
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.SelectionModelSelectedItemProperty.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProxyHost;
|
||||
@FXML
|
||||
private JFXTextField txtProxyPort;
|
||||
@FXML
|
||||
private JFXTextField txtProxyUsername;
|
||||
@FXML
|
||||
private JFXPasswordField txtProxyPassword;
|
||||
@FXML
|
||||
private JFXTextField txtFontSize;
|
||||
@FXML
|
||||
private JFXComboBox<Label> cboLanguage;
|
||||
@FXML
|
||||
private JFXComboBox<?> cboDownloadSource;
|
||||
@FXML
|
||||
private FontComboBox cboFont;
|
||||
@FXML
|
||||
private MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
@FXML
|
||||
private Label lblDisplay;
|
||||
@FXML
|
||||
private Label lblUpdate;
|
||||
@FXML
|
||||
private Label lblUpdateSub;
|
||||
@FXML
|
||||
private Text lblUpdateNote;
|
||||
@FXML
|
||||
private JFXRadioButton chkUpdateStable;
|
||||
@FXML
|
||||
private JFXRadioButton chkUpdateDev;
|
||||
@FXML
|
||||
private JFXButton btnUpdate;
|
||||
@FXML
|
||||
private ScrollPane scroll;
|
||||
@FXML
|
||||
private MultiFileItem<EnumBackgroundImage> backgroundItem;
|
||||
@FXML
|
||||
private StackPane themeColorPickerContainer;
|
||||
@FXML
|
||||
private JFXCheckBox chkDisableProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxyHttp;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxySocks;
|
||||
@FXML
|
||||
private JFXCheckBox chkProxyAuthentication;
|
||||
@FXML
|
||||
private GridPane authPane;
|
||||
@FXML
|
||||
private Pane proxyPane;
|
||||
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));
|
||||
|
||||
private ObjectProperty<Proxy.Type> selectedProxyType;
|
||||
|
||||
private InvalidationListener updateListener;
|
||||
|
||||
public SettingsPage() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/setting.fxml");
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.instance().getDownloadProvider()));
|
||||
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.instance().setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
|
||||
// ==== Download sources ====
|
||||
cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet());
|
||||
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
|
||||
// ====
|
||||
|
||||
cboFont.getSelectionModel().select(Settings.instance().getFont().getFamily());
|
||||
chkEnableGameList.selectedProperty().bindBidirectional(config().enableMainPageGameListProperty());
|
||||
|
||||
cboFont.initValue(Settings.instance().getFont());
|
||||
cboFont.valueProperty().addListener((a, b, newValue) -> {
|
||||
Font font = Font.font(newValue, Settings.instance().getFont().getSize());
|
||||
Settings.instance().setFont(font);
|
||||
@@ -146,13 +85,10 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
lblDisplay.setStyle("-fx-font: " + Settings.instance().getFont().getSize() + " \"" + Settings.instance().getFont().getFamily() + "\";");
|
||||
|
||||
ObservableList<Label> list = FXCollections.observableArrayList();
|
||||
for (Locales.SupportedLocale locale : Locales.LOCALES)
|
||||
list.add(new Label(locale.getName(config().getLocalization().getResourceBundle())));
|
||||
|
||||
cboLanguage.setItems(list);
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(config().getLocalization()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> config().setLocalization(Locales.getLocale(newValue.intValue())));
|
||||
// ==== Languages ====
|
||||
cboLanguage.getItems().setAll(Locales.LOCALES);
|
||||
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||
// ====
|
||||
|
||||
// ==== Proxy ====
|
||||
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
||||
@@ -200,7 +136,6 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
// ====
|
||||
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
fileCommonLocation.createChildren(i18n("launcher.common_directory.disabled"), EnumCommonDirectory.DISABLED),
|
||||
fileCommonLocation.createChildren(i18n("launcher.common_directory.default"), EnumCommonDirectory.DEFAULT)
|
||||
), EnumCommonDirectory.CUSTOM);
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
@@ -242,24 +177,24 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
lblUpdateNote.setWrappingWidth(470);
|
||||
|
||||
ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<EnumUpdateChannel>() {
|
||||
ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<UpdateChannel>() {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
EnumUpdateChannel updateChannel = Objects.requireNonNull(get());
|
||||
chkUpdateDev.setSelected(updateChannel == EnumUpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setSelected(updateChannel == EnumUpdateChannel.STABLE);
|
||||
UpdateChannel updateChannel = Objects.requireNonNull(get());
|
||||
chkUpdateDev.setSelected(updateChannel == UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setSelected(updateChannel == UpdateChannel.STABLE);
|
||||
}
|
||||
};
|
||||
|
||||
ToggleGroup updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(EnumUpdateChannel.DEVELOPMENT);
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(EnumUpdateChannel.STABLE);
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
updateChannelGroup.getToggles().forEach(
|
||||
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
updateChannel.set((EnumUpdateChannel) toggle.getUserData());
|
||||
updateChannel.set((UpdateChannel) toggle.getUserData());
|
||||
}
|
||||
}));
|
||||
updateChannel.bindBidirectional(ConfigHolder.config().updateChannelProperty());
|
||||
@@ -297,20 +232,25 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onUpdate() {
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
RemoteVersion target = UpdateChecker.getLatestVersion();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
UpdateHandler.updateFrom(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHelp() {
|
||||
FXUtils.openLink(Metadata.HELP_URL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
public abstract class SettingsView extends StackPane {
|
||||
protected final JFXTextField txtProxyHost;
|
||||
protected final JFXTextField txtProxyPort;
|
||||
protected final JFXTextField txtProxyUsername;
|
||||
protected final JFXPasswordField txtProxyPassword;
|
||||
protected final JFXTextField txtFontSize;
|
||||
protected final JFXComboBox<SupportedLocale> cboLanguage;
|
||||
protected final JFXComboBox<String> cboDownloadSource;
|
||||
protected final FontComboBox cboFont;
|
||||
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
protected final Label lblDisplay;
|
||||
protected final Label lblUpdate;
|
||||
protected final Label lblUpdateSub;
|
||||
protected final Text lblUpdateNote;
|
||||
protected final JFXRadioButton chkUpdateStable;
|
||||
protected final JFXRadioButton chkUpdateDev;
|
||||
protected final JFXButton btnUpdate;
|
||||
protected final ScrollPane scroll;
|
||||
protected final MultiFileItem<EnumBackgroundImage> backgroundItem;
|
||||
protected final StackPane themeColorPickerContainer;
|
||||
protected final JFXCheckBox chkDisableProxy;
|
||||
protected final JFXRadioButton chkProxyHttp;
|
||||
protected final JFXRadioButton chkProxySocks;
|
||||
protected final JFXCheckBox chkProxyAuthentication;
|
||||
protected final GridPane authPane;
|
||||
protected final Pane proxyPane;
|
||||
protected final JFXToggleButton chkEnableGameList;
|
||||
|
||||
public SettingsView() {
|
||||
scroll = new ScrollPane();
|
||||
getChildren().setAll(scroll);
|
||||
scroll.setStyle("-fx-font-size: 14;");
|
||||
scroll.setFitToWidth(true);
|
||||
|
||||
{
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setStyle("-fx-padding: 18;");
|
||||
{
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.setOnMouseClicked(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().setAll("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 20, 20));
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox();
|
||||
content.setSpacing(8);
|
||||
|
||||
chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
|
||||
VBox noteWrapper = new VBox();
|
||||
noteWrapper.setStyle("-fx-padding: 10 0 0 0;");
|
||||
lblUpdateNote = new Text(i18n("update.note"));
|
||||
noteWrapper.getChildren().setAll(lblUpdateNote);
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
|
||||
updatePane.getContent().add(content);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane updatePane = new BorderPane();
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
Label help = new Label(i18n("help"));
|
||||
Label helpSubtitle = new Label(i18n("help.detail"));
|
||||
helpSubtitle.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(help, helpSubtitle);
|
||||
updatePane.setLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnExternal = new JFXButton();
|
||||
btnExternal.setOnMouseClicked(e -> onHelp());
|
||||
btnExternal.getStyleClass().setAll("toggle-icon4");
|
||||
btnExternal.setGraphic(SVG.openInNew(Theme.blackFillBinding(), -1, -1));
|
||||
|
||||
updatePane.setRight(btnExternal);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
fileCommonLocation = new MultiFileItem<>(true);
|
||||
fileCommonLocation.setTitle(i18n("launcher.common_directory"));
|
||||
fileCommonLocation.setDirectory(true);
|
||||
fileCommonLocation.setChooserTitle(i18n("launcher.common_directory.choose"));
|
||||
fileCommonLocation.setHasSubtitle(true);
|
||||
fileCommonLocation.setCustomText("settings.custom");
|
||||
|
||||
settingsPane.getContent().add(fileCommonLocation);
|
||||
}
|
||||
|
||||
{
|
||||
backgroundItem = new MultiFileItem<>(true);
|
||||
backgroundItem.setTitle(i18n("launcher.background"));
|
||||
backgroundItem.setChooserTitle(i18n("launcher.background.choose"));
|
||||
backgroundItem.setHasSubtitle(true);
|
||||
backgroundItem.setCustomText(i18n("settings.custom"));
|
||||
|
||||
settingsPane.getContent().add(backgroundItem);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane borderPane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.enable_game_list"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
borderPane.setLeft(left);
|
||||
|
||||
chkEnableGameList = new JFXToggleButton();
|
||||
chkEnableGameList.setSize(8);
|
||||
FXUtils.setLimitHeight(chkEnableGameList, 20);
|
||||
borderPane.setRight(chkEnableGameList);
|
||||
|
||||
// Do not uncomment this line,
|
||||
// since option for user to enable game list in main page is unnecessary
|
||||
// settingsPane.addChildren(borderPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane downloadSourcePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("settings.launcher.download_source"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
downloadSourcePane.setLeft(label);
|
||||
}
|
||||
|
||||
{
|
||||
cboDownloadSource = new JFXComboBox<>();
|
||||
cboDownloadSource.setConverter(stringConverter(key -> i18n("download.provider." + key)));
|
||||
downloadSourcePane.setRight(cboDownloadSource);
|
||||
}
|
||||
settingsPane.getContent().add(downloadSourcePane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane languagePane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.language"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
languagePane.setLeft(left);
|
||||
|
||||
cboLanguage = new JFXComboBox<>();
|
||||
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
|
||||
FXUtils.setLimitWidth(cboLanguage, 400);
|
||||
languagePane.setRight(cboLanguage);
|
||||
|
||||
settingsPane.getContent().add(languagePane);
|
||||
}
|
||||
|
||||
{
|
||||
ComponentList proxyList = new ComponentList();
|
||||
proxyList.setTitle(i18n("settings.launcher.proxy"));
|
||||
|
||||
VBox proxyWrapper = new VBox();
|
||||
proxyWrapper.setSpacing(10);
|
||||
|
||||
{
|
||||
chkDisableProxy = new JFXCheckBox(i18n("settings.launcher.proxy.disable"));
|
||||
proxyWrapper.getChildren().add(chkDisableProxy);
|
||||
}
|
||||
|
||||
{
|
||||
proxyPane = new VBox();
|
||||
proxyPane.setStyle("-fx-padding: 0 0 0 30;");
|
||||
|
||||
ColumnConstraints colHgrow = new ColumnConstraints();
|
||||
colHgrow.setHgrow(Priority.ALWAYS);
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
|
||||
chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
|
||||
hBox.getChildren().setAll(chkProxyHttp, chkProxySocks);
|
||||
proxyPane.getChildren().add(hBox);
|
||||
}
|
||||
|
||||
{
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(20);
|
||||
gridPane.setVgap(10);
|
||||
gridPane.setStyle("-fx-padding: 0 0 0 15;");
|
||||
gridPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
|
||||
gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
{
|
||||
Label host = new Label(i18n("settings.launcher.proxy.host"));
|
||||
GridPane.setRowIndex(host, 1);
|
||||
GridPane.setColumnIndex(host, 0);
|
||||
GridPane.setHalignment(host, HPos.RIGHT);
|
||||
gridPane.getChildren().add(host);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyHost = new JFXTextField();
|
||||
txtProxyHost.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyHost, 1);
|
||||
GridPane.setColumnIndex(txtProxyHost, 1);
|
||||
gridPane.getChildren().add(txtProxyHost);
|
||||
}
|
||||
|
||||
{
|
||||
Label port = new Label(i18n("settings.launcher.proxy.port"));
|
||||
GridPane.setRowIndex(port, 2);
|
||||
GridPane.setColumnIndex(port, 0);
|
||||
GridPane.setHalignment(port, HPos.RIGHT);
|
||||
gridPane.getChildren().add(port);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyPort = new JFXTextField();
|
||||
txtProxyPort.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyPort, 2);
|
||||
GridPane.setColumnIndex(txtProxyPort, 1);
|
||||
FXUtils.setValidateWhileTextChanged(txtProxyPort, true);
|
||||
txtProxyHost.getValidators().setAll(new NumberValidator(i18n("input.number"), false));
|
||||
gridPane.getChildren().add(txtProxyPort);
|
||||
}
|
||||
proxyPane.getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
{
|
||||
VBox vBox = new VBox();
|
||||
vBox.setStyle("-fx-padding: 20 0 20 5;");
|
||||
|
||||
chkProxyAuthentication = new JFXCheckBox(i18n("settings.launcher.proxy.authentication"));
|
||||
vBox.getChildren().setAll(chkProxyAuthentication);
|
||||
|
||||
proxyPane.getChildren().add(vBox);
|
||||
}
|
||||
|
||||
{
|
||||
authPane = new GridPane();
|
||||
authPane.setHgap(20);
|
||||
authPane.setVgap(10);
|
||||
authPane.setStyle("-fx-padding: 0 0 0 15;");
|
||||
authPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
|
||||
authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
{
|
||||
Label username = new Label(i18n("settings.launcher.proxy.username"));
|
||||
GridPane.setRowIndex(username, 0);
|
||||
GridPane.setColumnIndex(username, 0);
|
||||
authPane.getChildren().add(username);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyUsername = new JFXTextField();
|
||||
txtProxyUsername.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyUsername, 0);
|
||||
GridPane.setColumnIndex(txtProxyUsername, 1);
|
||||
authPane.getChildren().add(txtProxyUsername);
|
||||
}
|
||||
|
||||
{
|
||||
Label password = new Label(i18n("settings.launcher.proxy.password"));
|
||||
GridPane.setRowIndex(password, 1);
|
||||
GridPane.setColumnIndex(password, 0);
|
||||
authPane.getChildren().add(password);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyPassword = new JFXPasswordField();
|
||||
txtProxyPassword.getStyleClass().setAll("fit-width");
|
||||
GridPane.setRowIndex(txtProxyPassword, 1);
|
||||
GridPane.setColumnIndex(txtProxyPassword, 1);
|
||||
authPane.getChildren().add(txtProxyPassword);
|
||||
}
|
||||
|
||||
proxyPane.getChildren().add(authPane);
|
||||
}
|
||||
proxyWrapper.getChildren().add(proxyPane);
|
||||
}
|
||||
proxyList.getContent().add(proxyWrapper);
|
||||
settingsPane.getContent().add(proxyList);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane themePane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.theme"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
themePane.setLeft(left);
|
||||
|
||||
themeColorPickerContainer = new StackPane();
|
||||
themeColorPickerContainer.setMinHeight(30);
|
||||
themePane.setRight(themeColorPickerContainer);
|
||||
|
||||
settingsPane.getContent().add(themePane);
|
||||
}
|
||||
|
||||
{
|
||||
VBox fontPane = new VBox();
|
||||
fontPane.setSpacing(5);
|
||||
settingsPane.getContent().add(fontPane);
|
||||
|
||||
{
|
||||
BorderPane borderPane = new BorderPane();
|
||||
fontPane.getChildren().add(borderPane);
|
||||
{
|
||||
Label left = new Label(i18n("settings.launcher.log_font"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
borderPane.setLeft(left);
|
||||
}
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
hBox.setSpacing(3);
|
||||
|
||||
cboFont = new FontComboBox(12, false);
|
||||
txtFontSize = new JFXTextField();
|
||||
FXUtils.setLimitWidth(txtFontSize, 50);
|
||||
hBox.getChildren().setAll(cboFont, txtFontSize);
|
||||
|
||||
borderPane.setRight(hBox);
|
||||
}
|
||||
}
|
||||
|
||||
lblDisplay = new Label("[23:33:33] [Client Thread/INFO] [WaterPower]: Loaded mod WaterPower.");
|
||||
fontPane.getChildren().add(lblDisplay);
|
||||
}
|
||||
|
||||
{
|
||||
StackPane aboutPane = new StackPane();
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(20);
|
||||
gridPane.setVgap(10);
|
||||
|
||||
ColumnConstraints col1 = new ColumnConstraints();
|
||||
col1.setHgrow(Priority.SOMETIMES);
|
||||
col1.setMaxWidth(Double.NEGATIVE_INFINITY);
|
||||
col1.setMinWidth(Double.NEGATIVE_INFINITY);
|
||||
|
||||
ColumnConstraints col2 = new ColumnConstraints();
|
||||
col2.setHgrow(Priority.SOMETIMES);
|
||||
col2.setMinWidth(20);
|
||||
col2.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col1, col2);
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row, row, row, row, row, row);
|
||||
|
||||
{
|
||||
Label label = new Label(i18n("about.copyright"));
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.copyright.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.author"));
|
||||
GridPane.setRowIndex(label, 1);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.author.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 1);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.thanks_to"));
|
||||
GridPane.setRowIndex(label, 2);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.thanks_to.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 2);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.dependency"));
|
||||
GridPane.setRowIndex(label, 3);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.dependency.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 3);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.claim"));
|
||||
GridPane.setRowIndex(label, 4);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.claim.statement"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 4);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.open_source"));
|
||||
GridPane.setRowIndex(label, 5);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.open_source.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 5);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
aboutPane.getChildren().setAll(gridPane);
|
||||
settingsPane.getContent().add(aboutPane);
|
||||
}
|
||||
rootPane.getChildren().add(settingsPane);
|
||||
}
|
||||
scroll.setContent(rootPane);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
protected abstract void onHelp();
|
||||
}
|
||||
@@ -34,12 +34,12 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionItem extends StackPane {
|
||||
@FXML
|
||||
private Pane icon;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.WeakListChangeListener;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class WeakListenerHolder {
|
||||
private List<Object> refs = new LinkedList<>();
|
||||
|
||||
public WeakListenerHolder() {
|
||||
}
|
||||
|
||||
public WeakInvalidationListener weak(InvalidationListener listener) {
|
||||
refs.add(listener);
|
||||
return new WeakInvalidationListener(listener);
|
||||
}
|
||||
|
||||
public <T> WeakChangeListener<T> weak(ChangeListener<T> listener) {
|
||||
refs.add(listener);
|
||||
return new WeakChangeListener<>(listener);
|
||||
}
|
||||
|
||||
public <T> WeakListChangeListener<T> weak(ListChangeListener<T> listener) {
|
||||
refs.add(listener);
|
||||
return new WeakListChangeListener<>(listener);
|
||||
}
|
||||
|
||||
public void add(Object obj) {
|
||||
refs.add(obj);
|
||||
}
|
||||
|
||||
public boolean remove(Object obj) {
|
||||
return refs.remove(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
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;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
private ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account account = get();
|
||||
if (account == null) {
|
||||
titleProperty().set(i18n("account.missing"));
|
||||
subtitleProperty().set(i18n("account.missing.add"));
|
||||
imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
viewportProperty().set(null);
|
||||
} else {
|
||||
titleProperty().set(account.getCharacter());
|
||||
subtitleProperty().set(accountSubtitle(account));
|
||||
|
||||
imageProperty().set(AccountHelper.getDefaultSkin(account.getUUID(), 4));
|
||||
viewportProperty().set(AccountHelper.getViewport(4));
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
AccountHelper.loadSkinAsync((YggdrasilAccount) account).subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
imageProperty().set(image);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public AccountAdvancedListItem() {
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> accountProperty() {
|
||||
return account;
|
||||
}
|
||||
|
||||
private static String accountSubtitle(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return account.getUsername();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountList extends ListPage<AccountListItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(i18n("account.manage"));
|
||||
private ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
itemsProperty().addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account selected = get();
|
||||
itemsProperty().forEach(item -> item.selectedProperty().set(item.getAccount() == selected));
|
||||
}
|
||||
};
|
||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
private final ObservableList<AccountListItem> accountItems;
|
||||
|
||||
public AccountList() {
|
||||
toggleGroup = new ToggleGroup();
|
||||
|
||||
accountItems = MappedObservableList.create(
|
||||
accountsProperty(),
|
||||
account -> new AccountListItem(toggleGroup, account));
|
||||
|
||||
itemsProperty().bindContent(accountItems);
|
||||
|
||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||
if (toggle == null || toggle.getUserData() == null) return;
|
||||
selectedAccount.set(((AccountListItem) toggle.getUserData()).getAccount());
|
||||
});
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public ListProperty<Account> accountsProperty() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
Controllers.dialog(new AddAccountPane());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountListItem extends Control {
|
||||
private final Account account;
|
||||
private final ToggleGroup toggleGroup;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>();
|
||||
|
||||
public AccountListItem(ToggleGroup toggleGroup, Account account) {
|
||||
this.account = account;
|
||||
this.toggleGroup = toggleGroup;
|
||||
|
||||
StringBuilder subtitleString = new StringBuilder(Accounts.getAccountTypeName(account));
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
|
||||
subtitleString.append(", ").append(i18n("account.injector.server")).append(": ").append(server.getName());
|
||||
}
|
||||
|
||||
if (account instanceof OfflineAccount)
|
||||
title.set(account.getCharacter());
|
||||
else
|
||||
title.set(account.getUsername() + " - " + account.getCharacter());
|
||||
subtitle.set(subtitleString.toString());
|
||||
selected.set(Accounts.selectedAccountProperty().get() == account);
|
||||
|
||||
viewport.set(AccountHelper.getViewport(4));
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
this.image.set(image);
|
||||
} else {
|
||||
this.image.set(AccountHelper.getDefaultSkin(account.getUUID(), 4));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new AccountListItemSkin(this);
|
||||
}
|
||||
|
||||
public ToggleGroup getToggleGroup() {
|
||||
return toggleGroup;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public ObjectProperty<Rectangle2D> viewportProperty() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
// progressBar.setVisible(true);
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
// progressBar.setVisible(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
this.image.set(image);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
Accounts.getAccounts().remove(account);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
|
||||
public AccountListItemSkin(AccountListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.setUserData(skinnable);
|
||||
chkSelected.selectedProperty().bindBidirectional(skinnable.selectedProperty());
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageView.viewportProperty().bind(skinnable.viewportProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRefresh, i18n("button.refresh")));
|
||||
right.getChildren().add(btnRefresh);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
btnRemove.setOnMouseClicked(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRemove, i18n("button.delete")));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
+15
-11
@@ -15,11 +15,10 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -31,7 +30,6 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
@@ -41,11 +39,9 @@ import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
@@ -55,9 +51,7 @@ import java.util.logging.Level;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
@@ -87,6 +81,9 @@ public class AddAccountPane extends StackPane {
|
||||
cboType.setConverter(stringConverter(Accounts::getAccountTypeName));
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer));
|
||||
checkIfNoServer();
|
||||
|
||||
ReadOnlyObjectProperty<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty();
|
||||
|
||||
txtPassword.visibleProperty().bind(loginType.isNotEqualTo(Accounts.FACTORY_OFFLINE));
|
||||
@@ -119,6 +116,13 @@ public class AddAccountPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfNoServer() {
|
||||
if (cboServers.getItems().isEmpty())
|
||||
cboServers.getStyleClass().setAll("jfx-combo-box-warning");
|
||||
else
|
||||
cboServers.getStyleClass().setAll("jfx-combo-box");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}.
|
||||
*/
|
||||
+11
-13
@@ -15,12 +15,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -30,16 +33,11 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
|
||||
+2
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
@@ -25,6 +25,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
+13
-14
@@ -15,29 +15,28 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
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 {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.manage.title"));
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.injector.manage.title"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private VBox listPane;
|
||||
@@ -68,8 +67,8 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -20,14 +20,11 @@ package org.jackhuang.hmcl.ui.animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.scene.Node;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -52,30 +49,35 @@ public enum ContainerAnimations {
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_IN(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
|
||||
@@ -86,13 +88,16 @@ public enum ContainerAnimations {
|
||||
ZOOM_OUT(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
(Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
|
||||
@@ -104,9 +109,11 @@ public enum ContainerAnimations {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
@@ -122,9 +129,11 @@ public enum ContainerAnimations {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
|
||||
@@ -42,7 +42,7 @@ public class AdvancedListBox extends ScrollPane {
|
||||
}
|
||||
|
||||
public AdvancedListBox add(Node child) {
|
||||
if (child instanceof Pane)
|
||||
if (child instanceof Pane || child instanceof AdvancedListItem)
|
||||
container.getChildren().add(child);
|
||||
else {
|
||||
StackPane pane = new StackPane();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -17,55 +17,60 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public final class AdvancedListItem extends StackPane {
|
||||
@FXML
|
||||
private StackPane imageViewContainer;
|
||||
@FXML
|
||||
private Label lblTitle;
|
||||
@FXML
|
||||
private Label lblSubtitle;
|
||||
@FXML
|
||||
private ImageView imageView;
|
||||
@FXML private JFXButton btnSettings;
|
||||
public class AdvancedListItem extends Control {
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>();
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
|
||||
public AdvancedListItem(String title) {
|
||||
this(title, "");
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public AdvancedListItem(String title, String subtitle) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/advanced-list-item.fxml");
|
||||
|
||||
lblTitle.setText(title);
|
||||
lblSubtitle.setText(subtitle);
|
||||
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
public ObjectProperty<Rectangle2D> viewportProperty() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void setOnSettingsButtonClicked(EventHandler<? super MouseEvent> handler) {
|
||||
btnSettings.setOnMouseClicked(handler);
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
lblTitle.setText(title);
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
lblSubtitle.setText(subtitle);
|
||||
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
|
||||
return onAction;
|
||||
}
|
||||
|
||||
public void setImage(Image image, Rectangle2D viewport) {
|
||||
imageView.setImage(image);
|
||||
imageView.setViewport(viewport);
|
||||
public final void setOnAction(EventHandler<ActionEvent> value) {
|
||||
onActionProperty().set(value);
|
||||
}
|
||||
|
||||
public final EventHandler<ActionEvent> getOnAction() {
|
||||
return onActionProperty().get();
|
||||
}
|
||||
|
||||
private ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<EventHandler<ActionEvent>>(this, "onAction") {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
setEventHandler(ActionEvent.ACTION, get());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new AdvancedListItemSkin(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
|
||||
public AdvancedListItemSkin(AdvancedListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPickOnBounds(false);
|
||||
|
||||
HBox left = new HBox();
|
||||
left.setAlignment(Pos.CENTER);
|
||||
left.setMouseTransparent(true);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageView.viewportProperty().bind(skinnable.viewportProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||
vbox.setPadding(new Insets(0, 0, 0, 10));
|
||||
|
||||
Label title = new Label();
|
||||
title.textProperty().bind(skinnable.titleProperty());
|
||||
title.setMaxWidth(90);
|
||||
title.setStyle("-fx-font-size: 15;");
|
||||
title.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
vbox.getChildren().add(title);
|
||||
|
||||
Label subtitle = new Label();
|
||||
subtitle.textProperty().bind(skinnable.subtitleProperty());
|
||||
subtitle.setMaxWidth(90);
|
||||
subtitle.setStyle("-fx-font-size: 10;");
|
||||
subtitle.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
vbox.getChildren().add(subtitle);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.subtitleProperty(), subtitleString -> {
|
||||
if (subtitleString == null) vbox.getChildren().setAll(title);
|
||||
else vbox.getChildren().setAll(title, subtitle);
|
||||
});
|
||||
|
||||
left.getChildren().setAll(imageViewContainer, vbox);
|
||||
root.setLeft(left);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER);
|
||||
right.setPickOnBounds(false);
|
||||
|
||||
JFXButton settings = new JFXButton();
|
||||
FXUtils.setLimitWidth(settings, 40);
|
||||
settings.getStyleClass().setAll("toggle-icon4");
|
||||
settings.setGraphic(SVG.dotsVertical(Theme.blackFillBinding(), -1, -1));
|
||||
right.getChildren().setAll(settings);
|
||||
root.setRight(right);
|
||||
|
||||
stackPane.setStyle("-fx-padding: 10 16 10 16;");
|
||||
stackPane.getStyleClass().setAll("transparent");
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getChildren().setAll(root);
|
||||
|
||||
getChildren().setAll(stackPane);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
@@ -26,12 +27,15 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class ComponentList extends StackPane {
|
||||
private final VBox vbox = new VBox();
|
||||
public class ComponentList extends Control {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", "Group");
|
||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle", "");
|
||||
private final IntegerProperty depth = new SimpleIntegerProperty(this, "depth", 0);
|
||||
@@ -39,31 +43,9 @@ public class ComponentList extends StackPane {
|
||||
public final ObservableList<Node> content = FXCollections.observableArrayList();
|
||||
|
||||
public ComponentList() {
|
||||
getChildren().setAll(vbox);
|
||||
content.addListener((ListChangeListener<? super Node>) change -> {
|
||||
while (change.next()) {
|
||||
for (int i = change.getFrom(); i < change.getTo(); ++i)
|
||||
addChildren(change.getList().get(i));
|
||||
}
|
||||
});
|
||||
getStyleClass().add("options-list");
|
||||
}
|
||||
|
||||
public void addChildren(Node node) {
|
||||
StackPane child = new StackPane();
|
||||
child.getChildren().add(new ComponentListCell(node));
|
||||
if (vbox.getChildren().isEmpty())
|
||||
child.getStyleClass().add("options-list-item-ahead");
|
||||
else
|
||||
child.getStyleClass().add("options-list-item");
|
||||
child.getProperties().put("node", node);
|
||||
vbox.getChildren().add(child);
|
||||
}
|
||||
|
||||
public void removeChildren(Node node) {
|
||||
vbox.getChildren().removeIf(node1 -> node1.getProperties().get("node") == node);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
@@ -111,4 +93,30 @@ public class ComponentList extends StackPane {
|
||||
public ObservableList<Node> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected javafx.scene.control.Skin<?> createDefaultSkin() {
|
||||
return new Skin(this);
|
||||
}
|
||||
|
||||
protected static class Skin extends SkinBase<ComponentList> {
|
||||
private final ObservableList<Node> list;
|
||||
|
||||
protected Skin(ComponentList control) {
|
||||
super(control);
|
||||
|
||||
list = MappedObservableList.create(control.getContent(), this::mapper);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
Bindings.bindContent(vbox.getChildren(), list);
|
||||
getChildren().setAll(vbox);
|
||||
}
|
||||
|
||||
private Node mapper(Node node) {
|
||||
StackPane child = new StackPane();
|
||||
child.getChildren().add(new ComponentListCell(node));
|
||||
child.getStyleClass().add("options-list-item");
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,13 +40,13 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ComponentListCell extends StackPane {
|
||||
class ComponentListCell extends StackPane {
|
||||
private final Node content;
|
||||
private Animation expandAnimation;
|
||||
private Rectangle clipRect;
|
||||
private final BooleanProperty expanded = new SimpleBooleanProperty(this, "expanded", false);
|
||||
|
||||
public ComponentListCell(Node content) {
|
||||
ComponentListCell(Node content) {
|
||||
this.content = content;
|
||||
|
||||
updateLayout();
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventTarget;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
/**
|
||||
* Indicates a close operation on the dialog.
|
||||
@@ -32,7 +31,7 @@ import javafx.scene.layout.Region;
|
||||
*/
|
||||
public class DialogCloseEvent extends Event {
|
||||
|
||||
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("CLOSE");
|
||||
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("DIALOG_CLOSE");
|
||||
|
||||
public DialogCloseEvent() {
|
||||
super(CLOSE);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
@@ -28,19 +27,18 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class FileItem extends BorderPane {
|
||||
private final Label lblPath = new Label();
|
||||
|
||||
|
||||
@@ -19,16 +19,14 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
public class FontComboBox extends JFXComboBox<String> {
|
||||
private boolean loaded = false;
|
||||
|
||||
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize,
|
||||
@NamedArg(value = "enableStyle", defaultValue = "false") boolean enableStyle) {
|
||||
super(FXCollections.observableArrayList(Font.getFamilies()));
|
||||
|
||||
valueProperty().addListener((a, b, newValue) -> {
|
||||
if (enableStyle)
|
||||
setStyle("-fx-font-family: \"" + newValue + "\";");
|
||||
@@ -44,5 +42,16 @@ public class FontComboBox extends JFXComboBox<String> {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
if (loaded) return;
|
||||
getItems().setAll(Font.getFamilies());
|
||||
loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
public void initValue(Font font) {
|
||||
getItems().setAll(font.getFamily());
|
||||
getSelectionModel().select(font.getFamily());
|
||||
}
|
||||
}
|
||||
@@ -34,18 +34,22 @@ public class IconedItem extends RipplerContainer {
|
||||
public IconedItem(Node icon) {
|
||||
super(createHBox(icon));
|
||||
label = ((Label) lookup("#label"));
|
||||
getStyleClass().setAll("iconed-item");
|
||||
}
|
||||
|
||||
private static HBox createHBox(Node icon) {
|
||||
HBox hBox = new HBox();
|
||||
icon.setMouseTransparent(true);
|
||||
|
||||
if (icon != null) {
|
||||
icon.setMouseTransparent(true);
|
||||
hBox.getChildren().add(icon);
|
||||
}
|
||||
|
||||
hBox.getStyleClass().setAll("iconed-item-container");
|
||||
Label textLabel = new Label();
|
||||
textLabel.setId("label");
|
||||
textLabel.setAlignment(Pos.CENTER);
|
||||
textLabel.setMouseTransparent(true);
|
||||
hBox.getChildren().addAll(icon, textLabel);
|
||||
hBox.setStyle("-fx-padding: 10 16 10 16; -fx-spacing: 10; -fx-font-size: 14;");
|
||||
hBox.setAlignment(Pos.CENTER_LEFT);
|
||||
hBox.getChildren().addAll(textLabel);
|
||||
return hBox;
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -15,9 +15,16 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
public enum EnumUpdateChannel {
|
||||
STABLE,
|
||||
DEVELOPMENT
|
||||
import javafx.scene.Node;
|
||||
|
||||
public class IconedMenuItem extends IconedItem {
|
||||
|
||||
public IconedMenuItem(Node node, String text, Runnable action) {
|
||||
super(node, text);
|
||||
|
||||
getStyleClass().setAll("iconed-menu-item");
|
||||
setOnMouseClicked(e -> action.run());
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,20 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@DefaultProperty("image")
|
||||
public final class ImagePickerItem extends BorderPane {
|
||||
|
||||
private final ImageView imageView;
|
||||
private final JFXButton selectButton;
|
||||
private final Label label;
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final ObjectProperty<EventHandler<? super MouseEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
|
||||
private final ObjectProperty<EventHandler<? super MouseEvent>> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked");
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
|
||||
public ImagePickerItem() {
|
||||
@@ -38,21 +36,26 @@ public final class ImagePickerItem extends BorderPane {
|
||||
imageView.setSmooth(false);
|
||||
imageView.setPreserveRatio(true);
|
||||
|
||||
selectButton = new JFXButton();
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
|
||||
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
|
||||
deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked);
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
FXUtils.installTooltip(selectButton, i18n("button.edit"));
|
||||
|
||||
HBox hBox = new HBox();
|
||||
hBox.getChildren().setAll(imageView, selectButton);
|
||||
hBox.getChildren().setAll(imageView, selectButton, deleteButton);
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hBox.setSpacing(8);
|
||||
setRight(hBox);
|
||||
|
||||
VBox vBox = new VBox();
|
||||
label = new Label();
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(title);
|
||||
vBox.getChildren().setAll(label);
|
||||
vBox.setAlignment(Pos.CENTER_LEFT);
|
||||
@@ -85,6 +88,18 @@ public final class ImagePickerItem extends BorderPane {
|
||||
this.onSelectButtonClicked.set(onSelectButtonClicked);
|
||||
}
|
||||
|
||||
public EventHandler<? super MouseEvent> getOnDeleteButtonClicked() {
|
||||
return onDeleteButtonClicked.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<? super MouseEvent>> onDeleteButtonClickedProperty() {
|
||||
return onDeleteButtonClicked;
|
||||
}
|
||||
|
||||
public void setOnDeleteButtonClicked(EventHandler<? super MouseEvent> onDeleteButtonClicked) {
|
||||
this.onDeleteButtonClicked.set(onDeleteButtonClicked);
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
@@ -20,14 +20,13 @@ 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;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class MessageBox {
|
||||
private MessageBox() {
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -13,14 +13,14 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class MultiColorItem extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
|
||||
@@ -57,7 +57,7 @@ public class MultiColorItem extends ComponentList {
|
||||
|
||||
if (hasCustom)
|
||||
pane.getChildren().add(custom);
|
||||
addChildren(pane);
|
||||
getContent().add(pane);
|
||||
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
if (toggleSelectedListener != null)
|
||||
|
||||
@@ -34,18 +34,18 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class MultiFileItem<T> extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
|
||||
@@ -106,7 +106,7 @@ public class MultiFileItem<T> extends ComponentList {
|
||||
|
||||
if (hasCustom)
|
||||
pane.getChildren().add(custom);
|
||||
addChildren(pane);
|
||||
getContent().add(pane);
|
||||
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
if (toggleSelectedListener != null)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
public class Navigator extends StackPane {
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + ".closeListener";
|
||||
|
||||
private final Stack<Node> stack = new Stack<>();
|
||||
private final TransitionHandler animationHandler = new TransitionHandler(this);
|
||||
private final ReadOnlyBooleanWrapper canGoBack = new ReadOnlyBooleanWrapper();
|
||||
|
||||
public Navigator(Node init) {
|
||||
stack.push(init);
|
||||
getChildren().setAll(init);
|
||||
}
|
||||
|
||||
public void navigate(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Node from = stack.peek();
|
||||
if (from == node)
|
||||
return;
|
||||
|
||||
stack.push(node);
|
||||
fireEvent(new NavigationEvent(this, from, NavigationEvent.NAVIGATING));
|
||||
setContent(node);
|
||||
fireEvent(new NavigationEvent(this, node, NavigationEvent.NAVIGATED));
|
||||
|
||||
EventHandler<PageCloseEvent> handler = event -> close(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(PageCloseEvent.CLOSE, handler);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
close(stack.peek());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void close(Node from) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
stack.remove(from);
|
||||
Node node = stack.peek();
|
||||
fireEvent(new NavigationEvent(this, from, NavigationEvent.NAVIGATING));
|
||||
setContent(node);
|
||||
fireEvent(new NavigationEvent(this, node, NavigationEvent.NAVIGATED));
|
||||
|
||||
Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> from.removeEventHandler(PageCloseEvent.CLOSE, (EventHandler<PageCloseEvent>) handler));
|
||||
}
|
||||
|
||||
public Node getCurrentPage() {
|
||||
return stack.peek();
|
||||
}
|
||||
|
||||
public boolean canGoBack() {
|
||||
return stack.size() > 1;
|
||||
}
|
||||
|
||||
private void setContent(Node content) {
|
||||
animationHandler.setContent(content, ContainerAnimations.FADE.getAnimationProducer());
|
||||
|
||||
if (content instanceof Region) {
|
||||
((Region) content).setMinSize(0, 0);
|
||||
FXUtils.setOverflowHidden((Region) content, true);
|
||||
}
|
||||
}
|
||||
|
||||
public EventHandler<NavigationEvent> getOnNavigated() {
|
||||
return onNavigated.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<NavigationEvent>> onNavigatedProperty() {
|
||||
return onNavigated;
|
||||
}
|
||||
|
||||
public void setOnNavigated(EventHandler<NavigationEvent> onNavigated) {
|
||||
this.onNavigated.set(onNavigated);
|
||||
}
|
||||
|
||||
private ObjectProperty<EventHandler<NavigationEvent>> onNavigated = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigated") {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
setEventHandler(NavigationEvent.NAVIGATED, get());
|
||||
}
|
||||
};
|
||||
|
||||
public EventHandler<NavigationEvent> getOnNavigating() {
|
||||
return onNavigating.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<NavigationEvent>> onNavigatingProperty() {
|
||||
return onNavigating;
|
||||
}
|
||||
|
||||
public void setOnNavigating(EventHandler<NavigationEvent> onNavigating) {
|
||||
this.onNavigating.set(onNavigating);
|
||||
}
|
||||
|
||||
private ObjectProperty<EventHandler<NavigationEvent>> onNavigating = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigating") {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
setEventHandler(NavigationEvent.NAVIGATING, get());
|
||||
}
|
||||
};
|
||||
|
||||
public static class NavigationEvent extends Event {
|
||||
public static final EventType<NavigationEvent> NAVIGATED = new EventType<>("NAVIGATED");
|
||||
public static final EventType<NavigationEvent> NAVIGATING = new EventType<>("NAVIGATING");
|
||||
|
||||
private final Node node;
|
||||
|
||||
public NavigationEvent(Object source, Node target, EventType<? extends Event> eventType) {
|
||||
super(source, target, eventType);
|
||||
|
||||
this.node = target;
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-13
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,26 +15,27 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultGameBuilder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventTarget;
|
||||
import javafx.event.EventType;
|
||||
|
||||
/**
|
||||
* Indicates a close operation on the navigator page.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class HMCLGameBuilder extends DefaultGameBuilder {
|
||||
private final Profile profile;
|
||||
public class PageCloseEvent extends Event {
|
||||
|
||||
public HMCLGameBuilder(Profile profile) {
|
||||
super(profile.getDependency());
|
||||
public static final EventType<PageCloseEvent> CLOSE = new EventType<>("PAGE_CLOSE");
|
||||
|
||||
this.profile = profile;
|
||||
public PageCloseEvent() {
|
||||
super(CLOSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task downloadGameAsync(String gameVersion, Version version) {
|
||||
return new HMCLGameDownloadTask(profile, gameVersion, version);
|
||||
public PageCloseEvent(Object source, EventTarget target) {
|
||||
super(source, target, CLOSE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -42,6 +43,8 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
@FXML
|
||||
private Label lblSubtitle;
|
||||
@FXML
|
||||
private Label lblProgress;
|
||||
@FXML
|
||||
private JFXButton btnCancel;
|
||||
@FXML
|
||||
private TaskListPane taskListPane;
|
||||
@@ -55,6 +58,11 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
|
||||
onCancel.accept(this);
|
||||
});
|
||||
|
||||
lblProgress.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> taskListPane.finishedTasksProperty().get() + "/" + taskListPane.totTasksProperty().get(),
|
||||
taskListPane.finishedTasksProperty(), taskListPane.totTasksProperty()
|
||||
));
|
||||
}
|
||||
|
||||
public void setExecutor(TaskExecutor executor) {
|
||||
|
||||
@@ -19,14 +19,14 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetRefreshTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
@@ -35,14 +35,17 @@ import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class TaskListPane extends StackPane {
|
||||
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() {
|
||||
listBox.setSpacing(0);
|
||||
@@ -50,11 +53,28 @@ public final class TaskListPane extends StackPane {
|
||||
getChildren().setAll(listBox);
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty finishedTasksProperty() {
|
||||
return finishedTasks.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty totTasksProperty() {
|
||||
return totTasks.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setExecutor(TaskExecutor executor) {
|
||||
executor.addTaskListener(new TaskListener() {
|
||||
@Override
|
||||
public void onStart() {
|
||||
Platform.runLater(listBox::clear);
|
||||
Platform.runLater(() -> {
|
||||
listBox.clear();
|
||||
finishedTasks.set(0);
|
||||
totTasks.set(0);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(Task task) {
|
||||
Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,9 +82,7 @@ public final class TaskListPane extends StackPane {
|
||||
if (!task.getSignificance().shouldShow())
|
||||
return;
|
||||
|
||||
if (task instanceof GameAssetRefreshTask) {
|
||||
task.setName(i18n("assets.download"));
|
||||
} else if (task instanceof GameAssetDownloadTask) {
|
||||
if (task instanceof GameAssetDownloadTask) {
|
||||
task.setName(i18n("assets.download_all"));
|
||||
} else if (task instanceof ForgeInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
@@ -100,7 +118,10 @@ public final class TaskListPane extends StackPane {
|
||||
if (node == null)
|
||||
return;
|
||||
node.unbind();
|
||||
Platform.runLater(() -> listBox.remove(node));
|
||||
Platform.runLater(() -> {
|
||||
listBox.remove(node);
|
||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +129,10 @@ public final class TaskListPane extends StackPane {
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
Platform.runLater(() -> node.setThrowable(throwable));
|
||||
Platform.runLater(() -> {
|
||||
node.setThrowable(throwable);
|
||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
public class DecoratorControl extends Control {
|
||||
private final ListProperty<Node> drawer = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty drawerTitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty closeNavButtonVisible = new SimpleBooleanProperty(true);
|
||||
private final BooleanProperty canRefresh = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty canBack = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty canClose = new SimpleBooleanProperty(false);
|
||||
private final Stage primaryStage;
|
||||
private StackPane drawerWrapper;
|
||||
|
||||
public DecoratorControl(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
}
|
||||
|
||||
public Stage getPrimaryStage() {
|
||||
return primaryStage;
|
||||
}
|
||||
|
||||
public StackPane getDrawerWrapper() {
|
||||
return drawerWrapper;
|
||||
}
|
||||
|
||||
void setDrawerWrapper(StackPane drawerWrapper) {
|
||||
this.drawerWrapper = drawerWrapper;
|
||||
}
|
||||
|
||||
public ObservableList<Node> getDrawer() {
|
||||
return drawer.get();
|
||||
}
|
||||
|
||||
public ListProperty<Node> drawerProperty() {
|
||||
return drawer;
|
||||
}
|
||||
|
||||
public void setDrawer(ObservableList<Node> drawer) {
|
||||
this.drawer.set(drawer);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContent() {
|
||||
return content.get();
|
||||
}
|
||||
|
||||
public ListProperty<Node> contentProperty() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(ObservableList<Node> content) {
|
||||
this.content.set(content);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getDrawerTitle() {
|
||||
return drawerTitle.get();
|
||||
}
|
||||
|
||||
public StringProperty drawerTitleProperty() {
|
||||
return drawerTitle;
|
||||
}
|
||||
|
||||
public void setDrawerTitle(String drawerTitle) {
|
||||
this.drawerTitle.set(drawerTitle);
|
||||
}
|
||||
|
||||
public Runnable getOnCloseButtonAction() {
|
||||
return onCloseButtonAction.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
||||
return onCloseButtonAction;
|
||||
}
|
||||
|
||||
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
||||
this.onCloseButtonAction.set(onCloseButtonAction);
|
||||
}
|
||||
|
||||
public boolean isCloseNavButtonVisible() {
|
||||
return closeNavButtonVisible.get();
|
||||
}
|
||||
|
||||
public BooleanProperty closeNavButtonVisibleProperty() {
|
||||
return closeNavButtonVisible;
|
||||
}
|
||||
|
||||
public void setCloseNavButtonVisible(boolean closeNavButtonVisible) {
|
||||
this.closeNavButtonVisible.set(closeNavButtonVisible);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContainer() {
|
||||
return container.get();
|
||||
}
|
||||
|
||||
public ListProperty<Node> containerProperty() {
|
||||
return container;
|
||||
}
|
||||
|
||||
public void setContainer(ObservableList<Node> container) {
|
||||
this.container.set(container);
|
||||
}
|
||||
|
||||
public Background getContentBackground() {
|
||||
return contentBackground.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Background> contentBackgroundProperty() {
|
||||
return contentBackground;
|
||||
}
|
||||
|
||||
public void setContentBackground(Background contentBackground) {
|
||||
this.contentBackground.set(contentBackground);
|
||||
}
|
||||
|
||||
public BooleanProperty canRefreshProperty() {
|
||||
return canRefresh;
|
||||
}
|
||||
|
||||
public BooleanProperty canBackProperty() {
|
||||
return canBack;
|
||||
}
|
||||
|
||||
public BooleanProperty canCloseProperty() {
|
||||
return canClose;
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonActionProperty() {
|
||||
return onBackNavButtonAction;
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonActionProperty() {
|
||||
return onCloseNavButtonAction;
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonActionProperty() {
|
||||
return onRefreshNavButtonAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new DecoratorSkin(this);
|
||||
}
|
||||
|
||||
public void minimize() {
|
||||
primaryStage.setIconified(true);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
onCloseButtonAction.get().run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
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.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class DecoratorController {
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final DecoratorControl decorator;
|
||||
private final ImageView welcomeView;
|
||||
private final Navigator navigator;
|
||||
private final Node mainPage;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
this.mainPage = mainPage;
|
||||
|
||||
decorator = new DecoratorControl(stage);
|
||||
decorator.titleProperty().set(Metadata.TITLE);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
|
||||
navigator = new Navigator(mainPage);
|
||||
navigator.setOnNavigating(this::onNavigating);
|
||||
navigator.setOnNavigated(this::onNavigated);
|
||||
|
||||
decorator.getContent().setAll(navigator);
|
||||
decorator.onCloseNavButtonActionProperty().set(e -> close());
|
||||
decorator.onBackNavButtonActionProperty().set(e -> back());
|
||||
decorator.onRefreshNavButtonActionProperty().set(e -> refresh());
|
||||
|
||||
welcomeView = new ImageView();
|
||||
welcomeView.setImage(new Image("/assets/img/welcome.png"));
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(welcomeView.opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), new KeyValue(welcomeView.opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), e2 -> decorator.getContainer().remove(welcomeView))
|
||||
);
|
||||
nowAnimation.play();
|
||||
});
|
||||
if (ConfigHolder.isNewlyCreated() && config().getLocalization().getLocale() == Locale.CHINA)
|
||||
decorator.getContainer().setAll(welcomeView);
|
||||
|
||||
setupBackground();
|
||||
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
public DecoratorControl getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
// ==== Background ====
|
||||
|
||||
private void setupBackground() {
|
||||
decorator.backgroundProperty().bind(
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
Image image = null;
|
||||
if (config().getBackgroundImageType() == EnumBackgroundImage.CUSTOM && config().getBackgroundImage() != null) {
|
||||
image = tryLoadImage(Paths.get(config().getBackgroundImage()))
|
||||
.orElse(null);
|
||||
}
|
||||
if (image == null) {
|
||||
image = loadDefaultBackgroundImage();
|
||||
}
|
||||
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
|
||||
},
|
||||
config().backgroundImageTypeProperty(),
|
||||
config().backgroundImageProperty()));
|
||||
}
|
||||
|
||||
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
||||
|
||||
/**
|
||||
* Load background image from bg/, background.png, background.jpg
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.png"));
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.jpg"));
|
||||
}
|
||||
return image.orElse(defaultBackground);
|
||||
}
|
||||
|
||||
private Optional<Image> randomImageIn(Path imageDir) {
|
||||
if (!Files.isDirectory(imageDir)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
try {
|
||||
candidates = Files.list(imageDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(it -> {
|
||||
String filename = it.getFileName().toString();
|
||||
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
while (candidates.size() > 0) {
|
||||
int selected = rnd.nextInt(candidates.size());
|
||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded.isPresent()) {
|
||||
return loaded;
|
||||
} else {
|
||||
candidates.remove(selected);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// ==== Navigation ====
|
||||
|
||||
public Navigator getNavigator() {
|
||||
return navigator;
|
||||
}
|
||||
|
||||
private void close() {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
page.onForceToClose();
|
||||
} else {
|
||||
navigator.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void back() {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
if (page.onClose())
|
||||
navigator.close();
|
||||
} else {
|
||||
navigator.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
if (navigator.getCurrentPage() instanceof Refreshable) {
|
||||
Refreshable refreshable = (Refreshable) navigator.getCurrentPage();
|
||||
|
||||
if (refreshable.canRefreshProperty().get())
|
||||
refreshable.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void onNavigating(Navigator.NavigationEvent event) {
|
||||
Node from = event.getNode();
|
||||
|
||||
if (from instanceof DecoratorPage)
|
||||
((DecoratorPage) from).onClose();
|
||||
}
|
||||
|
||||
private void onNavigated(Navigator.NavigationEvent event) {
|
||||
Node to = event.getNode();
|
||||
|
||||
if (to instanceof Refreshable) {
|
||||
decorator.canRefreshProperty().bind(((Refreshable) to).canRefreshProperty());
|
||||
} else {
|
||||
decorator.canRefreshProperty().unbind();
|
||||
decorator.canRefreshProperty().set(false);
|
||||
}
|
||||
|
||||
if (to instanceof DecoratorPage) {
|
||||
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
||||
decorator.canCloseProperty().set(((DecoratorPage) to).canForceToClose());
|
||||
} else {
|
||||
decorator.drawerTitleProperty().unbind();
|
||||
decorator.drawerTitleProperty().set("");
|
||||
decorator.canCloseProperty().set(false);
|
||||
}
|
||||
|
||||
decorator.canBackProperty().set(navigator.canGoBack());
|
||||
|
||||
if (navigator.canGoBack()) {
|
||||
decorator.setContentBackground(new Background(new BackgroundFill(Color.rgb(244, 244, 244, 0.5), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
} else {
|
||||
decorator.setContentBackground(null);
|
||||
}
|
||||
|
||||
if (to instanceof Region) {
|
||||
Region region = (Region) to;
|
||||
// Let root pane fix window size.
|
||||
StackPane parent = (StackPane) region.getParent();
|
||||
region.prefWidthProperty().bind(parent.widthProperty());
|
||||
region.prefHeightProperty().bind(parent.heightProperty());
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Dialog ====
|
||||
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialog == null) {
|
||||
if (decorator.getDrawerWrapper() == null) {
|
||||
// Sometimes showDialog will be invoked before decorator was initialized.
|
||||
// Keep trying again.
|
||||
Platform.runLater(() -> showDialog(node));
|
||||
return;
|
||||
}
|
||||
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
dialog.setDialogContainer(decorator.getDrawerWrapper());
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
dialogPane.push(node);
|
||||
|
||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
if (dialog != null) {
|
||||
dialogPane.pop(node);
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void showDialog(String text) {
|
||||
showDialog(text, null);
|
||||
}
|
||||
|
||||
public void showDialog(String text, String title) {
|
||||
showDialog(text, title, MessageBox.INFORMATION_MESSAGE);
|
||||
}
|
||||
|
||||
public void showDialog(String text, String title, int type) {
|
||||
showDialog(text, title, type, null);
|
||||
}
|
||||
|
||||
public void showDialog(String text, String title, int type, Runnable onAccept) {
|
||||
showDialog(new MessageDialogPane(text, title, type, onAccept));
|
||||
}
|
||||
|
||||
public void showConfirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
showDialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public InputDialogPane showInputDialog(String text, FutureCallback<String> onResult) {
|
||||
InputDialogPane pane = new InputDialogPane(text, onResult);
|
||||
showDialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public Region showTaskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
return showTaskDialog(executor, title, subtitle, null);
|
||||
}
|
||||
|
||||
public Region showTaskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setSubtitle(subtitle);
|
||||
pane.setExecutor(executor);
|
||||
showDialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
// ==== Wizard ====
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
startWizard(wizardProvider, null);
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category));
|
||||
}
|
||||
|
||||
// ==== Authlib Injector DnD ====
|
||||
|
||||
private void setupAuthlibInjectorDnD() {
|
||||
decorator.addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
|
||||
decorator.addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
|
||||
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
|
||||
}
|
||||
}
|
||||
+15
-7
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -15,13 +15,21 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
public class CurseCDNDownloadProvider extends MojangDownloadProvider {
|
||||
public static final CurseCDNDownloadProvider INSTANCE = new CurseCDNDownloadProvider();
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
return baseURL == null ? null : baseURL.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://ftb.cursecdn.com/FTB2/maven");
|
||||
public interface DecoratorPage {
|
||||
ReadOnlyStringProperty titleProperty();
|
||||
|
||||
default boolean canForceToClose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean onClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default void onForceToClose() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.BoundingBox;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
|
||||
private final BorderPane titleContainer;
|
||||
private final StackPane contentPlaceHolder;
|
||||
private final JFXButton refreshNavButton;
|
||||
private final JFXButton closeNavButton;
|
||||
private final HBox navLeft;
|
||||
private final Stage primaryStage;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
*
|
||||
* @param control The control for which this Skin should attach to.
|
||||
*/
|
||||
public DecoratorSkin(DecoratorControl control) {
|
||||
super(control);
|
||||
|
||||
primaryStage = control.getPrimaryStage();
|
||||
|
||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
|
||||
DecoratorControl skinnable = getSkinnable();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().setAll("jfx-decorator", "resize-border");
|
||||
root.setMaxHeight(519);
|
||||
root.setMaxWidth(800);
|
||||
|
||||
StackPane drawerWrapper = new StackPane();
|
||||
skinnable.setDrawerWrapper(drawerWrapper);
|
||||
drawerWrapper.getStyleClass().setAll("jfx-decorator-drawer");
|
||||
drawerWrapper.backgroundProperty().bind(skinnable.backgroundProperty());
|
||||
FXUtils.setOverflowHidden(drawerWrapper, true);
|
||||
{
|
||||
BorderPane drawer = new BorderPane();
|
||||
|
||||
{
|
||||
BorderPane leftRootPane = new BorderPane();
|
||||
FXUtils.setLimitWidth(leftRootPane, 200);
|
||||
leftRootPane.getStyleClass().setAll("jfx-decorator-content-container");
|
||||
|
||||
StackPane drawerContainer = new StackPane();
|
||||
drawerContainer.getStyleClass().setAll("gray-background");
|
||||
Bindings.bindContent(drawerContainer.getChildren(), skinnable.drawerProperty());
|
||||
leftRootPane.setCenter(drawerContainer);
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.heightProperty().bind(drawer.heightProperty());
|
||||
separator.setWidth(1);
|
||||
separator.setFill(Color.GRAY);
|
||||
|
||||
leftRootPane.setRight(separator);
|
||||
|
||||
drawer.setLeft(leftRootPane);
|
||||
}
|
||||
|
||||
{
|
||||
contentPlaceHolder = new StackPane();
|
||||
contentPlaceHolder.getStyleClass().setAll("jfx-decorator-content-container");
|
||||
contentPlaceHolder.backgroundProperty().bind(skinnable.contentBackgroundProperty());
|
||||
FXUtils.setOverflowHidden(contentPlaceHolder, true);
|
||||
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
||||
|
||||
drawer.setCenter(contentPlaceHolder);
|
||||
}
|
||||
|
||||
drawerWrapper.getChildren().add(drawer);
|
||||
}
|
||||
|
||||
{
|
||||
StackPane container = new StackPane();
|
||||
Bindings.bindContent(container.getChildren(), skinnable.containerProperty());
|
||||
ListChangeListener<Node> listener = new ListChangeListener<Node>() {
|
||||
@Override
|
||||
public void onChanged(Change<? extends Node> c) {
|
||||
if (skinnable.getContainer().isEmpty()) {
|
||||
container.setMouseTransparent(true);
|
||||
container.setVisible(false);
|
||||
} else {
|
||||
container.setMouseTransparent(false);
|
||||
container.setVisible(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
skinnable.containerProperty().addListener(listener);
|
||||
listener.onChanged(null);
|
||||
|
||||
drawerWrapper.getChildren().add(container);
|
||||
}
|
||||
|
||||
root.setCenter(drawerWrapper);
|
||||
|
||||
titleContainer = new BorderPane();
|
||||
titleContainer.setOnMouseReleased(this::onMouseReleased);
|
||||
titleContainer.setOnMouseDragged(this::onMouseDragged);
|
||||
titleContainer.setOnMouseMoved(this::onMouseMoved);
|
||||
titleContainer.setPickOnBounds(false);
|
||||
titleContainer.setMinHeight(40);
|
||||
titleContainer.getStyleClass().setAll("jfx-tool-bar");
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
||||
if (!isDragging) allowMove = false;
|
||||
});
|
||||
|
||||
Rectangle rectangle = new Rectangle(0, 0, 0, 0);
|
||||
rectangle.widthProperty().bind(titleContainer.widthProperty());
|
||||
rectangle.heightProperty().bind(Bindings.createDoubleBinding(() -> titleContainer.getHeight() + 100, titleContainer.heightProperty()));
|
||||
titleContainer.setClip(rectangle);
|
||||
{
|
||||
BorderPane titleWrapper = new BorderPane();
|
||||
FXUtils.setLimitWidth(titleWrapper, 200);
|
||||
{
|
||||
Label lblTitle = new Label();
|
||||
BorderPane.setMargin(lblTitle, new Insets(0, 0, 0, 3));
|
||||
lblTitle.setStyle("-fx-background-color: transparent; -fx-text-fill: -fx-base-text-fill; -fx-font-size: 15px;");
|
||||
lblTitle.setMouseTransparent(true);
|
||||
lblTitle.textProperty().bind(skinnable.titleProperty());
|
||||
BorderPane.setAlignment(lblTitle, Pos.CENTER);
|
||||
titleWrapper.setCenter(lblTitle);
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.heightProperty().bind(titleWrapper.heightProperty());
|
||||
separator.setWidth(1);
|
||||
separator.setFill(Color.GRAY);
|
||||
titleWrapper.setRight(separator);
|
||||
}
|
||||
titleContainer.setLeft(titleWrapper);
|
||||
|
||||
BorderPane navBar = new BorderPane();
|
||||
{
|
||||
navLeft = new HBox();
|
||||
navLeft.setAlignment(Pos.CENTER_LEFT);
|
||||
navLeft.setPadding(new Insets(0, 5, 0, 5));
|
||||
{
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.getStyleClass().setAll("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.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
|
||||
navLeft.getChildren().setAll(backNavButton);
|
||||
|
||||
skinnable.canCloseProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue) navLeft.getChildren().setAll(backNavButton, closeNavButton);
|
||||
else navLeft.getChildren().setAll(backNavButton);
|
||||
});
|
||||
}
|
||||
navBar.setLeft(navLeft);
|
||||
|
||||
VBox navCenter = new VBox();
|
||||
navCenter.setAlignment(Pos.CENTER_LEFT);
|
||||
Label titleLabel = new Label();
|
||||
titleLabel.getStyleClass().setAll("jfx-decorator-title");
|
||||
titleLabel.textProperty().bind(skinnable.drawerTitleProperty());
|
||||
navCenter.getChildren().setAll(titleLabel);
|
||||
navBar.setCenter(navCenter);
|
||||
|
||||
HBox navRight = new HBox();
|
||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||
refreshNavButton = new JFXButton();
|
||||
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||
refreshNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
refreshNavButton.visibleProperty().bind(skinnable.canRefreshProperty());
|
||||
navRight.getChildren().setAll(refreshNavButton);
|
||||
navBar.setRight(navRight);
|
||||
}
|
||||
titleContainer.setCenter(navBar);
|
||||
|
||||
HBox buttonsContainer = new HBox();
|
||||
buttonsContainer.setStyle("-fx-background-color: transparent;");
|
||||
buttonsContainer.setAlignment(Pos.CENTER_RIGHT);
|
||||
buttonsContainer.setPadding(new Insets(4));
|
||||
{
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
StackPane pane = new StackPane(minus);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
btnMin.setGraphic(pane);
|
||||
btnMin.getStyleClass().setAll("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.setOnAction(e -> skinnable.close());
|
||||
|
||||
buttonsContainer.getChildren().setAll(separator, btnMin, btnClose);
|
||||
}
|
||||
titleContainer.setRight(buttonsContainer);
|
||||
}
|
||||
root.setTop(titleContainer);
|
||||
|
||||
getChildren().setAll(root);
|
||||
|
||||
getSkinnable().closeNavButtonVisibleProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue) navLeft.getChildren().add(closeNavButton);
|
||||
else navLeft.getChildren().remove(closeNavButton);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
||||
initX = mouseEvent.getScreenX();
|
||||
initY = mouseEvent.getScreenY();
|
||||
xOffset = mouseEvent.getSceneX();
|
||||
yOffset = mouseEvent.getSceneY();
|
||||
}
|
||||
|
||||
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x < getSkinnable().getWidth() && x > getSkinnable().getWidth() - contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
|
||||
return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
|
||||
return y < getSkinnable().getHeight() && y > getSkinnable().getHeight() - contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean setStageWidth(double width) {
|
||||
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
||||
primaryStage.setWidth(width);
|
||||
initX = newX;
|
||||
return true;
|
||||
} else {
|
||||
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
||||
primaryStage.setWidth(titleContainer.getMinWidth());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setStageHeight(double height) {
|
||||
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
||||
primaryStage.setHeight(height);
|
||||
initY = newY;
|
||||
return true;
|
||||
} else {
|
||||
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
||||
primaryStage.setHeight(titleContainer.getHeight());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
protected void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isFullScreen()) {
|
||||
if (!primaryStage.isResizable())
|
||||
updateInitMouseValues(mouseEvent);
|
||||
else {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = getSkinnable().getBoundsInParent();
|
||||
if (getSkinnable().getBorder() != null && getSkinnable().getBorder().getStrokes().size() > 0) {
|
||||
double borderWidth = contentPlaceHolder.snappedLeftInset();
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
getSkinnable().setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > getSkinnable().getHeight() - borderWidth) {
|
||||
getSkinnable().setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
getSkinnable().setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
getSkinnable().setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > getSkinnable().getHeight() - borderWidth) {
|
||||
getSkinnable().setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
getSkinnable().setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
getSkinnable().setCursor(Cursor.N_RESIZE);
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
getSkinnable().setCursor(Cursor.S_RESIZE);
|
||||
} else {
|
||||
getSkinnable().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
this.updateInitMouseValues(mouseEvent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getSkinnable().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMouseReleased(MouseEvent mouseEvent) {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
protected void onMouseDragged(MouseEvent mouseEvent) {
|
||||
this.isDragging = true;
|
||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) {
|
||||
this.newX = mouseEvent.getScreenX();
|
||||
this.newY = mouseEvent.getScreenY();
|
||||
double deltaX = this.newX - this.initX;
|
||||
double deltaY = this.newY - this.initY;
|
||||
Cursor cursor = getSkinnable().getCursor();
|
||||
if (Cursor.E_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NE_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SE_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.S_RESIZE == cursor) {
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.W_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.N_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (this.allowMove) {
|
||||
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
||||
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorDialogWizardDisplayer, Refreshable, DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final BooleanProperty canRefresh = new SimpleBooleanProperty();
|
||||
|
||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final String category;
|
||||
|
||||
private Node nowPage;
|
||||
|
||||
public DecoratorWizardDisplayer(WizardProvider provider) {
|
||||
this(provider, null);
|
||||
}
|
||||
|
||||
public DecoratorWizardDisplayer(WizardProvider provider, String category) {
|
||||
this.category = category;
|
||||
|
||||
wizardController.setProvider(provider);
|
||||
wizardController.onStart();
|
||||
|
||||
getStyleClass().setAll("white-background");
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanProperty canRefreshProperty() {
|
||||
return canRefresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WizardController getWizardController() {
|
||||
return wizardController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queue<Object> getCancelQueue() {
|
||||
return cancelQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd() {
|
||||
fireEvent(new PageCloseEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
nowPage = page;
|
||||
|
||||
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
|
||||
canRefresh.set(page instanceof Refreshable);
|
||||
|
||||
String prefix = category == null ? "" : category + " - ";
|
||||
|
||||
if (page instanceof WizardPage)
|
||||
title.set(prefix + ((WizardPage) page).getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canForceToClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForceToClose() {
|
||||
wizardController.onCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClose() {
|
||||
if (wizardController.canPrev()) {
|
||||
wizardController.onPrev(true);
|
||||
return false;
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
((Refreshable) nowPage).refresh();
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
@@ -30,11 +29,12 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
private final InstallerWizardProvider provider;
|
||||
private final WizardController controller;
|
||||
|
||||
@@ -18,18 +18,18 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,17 +38,23 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class DownloadWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
private final int type;
|
||||
|
||||
public DownloadWizardProvider(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
profile = Settings.instance().getSelectedProfile();
|
||||
profile = Profiles.getSelectedProfile();
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||
GameBuilder builder = profile.getDependency().gameBuilder();
|
||||
|
||||
builder.name((String) settings.get("name"));
|
||||
String name = (String) settings.get("name");
|
||||
builder.name(name);
|
||||
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
@@ -60,7 +66,8 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version((RemoteVersion) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions());
|
||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions())
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
@@ -72,7 +79,8 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack);
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +88,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
switch (Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1)) {
|
||||
switch (type) {
|
||||
case 0: return finishVersionDownloadingAsync(settings);
|
||||
case 1: return finishModpackInstallingAsync(settings);
|
||||
default: return null;
|
||||
@@ -92,16 +100,13 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new InstallTypePage(controller);
|
||||
case 1:
|
||||
int subStep = Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1);
|
||||
switch (subStep) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));
|
||||
case 1:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + subStep + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + type + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class InstallTypePage extends StackPane implements WizardPage {
|
||||
|
||||
@FXML private JFXListView<Object> list;
|
||||
|
||||
public InstallTypePage(WizardController controller) {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/dltype.fxml");
|
||||
list.setOnMouseClicked(e -> {
|
||||
if (list.getSelectionModel().getSelectedIndex() < 0)
|
||||
return;
|
||||
controller.getSettings().put(INSTALL_TYPE, list.getSelectionModel().getSelectedIndex());
|
||||
controller.onNext();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALL_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return i18n("install.select");
|
||||
}
|
||||
|
||||
public static final String INSTALL_TYPE = "INSTALL_TYPE";
|
||||
}
|
||||
@@ -18,19 +18,20 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
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.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class InstallerWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final String gameVersion;
|
||||
@@ -40,16 +41,14 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
private final String optiFine;
|
||||
|
||||
public InstallerWizardProvider(Profile profile, String gameVersion, Version version) {
|
||||
this(profile, gameVersion, version, null, null, null);
|
||||
}
|
||||
|
||||
public InstallerWizardProvider(Profile profile, String gameVersion, Version version, String forge, String liteLoader, String optiFine) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.forge = forge;
|
||||
this.liteLoader = liteLoader;
|
||||
this.optiFine = optiFine;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
|
||||
@@ -23,7 +23,6 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
@@ -32,10 +31,11 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallersPage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
@@ -37,11 +36,12 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackPage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) Platform.runLater(() -> Controllers.navigate(null));
|
||||
if (selectedFile == null) Platform.runLater(controller::onEnd);
|
||||
else {
|
||||
controller.getSettings().put(MODPACK_FILE, selectedFile);
|
||||
lblModpackLocation.setText(selectedFile.getAbsolutePath());
|
||||
|
||||
@@ -27,7 +27,6 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -39,7 +38,6 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class VersionsPage extends StackPane implements WizardPage, Refreshable {
|
||||
@@ -47,6 +45,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final String libraryId;
|
||||
private final String title;
|
||||
private final WizardController controller;
|
||||
|
||||
@FXML
|
||||
private JFXListView<VersionsPageItem> list;
|
||||
@@ -55,6 +54,8 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
@FXML
|
||||
private StackPane failedPane;
|
||||
@FXML
|
||||
private StackPane emptyPane;
|
||||
@FXML
|
||||
private JFXCheckBox chkRelease;
|
||||
@FXML
|
||||
private JFXCheckBox chkSnapshot;
|
||||
@@ -74,6 +75,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
this.gameVersion = gameVersion;
|
||||
this.downloadProvider = downloadProvider;
|
||||
this.libraryId = libraryId;
|
||||
this.controller = controller;
|
||||
this.versionList = downloadProvider.getVersionListById(libraryId);
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
@@ -123,8 +125,18 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
List<VersionsPageItem> items = loadVersions();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
list.getItems().setAll(items);
|
||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
if (versionList.getVersions(gameVersion).isEmpty()) {
|
||||
transitionHandler.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
} else {
|
||||
if (items.isEmpty()) {
|
||||
chkRelease.setSelected(true);
|
||||
chkSnapshot.setSelected(true);
|
||||
chkOld.setSelected(true);
|
||||
} else {
|
||||
list.getItems().setAll(items);
|
||||
}
|
||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
@@ -150,4 +162,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
private void onRefresh() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBack() { controller.onPrev(true); }
|
||||
}
|
||||
@@ -23,17 +23,17 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
|
||||
@@ -79,8 +79,6 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
if (includeLauncher) {
|
||||
dependency = dependency.then(Task.of(() -> {
|
||||
boolean flag = true;
|
||||
|
||||
try (Zipper zip = new Zipper(modpackFile.toPath())) {
|
||||
Config exported = new Config();
|
||||
exported.setBackgroundImageType(config().getBackgroundImageType());
|
||||
|
||||
@@ -25,7 +25,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.game.ModAdviser;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -34,6 +33,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,13 +33,14 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
@FXML
|
||||
@@ -86,7 +87,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
if (file == null) {
|
||||
Controllers.navigate(null);
|
||||
controller.onEnd();
|
||||
return;
|
||||
}
|
||||
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.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.ui.construct.AdvancedListItem;
|
||||
|
||||
public class ProfileAdvancedListItem extends AdvancedListItem {
|
||||
private ObjectProperty<Profile> profile = new SimpleObjectProperty<Profile>() {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Profile profile = get();
|
||||
if (profile == null) {
|
||||
} else {
|
||||
titleProperty().set(Profiles.getProfileDisplayName(profile));
|
||||
subtitleProperty().set(profile.getGameDir().toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ProfileAdvancedListItem() {
|
||||
imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
}
|
||||
|
||||
public ObjectProperty<Profile> profileProperty() {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ProfileList extends ListPage<ProfileListItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(i18n("profile.manage"));
|
||||
private ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
{
|
||||
itemsProperty().addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Profile selected = get();
|
||||
itemsProperty().forEach(item -> item.selectedProperty().set(item.getProfile() == selected));
|
||||
}
|
||||
};
|
||||
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
private final ObservableList<ProfileListItem> profileItems;
|
||||
|
||||
public ProfileList() {
|
||||
toggleGroup = new ToggleGroup();
|
||||
|
||||
profileItems = MappedObservableList.create(
|
||||
profilesProperty(),
|
||||
profile -> new ProfileListItem(toggleGroup, profile));
|
||||
|
||||
itemsProperty().bindContent(profileItems);
|
||||
|
||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||
if (toggle == null || toggle.getUserData() == null) return;
|
||||
selectedProfile.set(((ProfileListItem) toggle.getUserData()).getProfile());
|
||||
});
|
||||
}
|
||||
|
||||
public ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public ListProperty<Profile> profilesProperty() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
Controllers.navigate(new ProfilePage(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
|
||||
public class ProfileListItem extends Control {
|
||||
private final Profile profile;
|
||||
private final ToggleGroup toggleGroup;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
|
||||
public ProfileListItem(ToggleGroup toggleGroup, Profile profile) {
|
||||
this.profile = profile;
|
||||
this.toggleGroup = toggleGroup;
|
||||
|
||||
title.set(Profiles.getProfileDisplayName(profile));
|
||||
subtitle.set(profile.getGameDir().toString());
|
||||
selected.set(Profiles.selectedProfileProperty().get() == profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ProfileListItemSkin(this);
|
||||
}
|
||||
|
||||
public ToggleGroup getToggleGroup() {
|
||||
return toggleGroup;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
Profiles.getProfiles().remove(profile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
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;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
public ProfileListItemSkin(ProfileListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.setUserData(skinnable);
|
||||
chkSelected.selectedProperty().bindBidirectional(skinnable.selectedProperty());
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().set(new Image("/assets/img/craft_table.png"));
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
btnRemove.setOnMouseClicked(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
+16
-24
@@ -15,36 +15,39 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title;
|
||||
private final ReadOnlyStringWrapper title;
|
||||
private final StringProperty location;
|
||||
private final Profile profile;
|
||||
|
||||
@FXML private JFXTextField txtProfileName;
|
||||
@FXML private FileItem gameDir;
|
||||
@FXML private JFXButton btnSave;
|
||||
@FXML private JFXButton btnDelete;
|
||||
@FXML private JFXCheckBox toggleUseRelativePath;
|
||||
|
||||
/**
|
||||
@@ -54,7 +57,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
this.profile = profile;
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
|
||||
title = new SimpleStringProperty(this, "title",
|
||||
title = new ReadOnlyStringWrapper(this, "title",
|
||||
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
@@ -70,18 +73,8 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty());
|
||||
if (profile == null) {
|
||||
btnDelete.setVisible(false);
|
||||
} else {
|
||||
toggleUseRelativePath.setSelected(profile.isUseRelativePath());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
if (profile != null) {
|
||||
Settings.instance().deleteProfile(profile);
|
||||
Controllers.navigate(null);
|
||||
toggleUseRelativePath.setSelected(profile.isUseRelativePath());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,11 +92,10 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
Profile newProfile = new Profile(txtProfileName.getText(), new File(getLocation()));
|
||||
newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
Settings.instance().putProfile(newProfile);
|
||||
Profiles.getProfiles().add(newProfile);
|
||||
}
|
||||
|
||||
Settings.instance().onProfileLoading();
|
||||
Controllers.navigate(null);
|
||||
fireEvent(new PageCloseEvent());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
@@ -111,8 +103,8 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
Loaded 100 of 225 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user