Compare commits

..
24 Commits
Author SHA1 Message Date
huanghongxun 45eaa0c60f Merge pull request #371 from yushijinhun/fix
Refactor(WIP) & Fixes
2018-07-08 20:06:32 +08:00
yushijinhun 66ed683196 Move config load/save code to ConfigHolder(WIP) 2018-07-08 19:32:43 +08:00
yushijinhun aa4239721e Use NIO api to load/save config 2018-07-08 19:19:37 +08:00
yushijinhun 5b0b926cbd Move config serialization/deserialization to Config class 2018-07-08 19:17:53 +08:00
yushijinhun 929ce398eb AddAccountPane 使用数据绑定 & 登录时设置disable为true 2018-07-08 18:33:37 +08:00
yushijinhun b3fdbe021d 添加验证服务器界面回车代表下一步 2018-07-08 17:47:37 +08:00
huanghongxun 423bd68ce8 Merge pull request #369 from yushijinhun/fix
Fixes
2018-07-08 12:36:29 +08:00
yushijinhun 9bffc68c70 Do not reuse dialog 2018-07-08 12:20:38 +08:00
yushijinhun b7a5a60091 Improve English translations 2018-07-08 10:25:41 +08:00
yushijinhun 333b11c19f Fix uncaught JsonParseException, mentioned in #367 2018-07-08 10:11:14 +08:00
huanghongxun fbd4d1b772 Merge pull request #368 from yushijinhun/dnd
支持 DnD 方式添加验证服务器
2018-07-07 23:28:53 +08:00
yushijinhun 06a58514b3 Fix black ComboBox 2018-07-07 23:06:44 +08:00
yushijinhun 80815fc3a0 authlib-injector DnD support 2018-07-07 23:04:56 +08:00
huanghongxun b97c277a3f Merge pull request #364 from yushijinhun/refactor-ui
UI 重构 & bug 修复
2018-07-07 20:53:22 +08:00
yushijinhun 310d693e2b Remove some error cause inferences
Trampoline - High frequency of false positives.
java.lang.OutOfMemoryError - It's more likely to be a memory leak.
2018-07-07 20:51:28 +08:00
yushijinhun c1596f6a11 Replace Controllers.closeDialog with DialogCloseEvent 2018-07-07 20:50:54 +08:00
yushijinhun 76a04343d9 Use Controllers.dialog for AddAuthlibInjectorServerDialog 2018-07-07 20:30:48 +08:00
yushijinhun 25261c99f1 Remove 'class ' prefix in error messages 2018-07-07 20:30:48 +08:00
yushijinhun d0b994fa9d Move add authentication server dialog to a new file 2018-07-07 20:30:48 +08:00
yushijinhun f259601397 Each TransitionHandler instance uses its own empty pane
Using shared empty pane may cause some probleams
2018-07-07 20:30:47 +08:00
yushijinhun 87904109ca [TransitionHandler]Set mouseTransparent of the whole view
, fix bug introduced in 3fb5048f10
2018-07-07 20:30:47 +08:00
yushijinhun 785790ccc4 Move promptText of cboServers to FXML 2018-07-07 20:29:03 +08:00
yushijinhun 759921d00e Fix missing license headers 2018-07-07 20:28:59 +08:00
huangyuhui e9db183d54 Fix blank page of add authlib-injector server dialog 2018-07-07 20:08:40 +08:00
35 changed files with 759 additions and 425 deletions

No files matched your search

@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.*;
@@ -148,11 +149,11 @@ public final class LauncherHelper {
else
launchingStepsPane.setCancel(it -> {
process.stop();
Controllers.closeDialog(it);
it.fireEvent(new DialogCloseEvent());
});
} else
Platform.runLater(() -> {
Controllers.closeDialog(launchingStepsPane);
launchingStepsPane.fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
});
@@ -177,7 +178,7 @@ public final class LauncherHelper {
// Check if the application has stopped
// because onStop will be invoked if tasks fail when the executor service shut down.
if (!Controllers.isStopped()) {
Controllers.closeDialog(launchingStepsPane);
launchingStepsPane.fireEvent(new DialogCloseEvent());
Exception ex = executor.getLastException();
if (ex != null) {
String message;
@@ -267,8 +268,9 @@ public final class LauncherHelper {
}
public void emitStatus(LoadingState state) {
if (state == LoadingState.DONE)
Controllers.closeDialog(launchingStepsPane);
if (state == LoadingState.DONE) {
launchingStepsPane.fireEvent(new DialogCloseEvent());
}
launchingStepsPane.setTitle(state.getLocalizedMessage());
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
@@ -71,7 +71,7 @@ public final class Accounts {
}
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
return Settings.SETTINGS.authlibInjectorServers.stream()
return ConfigHolder.CONFIG.authlibInjectorServers.stream()
.filter(server -> url.equals(server.getUrl()))
.findFirst()
.orElseGet(() -> {
@@ -85,7 +85,7 @@ public final class Accounts {
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
}
Settings.SETTINGS.authlibInjectorServers.add(server);
ConfigHolder.CONFIG.authlibInjectorServers.add(server);
return server;
});
}
@@ -17,12 +17,21 @@
*/
package org.jackhuang.hmcl.setting;
import java.io.File;
import java.util.Map;
import java.util.TreeMap;
import org.hildan.fxgson.creators.ObservableListCreator;
import org.hildan.fxgson.creators.ObservableMapCreator;
import org.hildan.fxgson.creators.ObservableSetCreator;
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.util.FileTypeAdapter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.BooleanProperty;
@@ -36,9 +45,25 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
public final class Config implements Cloneable {
private static final Gson CONFIG_GSON = new GsonBuilder()
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
.setPrettyPrinting()
.create();
public static Config fromJson(String json) throws JsonParseException {
return CONFIG_GSON.fromJson(json, Config.class);
}
@SerializedName("last")
public final StringProperty selectedProfile = new SimpleStringProperty("");
@@ -104,8 +129,12 @@ public final class Config implements Cloneable {
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
public String toJson() {
return CONFIG_GSON.toJson(this);
}
@Override
public Config clone() {
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
return fromJson(this.toJson());
}
}
@@ -0,0 +1,65 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import com.google.gson.JsonParseException;
public final class ConfigHolder {
private ConfigHolder() {}
public static final String CONFIG_FILENAME = "hmcl.json";
public static final Path CONFIG_PATH = Paths.get(CONFIG_FILENAME).toAbsolutePath();
public static final Config CONFIG = initSettings();
private static Config initSettings() {
Config config = new Config();
if (Files.exists(CONFIG_PATH)) {
try {
Config deserialized = Config.fromJson(new String(Files.readAllBytes(CONFIG_PATH), UTF_8));
if (deserialized == null) {
LOG.finer("Settings file is empty, use the default settings.");
} else {
config = deserialized;
}
LOG.finest("Initialized settings.");
} catch (IOException | JsonParseException e) {
LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
}
}
return config;
}
static void saveConfig(Config config) {
try {
Files.write(CONFIG_PATH, config.toJson().getBytes(UTF_8));
} catch (IOException ex) {
LOG.log(Level.SEVERE, "Failed to save config", ex);
}
}
}
@@ -17,21 +17,11 @@
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.scene.text.Font;
import org.hildan.fxgson.creators.ObservableListCreator;
import org.hildan.fxgson.creators.ObservableMapCreator;
import org.hildan.fxgson.creators.ObservableSetCreator;
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
@@ -43,8 +33,6 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.Locales;
import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
@@ -60,21 +48,6 @@ import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
public class Settings {
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
.setPrettyPrinting()
.create();
public static final String SETTINGS_FILE_NAME = "hmcl.json";
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
public static final Config SETTINGS = initSettings();
public static final Settings INSTANCE = new Settings();
@@ -83,11 +56,12 @@ public class Settings {
private final boolean firstLaunch;
private Settings() {
firstLaunch = SETTINGS.firstLaunch.get();
firstLaunch = ConfigHolder.CONFIG.firstLaunch.get();
ConfigHolder.CONFIG.firstLaunch.set(false);
loadProxy();
for (Iterator<Map<Object, Object>> iterator = SETTINGS.accounts.iterator(); iterator.hasNext();) {
for (Iterator<Map<Object, Object>> iterator = ConfigHolder.CONFIG.accounts.iterator(); iterator.hasNext();) {
Map<Object, Object> settings = iterator.next();
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
if (factory == null) {
@@ -108,7 +82,7 @@ public class Settings {
accounts.put(Accounts.getAccountId(account), account);
}
SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
ConfigHolder.CONFIG.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
checkProfileMap();
@@ -123,51 +97,26 @@ public class Settings {
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
}
private static Config initSettings() {
Config c = new Config();
if (SETTINGS_FILE.exists())
try {
String str = FileUtils.readText(SETTINGS_FILE);
if (StringUtils.isBlank(str))
Logging.LOG.finer("Settings file is empty, use the default settings.");
else {
Config d = GSON.fromJson(str, Config.class);
if (d != null)
c = d;
}
Logging.LOG.finest("Initialized settings.");
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
}
return c;
}
public void save() {
try {
SETTINGS.accounts.clear();
SETTINGS.firstLaunch.set(false);
for (Account account : accounts.values()) {
Map<Object, Object> storage = account.toStorage();
storage.put("type", Accounts.getAccountType(account));
SETTINGS.accounts.add(storage);
}
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to save config", ex);
ConfigHolder.CONFIG.accounts.clear();
for (Account account : accounts.values()) {
Map<Object, Object> storage = account.toStorage();
storage.put("type", Accounts.getAccountType(account));
ConfigHolder.CONFIG.accounts.add(storage);
}
ConfigHolder.saveConfig(ConfigHolder.CONFIG);
}
public boolean isFirstLaunch() {
return firstLaunch;
}
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.commonDirectory.get()) {
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", ConfigHolder.CONFIG.commonDirectory.get()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.commonDirectory.set(get());
ConfigHolder.CONFIG.commonDirectory.set(get());
save();
}
};
@@ -184,7 +133,7 @@ public class Settings {
this.commonPath.set(commonPath);
}
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.localization.get());
private Locales.SupportedLocale locale = Locales.getLocaleByName(ConfigHolder.CONFIG.localization.get());
public Locales.SupportedLocale getLocale() {
return locale;
@@ -192,7 +141,7 @@ public class Settings {
public void setLocale(Locales.SupportedLocale locale) {
this.locale = locale;
SETTINGS.localization.set(Locales.getNameByLocale(locale));
ConfigHolder.CONFIG.localization.set(Locales.getNameByLocale(locale));
save();
}
@@ -202,7 +151,7 @@ public class Settings {
return proxy;
}
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.proxyType.get());
private Proxy.Type proxyType = Proxies.getProxyType(ConfigHolder.CONFIG.proxyType.get());
public Proxy.Type getProxyType() {
return proxyType;
@@ -210,62 +159,62 @@ public class Settings {
public void setProxyType(Proxy.Type proxyType) {
this.proxyType = proxyType;
SETTINGS.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
ConfigHolder.CONFIG.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
save();
loadProxy();
}
public String getProxyHost() {
return SETTINGS.proxyHost.get();
return ConfigHolder.CONFIG.proxyHost.get();
}
public void setProxyHost(String proxyHost) {
SETTINGS.proxyHost.set(proxyHost);
ConfigHolder.CONFIG.proxyHost.set(proxyHost);
save();
}
public String getProxyPort() {
return SETTINGS.proxyPort.get();
return ConfigHolder.CONFIG.proxyPort.get();
}
public void setProxyPort(String proxyPort) {
SETTINGS.proxyPort.set(proxyPort);
ConfigHolder.CONFIG.proxyPort.set(proxyPort);
save();
}
public String getProxyUser() {
return SETTINGS.proxyUser.get();
return ConfigHolder.CONFIG.proxyUser.get();
}
public void setProxyUser(String proxyUser) {
SETTINGS.proxyUser.set(proxyUser);
ConfigHolder.CONFIG.proxyUser.set(proxyUser);
save();
}
public String getProxyPass() {
return SETTINGS.proxyPass.get();
return ConfigHolder.CONFIG.proxyPass.get();
}
public void setProxyPass(String proxyPass) {
SETTINGS.proxyPass.set(proxyPass);
ConfigHolder.CONFIG.proxyPass.set(proxyPass);
save();
}
public boolean hasProxy() {
return SETTINGS.hasProxy.get();
return ConfigHolder.CONFIG.hasProxy.get();
}
public void setHasProxy(boolean hasProxy) {
SETTINGS.hasProxy.set(hasProxy);
ConfigHolder.CONFIG.hasProxy.set(hasProxy);
save();
}
public boolean hasProxyAuth() {
return SETTINGS.hasProxyAuth.get();
return ConfigHolder.CONFIG.hasProxyAuth.get();
}
public void setHasProxyAuth(boolean hasProxyAuth) {
SETTINGS.hasProxyAuth.set(hasProxyAuth);
ConfigHolder.CONFIG.hasProxyAuth.set(hasProxyAuth);
save();
}
@@ -296,21 +245,21 @@ public class Settings {
}
public Font getFont() {
return Font.font(SETTINGS.fontFamily.get(), SETTINGS.fontSize.get());
return Font.font(ConfigHolder.CONFIG.fontFamily.get(), ConfigHolder.CONFIG.fontSize.get());
}
public void setFont(Font font) {
SETTINGS.fontFamily.set(font.getFamily());
SETTINGS.fontSize.set(font.getSize());
ConfigHolder.CONFIG.fontFamily.set(font.getFamily());
ConfigHolder.CONFIG.fontSize.set(font.getSize());
save();
}
public int getLogLines() {
return Math.max(SETTINGS.logLines.get(), 100);
return Math.max(ConfigHolder.CONFIG.logLines.get(), 100);
}
public void setLogLines(int logLines) {
SETTINGS.logLines.set(logLines);
ConfigHolder.CONFIG.logLines.set(logLines);
save();
}
@@ -326,7 +275,7 @@ public class Settings {
private void removeDanglingAuthlibInjectorAccounts() {
accounts.values().stream()
.filter(AuthlibInjectorAccount.class::isInstance)
.filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
.filter(it -> !ConfigHolder.CONFIG.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
.collect(toList())
.forEach(this::deleteAccount);
}
@@ -336,14 +285,14 @@ public class Settings {
****************************************/
public DownloadProvider getDownloadProvider() {
return DownloadProviders.getDownloadProvider(SETTINGS.downloadType.get());
return DownloadProviders.getDownloadProvider(ConfigHolder.CONFIG.downloadType.get());
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
if (index == -1)
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
SETTINGS.downloadType.set(index);
ConfigHolder.CONFIG.downloadType.set(index);
save();
}
@@ -351,7 +300,7 @@ public class Settings {
* ACCOUNTS *
****************************************/
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.selectedAccount.get())) {
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(ConfigHolder.CONFIG.selectedAccount.get())) {
@Override
public Account get() {
Account a = super.get();
@@ -373,7 +322,7 @@ public class Settings {
public void invalidated() {
super.invalidated();
SETTINGS.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
ConfigHolder.CONFIG.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
save();
}
};
@@ -423,12 +372,12 @@ public class Settings {
* BACKGROUND *
****************************************/
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.backgroundImage.get()) {
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", ConfigHolder.CONFIG.backgroundImage.get()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.backgroundImage.set(get());
ConfigHolder.CONFIG.backgroundImage.set(get());
save();
}
};
@@ -445,12 +394,12 @@ public class Settings {
this.backgroundImage.set(backgroundImage);
}
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.backgroundImageType.get())) {
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(ConfigHolder.CONFIG.backgroundImageType.get())) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.backgroundImageType.set(get().ordinal());
ConfigHolder.CONFIG.backgroundImageType.set(get().ordinal());
save();
}
};
@@ -471,12 +420,12 @@ public class Settings {
* THEME *
****************************************/
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.theme.get()).orElse(Theme.BLUE)) {
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(ConfigHolder.CONFIG.theme.get()).orElse(Theme.BLUE)) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.theme.set(get().getName().toLowerCase());
ConfigHolder.CONFIG.theme.set(get().getName().toLowerCase());
save();
}
};
@@ -500,19 +449,19 @@ public class Settings {
public Profile getSelectedProfile() {
checkProfileMap();
if (!hasProfile(SETTINGS.selectedProfile.get())) {
if (!hasProfile(ConfigHolder.CONFIG.selectedProfile.get())) {
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
SETTINGS.selectedProfile.set(selectedProfile);
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile);
save();
});
Schedulers.computation().schedule(this::onProfileChanged);
}
return getProfile(SETTINGS.selectedProfile.get());
return getProfile(ConfigHolder.CONFIG.selectedProfile.get());
}
public void setSelectedProfile(Profile selectedProfile) {
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.selectedProfile.get())) {
SETTINGS.selectedProfile.set(selectedProfile.getName());
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), ConfigHolder.CONFIG.selectedProfile.get())) {
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile.getName());
save();
Schedulers.computation().schedule(this::onProfileChanged);
}
@@ -530,7 +479,7 @@ public class Settings {
}
public Map<String, Profile> getProfileMap() {
return SETTINGS.configurations;
return ConfigHolder.CONFIG.configurations;
}
public Collection<Profile> getProfiles() {
@@ -589,6 +538,6 @@ public class Settings {
}
public Config getRawConfig() {
return SETTINGS.clone();
return ConfigHolder.CONFIG.clone();
}
}
@@ -21,13 +21,13 @@ import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXProgressBar;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import java.util.function.Consumer;
@@ -35,7 +35,6 @@ public class AccountLoginPane extends StackPane {
private final Account oldAccount;
private final Consumer<AuthInfo> success;
private final Runnable failed;
private final Consumer<Region> closeConsumer;
@FXML
private Label lblUsername;
@@ -43,11 +42,10 @@ public class AccountLoginPane extends StackPane {
@FXML private Label lblCreationWarning;
@FXML private JFXProgressBar progressBar;
public AccountLoginPane(Account oldAccount, Consumer<Region> closeConsumer, Consumer<AuthInfo> success, Runnable failed) {
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
this.oldAccount = oldAccount;
this.success = success;
this.failed = failed;
this.closeConsumer = closeConsumer;
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
@@ -70,9 +68,9 @@ public class AccountLoginPane extends StackPane {
Object account = variable.get("login");
if (account instanceof AuthInfo) {
success.accept(((AuthInfo) account));
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
} else if (account instanceof NoSelectedCharacterException) {
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
} else if (account instanceof Exception) {
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
}
@@ -84,6 +82,6 @@ public class AccountLoginPane extends StackPane {
@FXML
private void onCancel() {
failed.run();
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
}
}
@@ -19,16 +19,17 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.*;
@@ -40,23 +41,25 @@ import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.logging.Level;
public class AddAccountPane extends StackPane {
@@ -72,47 +75,49 @@ public class AddAccountPane extends StackPane {
@FXML private JFXDialogLayout layout;
@FXML private JFXButton btnAccept;
@FXML private SpinnerPane acceptPane;
private final Consumer<Region> finalization;
public AddAccountPane(Consumer<Region> finalization) {
this.finalization = finalization;
public AddAccountPane() {
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
cboServers.setPromptText(i18n("general.prompt.empty"));
// workaround: otherwise the combox will be black
if (!cboServers.getItems().isEmpty())
cboServers.getSelectionModel().select(0);
Bindings.bindContent(cboServers.getItems(), ConfigHolder.CONFIG.authlibInjectorServers);
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
selectDefaultServer();
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
txtPassword.setVisible(newValue.intValue() != 0);
lblPassword.setVisible(newValue.intValue() != 0);
cboServers.setVisible(newValue.intValue() == 2);
linkManageInjectorServers.setVisible(newValue.intValue() == 2);
lblInjectorServer.setVisible(newValue.intValue() == 2);
validateAcceptButton();
});
cboType.getSelectionModel().select(0);
txtPassword.setOnAction(e -> onCreationAccept());
txtUsername.setOnAction(e -> onCreationAccept());
ReadOnlyIntegerProperty loginTypeIdProperty = cboType.getSelectionModel().selectedIndexProperty();
txtPassword.visibleProperty().bind(loginTypeIdProperty.isNotEqualTo(0));
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
cboServers.visibleProperty().bind(loginTypeIdProperty.isEqualTo(2));
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
linkManageInjectorServers.visibleProperty().bind(cboServers.visibleProperty());
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
txtUsername.textProperty().addListener(it -> validateAcceptButton());
txtPassword.textProperty().addListener(it -> validateAcceptButton());
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
() -> !txtUsername.validate() || (loginTypeIdProperty.get() != 0 && !txtPassword.validate()),
txtUsername.textProperty(), txtPassword.textProperty(), loginTypeIdProperty));
}
private void validateAcceptButton() {
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
/**
* Selects the first server if no server is selected.
*/
private void selectDefaultServer() {
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
cboServers.getSelectionModel().select(0);
}
}
@FXML
private void onCreationAccept() {
if (btnAccept.isDisabled())
return;
String username = txtUsername.getText();
String password = txtPassword.getText();
Object addtionalData;
@@ -144,41 +149,35 @@ public class AddAccountPane extends StackPane {
acceptPane.showSpinner();
lblCreationWarning.setText("");
setDisable(true);
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy()))
.finalized(Schedulers.javafx(), variables -> {
Settings.INSTANCE.addAccount(variables.get("create_account"));
acceptPane.hideSpinner();
finalization.accept(this);
fireEvent(new DialogCloseEvent());
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
finalization.accept(this);
fireEvent(new DialogCloseEvent());
} else {
lblCreationWarning.setText(accountException(exception));
}
setDisable(false);
acceptPane.hideSpinner();
}).start();
}
@FXML
private void onCreationCancel() {
finalization.accept(this);
fireEvent(new DialogCloseEvent());
}
@FXML
private void onManageInjecterServers() {
finalization.accept(this);
fireEvent(new DialogCloseEvent());
Controllers.navigate(Controllers.getServersPage());
}
private void showSelector(Node node) {
getChildren().setAll(node);
}
private void closeSelector() {
getChildren().setAll(layout);
}
private class Selector extends BorderPane implements CharacterSelector {
private final AdvancedListBox listBox = new AdvancedListBox();
private final JFXButton cancel = new JFXButton();
@@ -235,7 +234,7 @@ public class AddAccountPane extends StackPane {
listBox.add(accountItem);
}
JFXUtilities.runInFX(() -> showSelector(this));
JFXUtilities.runInFX(() -> Controllers.dialog(this));
try {
latch.await();
@@ -243,11 +242,11 @@ public class AddAccountPane extends StackPane {
if (selectedProfile == null)
throw new NoSelectedCharacterException(account);
JFXUtilities.runInFX(AddAccountPane.this::closeSelector);
return selectedProfile;
} catch (InterruptedException ignore) {
throw new NoSelectedCharacterException(account);
} finally {
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
}
}
}
@@ -270,7 +269,7 @@ public class AddAccountPane extends StackPane {
}
return exception.getMessage();
} else {
return exception.getClass() + ": " + exception.getLocalizedMessage();
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
}
}
@@ -0,0 +1,141 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.IOException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.NetworkUtils;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
public class AddAuthlibInjectorServerPane extends StackPane {
@FXML private StackPane addServerContainer;
@FXML private Label lblServerUrl;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private JFXTextField txtServerUrl;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private SpinnerPane nextPane;
@FXML private JFXButton btnAddNext;
private TransitionHandler transitionHandler;
private AuthlibInjectorServer serverBeingAdded;
public AddAuthlibInjectorServerPane(String url) {
this();
txtServerUrl.setText(url);
onAddNext();
}
public AddAuthlibInjectorServerPane() {
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
transitionHandler = new TransitionHandler(addServerContainer);
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
btnAddNext.disableProperty().bind(
Bindings.createBooleanBinding(txtServerUrl::validate, txtServerUrl.textProperty()).not());
nextPane.hideSpinner();
}
private String fixInputUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url;
}
private String resolveFetchExceptionMessage(Throwable exception) {
if (exception instanceof IOException) {
return i18n("account.failed.connect_injector_server");
} else {
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
}
}
@FXML
private void onAddCancel() {
fireEvent(new DialogCloseEvent());
}
@FXML
private void onAddNext() {
if (btnAddNext.isDisabled())
return;
lblCreationWarning.setText("");
String url = fixInputUrl(txtServerUrl.getText());
nextPane.showSpinner();
addServerPane.setDisable(true);
Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
addServerPane.setDisable(false);
nextPane.hideSpinner();
if (isDependentsSucceeded) {
lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
}
}).start();
}
@FXML
private void onAddPrev() {
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
}
@FXML
private void onAddFinish() {
if (!ConfigHolder.CONFIG.authlibInjectorServers.contains(serverBeingAdded)) {
ConfigHolder.CONFIG.authlibInjectorServers.add(serverBeingAdded);
}
fireEvent(new DialogCloseEvent());
}
}
@@ -1,134 +1,69 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import static java.util.stream.Collectors.toList;
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.setting.ConfigHolder;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.NetworkUtils;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.IOException;
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.server"));
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.manage.title"));
@FXML private ScrollPane scrollPane;
@FXML private StackPane addServerContainer;
@FXML private Label lblServerUrl;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private VBox listPane;
@FXML private JFXTextField txtServerUrl;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private JFXDialog dialog;
@FXML private StackPane contentPane;
@FXML private SpinnerPane nextPane;
@FXML private JFXButton btnAddNext;
private final TransitionHandler transitionHandler;
private InvalidationListener serversListener;
private AuthlibInjectorServer serverBeingAdded;
public AuthlibInjectorServersPage() {
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
smoothScrolling(scrollPane);
{
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
FXUtils.smoothScrolling(scrollPane);
transitionHandler = new TransitionHandler(addServerContainer);
getChildren().remove(dialog);
dialog.setDialogContainer(this);
txtServerUrl.textProperty().addListener((a, b, newValue) ->
btnAddNext.setDisable(!txtServerUrl.validate()));
reload();
serversListener = observable -> updateServersList();
ConfigHolder.CONFIG.authlibInjectorServers.addListener(new WeakInvalidationListener(serversListener));
updateServersList();
}
private void removeServer(AuthlibInjectorServerItem item) {
Settings.SETTINGS.authlibInjectorServers.remove(item.getServer());
reload();
}
private void reload() {
private void updateServersList() {
listPane.getChildren().setAll(
Settings.SETTINGS.authlibInjectorServers.stream()
.map(server -> new AuthlibInjectorServerItem(server, this::removeServer))
ConfigHolder.CONFIG.authlibInjectorServers.stream()
.map(server -> new AuthlibInjectorServerItem(server,
item -> ConfigHolder.CONFIG.authlibInjectorServers.remove(item.getServer())))
.collect(toList()));
if (Settings.SETTINGS.authlibInjectorServers.isEmpty()) {
onAdd();
}
}
@FXML
private void onAdd() {
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
txtServerUrl.setText("");
txtServerUrl.resetValidation();
lblCreationWarning.setText("");
addServerPane.setDisable(false);
nextPane.hideSpinner();
dialog.show();
}
@FXML
private void onAddCancel() {
dialog.close();
}
@FXML
private void onAddNext() {
String url = fixInputUrl(txtServerUrl.getText());
nextPane.showSpinner();
addServerPane.setDisable(true);
Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
nextPane.hideSpinner();
addServerPane.setDisable(false);
if (isDependentsSucceeded) {
lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
}
}).start();
}
@FXML
private void onAddPrev() {
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
}
@FXML
private void onAddFinish() {
if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) {
Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded);
}
reload();
dialog.close();
Controllers.dialog(new AddAuthlibInjectorServerPane());
}
public String getTitle() {
@@ -143,19 +78,4 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
public void setTitle(String title) {
this.title.set(title);
}
private String fixInputUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url;
}
private String resolveFetchExceptionMessage(Throwable exception) {
if (exception instanceof IOException) {
return i18n("account.failed.connect_injector_server");
} else {
return exception.getClass() + ": " + exception.getLocalizedMessage();
}
}
}
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
@@ -132,15 +133,15 @@ public final class Controllers {
}
public static void dialog(String text, String title, int type, Runnable onAccept) {
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, type, onAccept));
dialog(new MessageDialogPane(text, title, type, onAccept));
}
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, onAccept, onCancel));
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
}
public static void inputDialog(String text, Consumer<String> onResult) {
dialog(new InputDialogPane(text, Controllers::closeDialog, onResult));
dialog(new InputDialogPane(text, onResult));
}
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
@@ -156,6 +157,9 @@ public final class Controllers {
return pane;
}
/**
* Use {@link DialogCloseEvent}
*/
public static void closeDialog(Region content) {
if (stage == null) // shut down
return;
@@ -32,6 +32,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
@@ -43,6 +44,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
@@ -52,6 +54,7 @@ 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.EnumBackgroundImage;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.Theme;
@@ -59,6 +62,7 @@ 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.*;
@@ -67,6 +71,7 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.util.Locale;
import java.util.Optional;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -81,6 +86,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Decorator.class.getName() + ".dialog.closeListener";
private final ObjectProperty<Runnable> onCloseButtonAction;
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
@@ -89,13 +96,14 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
private final boolean max, min;
private final WizardController wizardController = new WizardController(this);
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
private final JFXDialog dialog;
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean allowMove, isDragging, maximized;
private BoundingBox originalBox, maximizedBox;
private final TransitionHandler animationHandler;
private final StackContainerPane dialogPane = new StackContainerPane();
private JFXDialog dialog;
private StackContainerPane dialogPane;
@FXML
private StackPane contentPlaceHolder;
@@ -174,12 +182,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
btnMax.fire();
});
dialog = new JFXDialog();
dialog.setOverlayClose(false);
drawerWrapper.getChildren().add(0, dialog);
dialog.setDialogContainer(drawerWrapper);
dialog.setContent(dialogPane);
welcomeView.setCursor(Cursor.HAND);
welcomeView.setOnMouseClicked(e -> {
Timeline nowAnimation = new Timeline();
@@ -209,6 +211,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
animationHandler = new TransitionHandler(contentPlaceHolder);
loadBackground();
setupAuthlibInjectorDnD();
}
private void loadBackground() {
@@ -563,20 +567,37 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
public void showDialog(Node node) {
FXUtils.checkFxUserThread();
if (dialogPane.isEmpty())
if (dialog == null) {
dialog = new JFXDialog();
dialogPane = new StackContainerPane();
dialog.setContent(dialogPane);
dialog.setDialogContainer(drawerWrapper);
dialog.setOverlayClose(false);
dialog.show();
}
dialogPane.push(node);
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
}
@SuppressWarnings("unchecked")
public void closeDialog(Node node) {
FXUtils.checkFxUserThread();
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
dialogPane.pop(node);
if (dialogPane.getChildren().isEmpty())
Platform.runLater(() -> {
if (dialogPane.getChildren().isEmpty())
dialog.close();
});
if (dialogPane.getChildren().isEmpty()) {
dialog.close();
dialog = null;
dialogPane = null;
}
}
public void startWizard(WizardProvider wizardProvider) {
@@ -670,4 +691,10 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
public AdvancedListBox getLeftPane() {
return leftPane;
}
private void setupAuthlibInjectorDnD() {
addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
}
}
@@ -34,7 +34,7 @@ public final class DialogController {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
JFXUtilities.runInFX(() -> {
AccountLoginPane pane = new AccountLoginPane(account, Controllers::closeDialog, it -> {
AccountLoginPane pane = new AccountLoginPane(account, it -> {
res.set(it);
latch.countDown();
}, latch::countDown);
@@ -45,6 +45,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.ClassTitle;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.Lang;
@@ -100,7 +101,7 @@ public final class LeftPaneController {
}
private void addNewAccount() {
Controllers.dialog(new AddAccountPane(Controllers::closeDialog));
Controllers.dialog(new AddAccountPane());
}
private void onSelectedAccountChanged(Account newAccount) {
@@ -228,7 +229,7 @@ public final class LeftPaneController {
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), () -> {
Controllers.closeDialog(region.get());
region.get().fireEvent(new DialogCloseEvent());
checkAccount();
})).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.installing"), ""));
@@ -42,6 +42,7 @@ 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;
@@ -165,17 +166,17 @@ public final class MainPage extends StackPane implements DecoratorPage {
AtomicReference<Region> region = new AtomicReference<>();
try {
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
.then(Task.of(Schedulers.javafx(), () -> Controllers.closeDialog(region.get()))).executor();
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
executor.start();
} catch (UnsupportedModpackException e) {
Controllers.closeDialog(region.get());
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (MismatchedModpackTypeException e) {
Controllers.closeDialog(region.get());
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (IOException e) {
Controllers.closeDialog(region.get());
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.beans.property.SimpleStringProperty;
@@ -18,11 +18,12 @@
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.util.Duration;
import java.util.List;
@FunctionalInterface
public interface AnimationProducer {
void init(AnimationHandler handler);
List<KeyFrame> animate(AnimationHandler handler);
}
@@ -20,17 +20,43 @@ package org.jackhuang.hmcl.ui.animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.scene.Node;
import javafx.util.Duration;
import org.jackhuang.hmcl.util.Lang;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public enum ContainerAnimations {
NONE(c -> Collections.emptyList()),
NONE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c -> Collections.emptyList()),
/**
* A fade between the old and new view
*/
FADE(c ->
FADE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
@@ -40,7 +66,12 @@ public enum ContainerAnimations {
/**
* A zoom effect
*/
ZOOM_IN(c ->
ZOOM_IN(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
@@ -52,7 +83,12 @@ public enum ContainerAnimations {
/**
* A zoom effect
*/
ZOOM_OUT(c ->
ZOOM_OUT(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
(Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
@@ -64,7 +100,14 @@ public enum ContainerAnimations {
/**
* A swipe effect
*/
SWIPE_LEFT(c ->
SWIPE_LEFT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
@@ -75,7 +118,14 @@ public enum ContainerAnimations {
/**
* A swipe effect
*/
SWIPE_RIGHT(c ->
SWIPE_RIGHT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
@@ -85,8 +135,18 @@ public enum ContainerAnimations {
private final AnimationProducer animationProducer;
ContainerAnimations(AnimationProducer animationProducer) {
this.animationProducer = animationProducer;
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
this.animationProducer = new AnimationProducer() {
@Override
public void init(AnimationHandler handler) {
init.accept(handler);
}
@Override
public List<KeyFrame> animate(AnimationHandler handler) {
return animationProducer.apply(handler);
}
};
}
public AnimationProducer getAnimationProducer() {
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
@@ -78,35 +79,38 @@ public final class TransitionHandler implements AnimationHandler {
updateContent(newView);
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
previousNode.setMouseTransparent((Boolean) previousNode.getProperties().get(MOUSE_TRANSPARENT));
view.getChildren().remove(previousNode);
}));
nowAnimation.play();
animation = nowAnimation;
transition.init(this);
// runLater or "init" will not work
Platform.runLater(() -> {
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
view.setMouseTransparent(false);
view.getChildren().remove(previousNode);
}));
nowAnimation.play();
animation = nowAnimation;
});
}
private void updateContent(Node newView) {
if (view.getWidth() > 0 && view.getHeight() > 0) {
previousNode = currentNode;
if (previousNode == null)
previousNode = NULL;
previousNode = EMPTY_PANE;
} else
previousNode = NULL;
previousNode = EMPTY_PANE;
if (previousNode == newView)
previousNode = NULL;
previousNode = EMPTY_PANE;
previousNode.getProperties().put(MOUSE_TRANSPARENT, previousNode.isMouseTransparent());
previousNode.setMouseTransparent(true);
view.setMouseTransparent(true);
currentNode = newView;
view.getChildren().setAll(previousNode, currentNode);
}
private static final String MOUSE_TRANSPARENT = "TransitionHandler.MOUSE_TRANSPARENT";
private static final StackPane NULL = new StackPane();
private final StackPane EMPTY_PANE = new StackPane();
}
@@ -0,0 +1,45 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import org.jackhuang.hmcl.ui.Controllers;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.layout.Region;
/**
* Indicates a close operation on the dialog.
*
* @author yushijinhun
* @see Controllers#dialog(Region)
*/
public class DialogCloseEvent extends Event {
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("CLOSE");
public DialogCloseEvent() {
super(CLOSE);
}
public DialogCloseEvent(Object source, EventTarget target) {
super(source, target, CLOSE);
}
}
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -38,13 +37,13 @@ public class InputDialogPane extends StackPane {
@FXML
private Label content;
public InputDialogPane(String text, Consumer<Region> closeConsumer, Consumer<String> onResult) {
public InputDialogPane(String text, Consumer<String> onResult) {
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
content.setText(text);
cancelButton.setOnMouseClicked(e -> closeConsumer.accept(this));
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
acceptButton.setOnMouseClicked(e -> {
onResult.accept(textField.getText());
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
});
}
}
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.setting.Theme;
@@ -30,10 +29,8 @@ import org.jackhuang.hmcl.ui.SVG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Optional;
import java.util.function.Consumer;
public final class MessageDialogPane extends StackPane {
private boolean closingDialog = true;
@FXML
private JFXButton acceptButton;
@@ -48,7 +45,7 @@ public final class MessageDialogPane extends StackPane {
@FXML
private HBox actions;
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, int type, Runnable onAccept) {
public MessageDialogPane(String text, String title, int type, Runnable onAccept) {
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
if (title != null)
@@ -56,7 +53,7 @@ public final class MessageDialogPane extends StackPane {
content.setText(text);
acceptButton.setOnMouseClicked(e -> {
closeConsumer.accept(MessageDialogPane.this);
fireEvent(new DialogCloseEvent());
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
});
@@ -83,12 +80,12 @@ public final class MessageDialogPane extends StackPane {
}
}
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, Runnable onAccept, Runnable onCancel) {
this(text, title, closeConsumer, MessageBox.QUESTION_MESSAGE, onAccept);
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
this(text, title, MessageBox.QUESTION_MESSAGE, onAccept);
cancelButton.setVisible(true);
cancelButton.setOnMouseClicked(e -> {
closeConsumer.accept(MessageDialogPane.this);
fireEvent(new DialogCloseEvent());
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
});
@@ -97,8 +94,4 @@ public final class MessageDialogPane extends StackPane {
actions.getChildren().add(cancelButton);
}
public void disableClosingDialog() {
closingDialog = false;
}
}
@@ -35,7 +35,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
@Override
default void handleTask(Map<String, Object> settings, Task task) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
Controllers.closeDialog(it);
it.fireEvent(new DialogCloseEvent());
Controllers.navigate(null);
});
@@ -62,7 +62,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
@Override
public void onStop(boolean success, TaskExecutor executor) {
JFXUtilities.runInFX(() -> {
Controllers.closeDialog(pane);
pane.fireEvent(new DialogCloseEvent());
if (success) {
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.game.HMCLModpackExportTask;
import org.jackhuang.hmcl.game.HMCLModpackManager;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.setting.Config;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
@@ -93,7 +94,7 @@ public final class ExportWizardProvider implements WizardProvider {
config.logLines.set(100);
config.configurations.clear();
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
zip.putTextFile(config.toJson(), ConfigHolder.CONFIG_FILENAME);
zip.putFile(tempModpack, "modpack.zip");
File bg = new File("bg").getAbsoluteFile();
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -120,7 +121,7 @@ public class AppDataUpgrader extends IUpgrader {
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
@@ -138,7 +139,7 @@ public class AppDataUpgrader extends IUpgrader {
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.VersionNumber;
import java.io.File;
@@ -72,7 +73,7 @@ public class NewFileUpgrader extends IUpgrader {
}
System.exit(0);
}
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
}
private static String getRealPath() {
@@ -51,8 +51,6 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
put("netscape.javascript.JSException", i18n("crash.NoClassDefFound"));
put("java.lang.IncompatibleClassChangeError", i18n("crash.NoClassDefFound"));
put("java.lang.ClassFormatError", i18n("crash.NoClassDefFound"));
put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!");
put("Trampoline", i18n("launcher.update_java"));
put("com.sun.javafx.css.StyleManager.findMatchingStyles", i18n("launcher.update_java"));
put("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?");
}
@@ -569,7 +569,6 @@
.list-cell:selected, .jfx-list-cell:selected {
-fx-background-insets: 0.0;
-fx-text-fill: BLACK;
-fx-background-color: black;
}
.jfx-list-cell:filled:hover,
@@ -29,7 +29,7 @@
<Label fx:id="lblInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<JFXComboBox fx:id="cboServers" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<JFXComboBox fx:id="cboServers" promptText="%general.prompt.empty" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Hyperlink fx:id="linkManageInjectorServers" text="%account.injector.manage"
onMouseClicked="#onManageInjecterServers" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
@@ -37,7 +37,7 @@
<Label text="%account.username" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
<JFXTextField fx:id="txtUsername" GridPane.columnIndex="1" GridPane.rowIndex="2" GridPane.columnSpan="2"
FXUtils.validateWhileTextChanged="true">
FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
<validators>
<RequiredFieldValidator message="%input.not_empty">
</RequiredFieldValidator>
@@ -47,7 +47,7 @@
<Label fx:id="lblPassword" text="%account.password" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
<JFXPasswordField fx:id="txtPassword" GridPane.columnIndex="1" GridPane.rowIndex="3"
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true">
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
<validators>
<RequiredFieldValidator message="%input.not_empty">
</RequiredFieldValidator>
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.*?>
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<StackPane fx:id="addServerContainer">
<JFXDialogLayout fx:id="addServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url" onAction="#onAddNext">
<validators>
<URLValidator message="%input.url">
<protocols>
<String fx:value="http" />
<String fx:value="https" />
</protocols>
</URLValidator>
</validators>
</JFXTextField>
</body>
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
</SpinnerPane>
</actions>
</JFXDialogLayout>
<JFXDialogLayout fx:id="confirmServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
<columnConstraints>
<ColumnConstraints maxWidth="100" />
<ColumnConstraints />
</columnConstraints>
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;"
GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
</GridPane>
</body>
<actions>
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
</actions>
</JFXDialogLayout>
</StackPane>
</fx:root>
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
<?import java.lang.String?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<?import com.jfoenix.controls.*?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
@@ -23,61 +21,4 @@
</JFXButton>
</VBox>
</StackPane>
<JFXDialog fx:id="dialog">
<StackPane>
<StackPane fx:id="addServerContainer">
<JFXDialogLayout fx:id="addServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url">
<validators>
<URLValidator message="%input.url">
<protocols>
<String fx:value="http" />
<String fx:value="https" />
</protocols>
</URLValidator>
</validators>
</JFXTextField>
</body>
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
</SpinnerPane>
</actions>
</JFXDialogLayout>
<JFXDialogLayout fx:id="confirmServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
<columnConstraints>
<ColumnConstraints maxWidth="100" />
<ColumnConstraints />
</columnConstraints>
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
</GridPane>
</body>
<actions>
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
</actions>
</JFXDialogLayout>
</StackPane>
</StackPane>
</JFXDialog>
</fx:root>
@@ -41,8 +41,9 @@ account.failed.invalid_token=Please log out and re-input your password to login.
account.failed.no_character=No character in this account.
account.failed.no_selected_server=No authentication server is selected.
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
account.injector.add=Add authentication server
account.injector.add=Add an authentication server
account.injector.manage=Manage authentication servers
account.injector.manage.title=Authentication servers
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
account.injector.server=Auth Server
account.injector.server_url=Server URL
@@ -52,7 +53,7 @@ account.methods.authlib_injector=authlib-injector
account.methods.no_method=No login method
account.methods.offline=Offline
account.methods.yggdrasil=Mojang
account.missing=Missing account
account.missing=None
account.password=Password
account.username=Name
@@ -127,7 +128,7 @@ install.select=Select an operation
install.success=Installed successfully
lang=English
lang.default=Belong to OS language.
lang.default=Use system language
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
@@ -306,8 +307,8 @@ settings.type.special=Specialized version settings(will not affect other version
update=Update
update.failed=Failed to check for updates.
update.found=Found Update!
update.newest_version=Newest version: %s
update.found=Update Available!
update.newest_version=Latest version: %s
update.latest=This is latest Version.
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
update.tooltip=Update
@@ -43,6 +43,7 @@ account.failed.no_selected_server=未選擇認證服務器
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
account.injector.add=添加認證服務器
account.injector.manage=管理認證服務器
account.injector.manage.title=認證服務器
account.injector.http=警告:此服務器使用不安全的 HTTP 協議,您的密碼在登錄時會被明文傳輸。
account.injector.server=認證服務器
account.injector.server_url=服務器地址
@@ -43,6 +43,7 @@ account.failed.no_selected_server=未选择认证服务器
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
account.injector.add=添加认证服务器
account.injector.manage=管理认证服务器
account.injector.manage.title=认证服务器
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
account.injector.server=认证服务器
account.injector.server_url=服务器地址
@@ -0,0 +1,71 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.authlibinjector;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Optional;
import java.util.function.Consumer;
import javafx.event.EventHandler;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
/**
* @author yushijinhun
* @see https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF
*/
public final class AuthlibInjectorDnD {
private static final String SCHEME = "authlib-injector";
private static final String PATH_YGGDRASIL_SERVER = "yggdrasil-server";
private AuthlibInjectorDnD() {}
public static Optional<String> parseUrlFromDragboard(Dragboard dragboard) {
String uri = dragboard.getString();
if (uri == null) return Optional.empty();
String[] uriElements = uri.split(":");
if (uriElements.length == 3 && SCHEME.equals(uriElements[0]) && PATH_YGGDRASIL_SERVER.equals(uriElements[1])) {
try {
return Optional.of(URLDecoder.decode(uriElements[2], "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
return Optional.empty();
}
public static EventHandler<DragEvent> dragOverHandler() {
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
event.acceptTransferModes(TransferMode.COPY);
event.consume();
});
}
public static EventHandler<DragEvent> dragDroppedHandler(Consumer<String> onUrlTransfered) {
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
event.setDropCompleted(true);
event.consume();
onUrlTransfered.accept(url);
});
}
}
@@ -26,8 +26,8 @@ import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class AuthlibInjectorServer {
@@ -36,7 +36,7 @@ public class AuthlibInjectorServer {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
String name = extractServerName(response).orElse(url);
return new AuthlibInjectorServer(url, name);
} catch (JsonSyntaxException e) {
} catch (JsonParseException e) {
throw new IOException("Malformed response", e);
}
}