Prepare for MCBBS modpack

This commit is contained in:
huangyuhui
2018-01-12 21:30:09 +08:00
parent 8edfe7bc9c
commit 4b39c046a7
93 changed files with 3187 additions and 2572 deletions

View File

@@ -15,30 +15,33 @@
* 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
package org.jackhuang.hmcl.event;
import org.jackhuang.hmcl.event.Event
import org.jackhuang.hmcl.setting.Profile
import java.util.*
import org.jackhuang.hmcl.setting.Profile;
/**
* This event gets fired when the selected profile changed.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.setting.Settings]
* *
* @param Profile the new profile.
* *
* <br>
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
* @author huangyuhui
*/
class ProfileChangedEvent(source: Any, val value: Profile) : Event(source)
public final class ProfileChangedEvent extends Event {
private final Profile profile;
/**
* This event gets fired when loading profiles.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.setting.Settings]
* *
* @author huangyuhui
*/
class ProfileLoadingEvent(source: Any) : Event(source)
/**
* 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;
}
}

View File

@@ -15,18 +15,25 @@
* 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.event;
import javafx.scene.Scene
import javafx.scene.image.Image
import javafx.scene.web.WebView
import javafx.stage.Stage
/**
* 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 {
class WebStage: Stage() {
val webView = WebView()
init {
scene = Scene(webView, 800.0, 480.0)
scene.stylesheets.addAll(*stylesheets)
icons += Image("/assets/img/icon.png")
/**
* Constructor.
*
* @param source {@link org.jackhuang.hmcl.setting.Settings}
*/
public ProfileLoadingEvent(Object source) {
super(source);
}
}
}

View File

@@ -100,7 +100,7 @@ public final class LauncherHelper {
public void onFinished(Task task) {
finished.incrementAndGet();
Platform.runLater(() -> {
launchingStepsPane.getPgsTasks().setProgress(1.0 * finished.get() / executor.getRunningTasks());
launchingStepsPane.setProgress(1.0 * finished.get() / executor.getRunningTasks());
});
}
@@ -124,8 +124,8 @@ public final class LauncherHelper {
if (state == LoadingState.DONE)
Controllers.INSTANCE.closeDialog();
launchingStepsPane.getLblCurrentState().setText(state.toString());
launchingStepsPane.getLblSteps().setText((state.ordinal() + 1) + " / " + LoadingState.values().length);
launchingStepsPane.setCurrentState(state.toString());
launchingStepsPane.setSteps((state.ordinal() + 1) + " / " + LoadingState.values().length);
}
private void checkExit(LauncherVisibility v) {

View File

@@ -0,0 +1,244 @@
/*
* 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.setting;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.util.JavaVersion;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public final class Config {
@SerializedName("last")
private String selectedProfile = "";
@SerializedName("bgpath")
private String backgroundImage = null;
@SerializedName("commonpath")
private String commonDirectory = Main.getMinecraftDirectory().getAbsolutePath();
@SerializedName("proxyType")
private int proxyType = 0;
@SerializedName("proxyHost")
private String proxyHost = null;
@SerializedName("proxyPort")
private String proxyPort = null;
@SerializedName("proxyUserName")
private String proxyUser = null;
@SerializedName("proxyPassword")
private String proxyPass = null;
@SerializedName("theme")
private String theme = null;
@SerializedName("java")
private List<JavaVersion> java = null;
@SerializedName("localization")
private String localization;
@SerializedName("downloadtype")
private int downloadType = 0;
@SerializedName("configurations")
private Map<String, Profile> configurations = new TreeMap<>();
@SerializedName("accounts")
private Map<String, Map<Object, Object>> accounts = new TreeMap<>();
@SerializedName("selectedAccount")
private String selectedAccount = "";
@SerializedName("fontFamily")
private String fontFamily = "Consolas";
@SerializedName("fontSize")
private double fontSize = 12;
@SerializedName("logLines")
private int logLines = 100;
public String getSelectedProfile() {
return selectedProfile;
}
public void setSelectedProfile(String selectedProfile) {
this.selectedProfile = selectedProfile;
Settings.INSTANCE.save();
}
public String getBackgroundImage() {
return backgroundImage;
}
public void setBackgroundImage(String backgroundImage) {
this.backgroundImage = backgroundImage;
Settings.INSTANCE.save();
}
public String getCommonDirectory() {
return commonDirectory;
}
public void setCommonDirectory(String commonDirectory) {
this.commonDirectory = commonDirectory;
Settings.INSTANCE.save();
}
public int getProxyType() {
return proxyType;
}
public void setProxyType(int proxyType) {
this.proxyType = proxyType;
Settings.INSTANCE.save();
}
public String getProxyHost() {
return proxyHost;
}
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
Settings.INSTANCE.save();
}
public String getProxyPort() {
return proxyPort;
}
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
Settings.INSTANCE.save();
}
public String getProxyUser() {
return proxyUser;
}
public void setProxyUser(String proxyUser) {
this.proxyUser = proxyUser;
Settings.INSTANCE.save();
}
public String getProxyPass() {
return proxyPass;
}
public void setProxyPass(String proxyPass) {
this.proxyPass = proxyPass;
Settings.INSTANCE.save();
}
public String getTheme() {
return theme;
}
public void setTheme(String theme) {
this.theme = theme;
Settings.INSTANCE.save();
}
public List<JavaVersion> getJava() {
return java;
}
public void setJava(List<JavaVersion> java) {
this.java = java;
Settings.INSTANCE.save();
}
public String getLocalization() {
return localization;
}
public void setLocalization(String localization) {
this.localization = localization;
Settings.INSTANCE.save();
}
public int getDownloadType() {
return downloadType;
}
public void setDownloadType(int downloadType) {
this.downloadType = downloadType;
Settings.INSTANCE.save();
}
public Map<String, Profile> getConfigurations() {
return configurations;
}
public void setConfigurations(Map<String, Profile> configurations) {
this.configurations = configurations;
Settings.INSTANCE.save();
}
public Map<String, Map<Object, Object>> getAccounts() {
return accounts;
}
public void setAccounts(Map<String, Map<Object, Object>> accounts) {
this.accounts = accounts;
Settings.INSTANCE.save();
}
public String getSelectedAccount() {
return selectedAccount;
}
public void setSelectedAccount(String selectedAccount) {
this.selectedAccount = selectedAccount;
Settings.INSTANCE.save();
}
public String getFontFamily() {
return fontFamily;
}
public void setFontFamily(String fontFamily) {
this.fontFamily = fontFamily;
Settings.INSTANCE.save();
}
public double getFontSize() {
return fontSize;
}
public void setFontSize(double fontSize) {
this.fontSize = fontSize;
Settings.INSTANCE.save();
}
public int getLogLines() {
return logLines;
}
public void setLogLines(int logLines) {
this.logLines = logLines;
}
}

View File

@@ -0,0 +1,443 @@
/*
* 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.setting;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
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)
.setPrettyPrinting().create();
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
public static final Settings INSTANCE = new Settings();
private Settings() {}
private final Config SETTINGS = initSettings();
private Map<String, Account> accounts = new HashMap<>();
{
for (Map.Entry<String, Map<Object, Object>> entry : SETTINGS.getAccounts().entrySet()) {
String name = entry.getKey();
Map<Object, Object> settings = entry.getValue();
AccountFactory factory = Accounts.ACCOUNT_FACTORY.get(Lang.get(settings, "type", String.class, ""));
if (factory == null) {
SETTINGS.getAccounts().remove(name);
continue;
}
Account account;
try {
account = factory.fromStorage(settings);
} catch (Exception e) {
SETTINGS.getAccounts().remove(name);
// storage is malformed, delete.
continue;
}
if (!Objects.equals(account.getUsername(), name)) {
SETTINGS.getAccounts().remove(name);
continue;
}
accounts.put(name, account);
}
save();
if (!getProfileMap().containsKey(DEFAULT_PROFILE))
getProfileMap().put(DEFAULT_PROFILE, new Profile());
for (Map.Entry<String, Profile> entry2 : getProfileMap().entrySet()) {
entry2.getValue().setName(entry2.getKey());
entry2.getValue().addPropertyChangedListener(e -> {
save();
});
}
Lang.ignoringException(() -> {
Runtime.getRuntime().addShutdownHook(new Thread(this::save));
});
loadProxy();
}
private 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);
}
else {
Logging.LOG.config("No settings file here, may be first loading.");
if (!c.getConfigurations().containsKey(HOME_PROFILE))
c.getConfigurations().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.getMinecraftDirectory()));
}
return c;
}
public void save() {
try {
SETTINGS.getAccounts().clear();
for (Map.Entry<String, Account> entry : accounts.entrySet()) {
String name = entry.getKey();
Account account = entry.getValue();
Map<Object, Object> storage = account.toStorage();
storage.put("type", Accounts.getAccountType(account));
SETTINGS.getAccounts().put(name, storage);
}
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to save config", ex);
}
}
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.getCommonDirectory()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setCommonDirectory(get());
}
};
public String getCommonPath() {
return commonPath.get();
}
public StringProperty commonPathProperty() {
return commonPath;
}
public void setCommonPath(String commonPath) {
this.commonPath.set(commonPath);
}
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.getLocalization());
public Locales.SupportedLocale getLocale() {
return locale;
}
public void setLocale(Locales.SupportedLocale locale) {
this.locale = locale;
SETTINGS.setLocalization(Locales.getNameByLocale(locale));
}
private Proxy proxy = Proxy.NO_PROXY;
public Proxy getProxy() {
return proxy;
}
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.getProxyType());
public Proxy.Type getProxyType() {
return proxyType;
}
public void setProxyType(Proxy.Type proxyType) {
this.proxyType = proxyType;
SETTINGS.setProxyType(Proxies.PROXIES.indexOf(proxyType));
loadProxy();
}
public String getProxyHost() {
return SETTINGS.getProxyHost();
}
public void setProxyHost(String proxyHost) {
SETTINGS.setProxyHost(proxyHost);
}
public String getProxyPort() {
return SETTINGS.getProxyPort();
}
public void setProxyPort(String proxyPort) {
SETTINGS.setProxyPort(proxyPort);
}
public String getProxyUser() {
return SETTINGS.getProxyUser();
}
public void setProxyUser(String proxyUser) {
SETTINGS.setProxyUser(proxyUser);
}
public String getProxyPass() {
return SETTINGS.getProxyPass();
}
public void setProxyPass(String proxyPass) {
SETTINGS.setProxyPass(proxyPass);
}
private void loadProxy() {
String host = getProxyHost();
Integer port = Lang.toIntOrNull(getProxyPort());
if (StringUtils.isBlank(host) || port == null)
proxy = Proxy.NO_PROXY;
else {
System.setProperty("http.proxyHost", getProxyHost());
System.setProperty("http.proxyPort", getProxyPort());
if (getProxyType() == Proxy.Type.DIRECT)
proxy = Proxy.NO_PROXY;
else
proxy = new Proxy(proxyType, new InetSocketAddress(host, port));
String user = getProxyUser();
String pass = getProxyPass();
if (StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass)) {
System.setProperty("http.proxyUser", user);
System.setProperty("http.proxyPassword", pass);
Authenticator.setDefault(new Authenticator() {
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, pass.toCharArray());
}
});
}
}
}
public Font getFont() {
return Font.font(SETTINGS.getFontFamily(), SETTINGS.getFontSize());
}
public void setFont(Font font) {
SETTINGS.setFontFamily(font.getFamily());
SETTINGS.setFontSize(font.getSize());
}
public int getLogLines() {
return Math.max(SETTINGS.getLogLines(), 100);
}
public void setLogLines(int logLines) {
SETTINGS.setLogLines(logLines);
}
public DownloadProvider getDownloadProvider() {
switch (SETTINGS.getDownloadType()) {
case 0:
return MojangDownloadProvider.INSTANCE;
case 1:
return BMCLAPIDownloadProvider.INSTANCE;
default:
return MojangDownloadProvider.INSTANCE;
}
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
if (downloadProvider == MojangDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(0);
else if (downloadProvider == BMCLAPIDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(1);
else
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
}
/****************************************
* ACCOUNTS *
****************************************/
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", getAccount(SETTINGS.getSelectedAccount())) {
@Override
public Account get() {
Account a = super.get();
if (a == null || !accounts.containsKey(a.getUsername())) {
Account acc = accounts.values().stream().findAny().orElse(null);
set(acc);
return acc;
} else return a;
}
@Override
public void set(Account newValue) {
if (newValue == null || accounts.containsKey(newValue.getUsername())) {
super.set(newValue);
}
}
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setSelectedAccount(getValue() == null ? "" : getValue().getUsername());
}
};
public Account getSelectedAccount() {
return selectedAccount.get();
}
public ObjectProperty<Account> selectedAccountProperty() {
return selectedAccount;
}
public void setSelectedAccount(Account selectedAccount) {
this.selectedAccount.set(selectedAccount);
}
public void addAccount(Account account) {
accounts.put(account.getUsername(), account);
}
public Account getAccount(String name) {
return accounts.get(name);
}
public Map<String, Account> getAccounts() {
return Collections.unmodifiableMap(accounts);
}
public void deleteAccount(String name) {
accounts.remove(name);
selectedAccount.get();
}
/****************************************
* PROFILES *
****************************************/
private Profile selectedProfile;
public Profile getSelectedProfile() {
if (!hasProfile(SETTINGS.getSelectedProfile())) {
SETTINGS.setSelectedProfile(DEFAULT_PROFILE);
Schedulers.computation().schedule(this::onProfileChanged);
}
return getProfile(SETTINGS.getSelectedProfile());
}
public void setSelectedProfile(Profile selectedProfile) {
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.getSelectedProfile())) {
SETTINGS.setSelectedProfile(selectedProfile.getName());
Schedulers.computation().schedule(this::onProfileChanged);
}
}
public Profile getProfile(String name) {
Profile p = getProfileMap().get(Lang.nonNull(name, DEFAULT_PROFILE));
if (p == null)
if (getProfileMap().containsKey(DEFAULT_PROFILE))
p = getProfileMap().get(DEFAULT_PROFILE);
else {
p = new Profile();
getProfileMap().put(DEFAULT_PROFILE, p);
}
return p;
}
public boolean hasProfile(String name) {
return getProfileMap().containsKey(Lang.nonNull(name, DEFAULT_PROFILE));
}
public Map<String, Profile> getProfileMap() {
return SETTINGS.getConfigurations();
}
public Collection<Profile> getProfiles() {
return getProfileMap().values().stream().filter(t -> StringUtils.isNotBlank(t.getName())).collect(Collectors.toList());
}
public boolean putProfile(Profile ver) {
if (ver == null || StringUtils.isBlank(ver.getName()) || getProfileMap().containsKey(ver.getName()))
return false;
getProfileMap().put(ver.getName(), ver);
return true;
}
public boolean deleteProfile(Profile ver) {
return deleteProfile(ver.getName());
}
public boolean deleteProfile(String ver) {
if (Objects.equals(DEFAULT_PROFILE, ver)) {
return false;
}
boolean flag = getProfileMap().remove(ver) != null;
if (flag)
Schedulers.computation().schedule(this::onProfileLoading);
return flag;
}
private void onProfileChanged() {
getSelectedProfile().getRepository().refreshVersions();
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile()));
}
/**
* Start profiles loading process.
* Invoked by loading GUI phase.
*/
public void onProfileLoading() {
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(SETTINGS));
onProfileChanged();
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
/**
* @author huangyuhui
*/
public class ClassTitle extends StackPane {
private final String text;
public ClassTitle(String text) {
this.text = text;
VBox vbox = new VBox();
vbox.getChildren().addAll(new Text(text));
Rectangle rectangle = new Rectangle();
rectangle.widthProperty().bind(vbox.widthProperty());
rectangle.setHeight(1.0);
rectangle.setFill(Color.GRAY);
vbox.getChildren().add(rectangle);
getChildren().setAll(vbox);
getStyleClass().add("class-title");
}
public String getText() {
return text;
}
}

View File

@@ -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 com.jfoenix.concurrency.JFXUtilities;
import kotlin.Unit;
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 java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public final class DialogController {
public static final DialogController INSTANCE = new DialogController();
private DialogController() {}
public static AuthInfo logIn(Account account) throws Exception {
if (account instanceof YggdrasilAccount) {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
JFXUtilities.runInFX(() -> {
YggdrasilAccountLoginPane pane = new YggdrasilAccountLoginPane((YggdrasilAccount) account, it -> {
res.set(it);
latch.countDown();
Controllers.INSTANCE.closeDialog();
return Unit.INSTANCE;
}, () -> {
latch.countDown();
Controllers.INSTANCE.closeDialog();
return Unit.INSTANCE;
});
pane.dialog = Controllers.INSTANCE.dialog(pane);
});
latch.await();
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
}
return null;
}
}

View File

@@ -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 javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
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.Task;
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
import java.util.LinkedList;
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;
public void initialize() {
FXUtilsKt.smoothScrolling(scrollPane);
}
public void loadVersion(Profile profile, String versionId) {
this.profile = profile;
this.versionId = versionId;
this.version = profile.getRepository().getVersion(versionId).resolve(profile.getRepository());
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 VersionJsonSaveTask(profile.getRepository(), version.setLibraries(newList))
.with(Task.of(e -> profile.getRepository().refreshVersions()))
.with(Task.of(e -> 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();
}
}
}
public void onAdd() {
String gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
// TODO: if minecraftVersion returns null.
if (gameVersion == null) return;
Controllers.INSTANCE.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion, version, forge, liteLoader, optiFine));
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.effects.JFXDepthManager;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import java.util.function.Consumer;
/**
* @author huangyuhui
*/
public class InstallerItem extends BorderPane {
private final Consumer<InstallerItem> deleteCallback;
@FXML
private Label lblInstallerArtifact;
@FXML
private Label lblInstallerVersion;
public InstallerItem(String artifact, String version, Consumer<InstallerItem> deleteCallback) {
this.deleteCallback = deleteCallback;
FXUtilsKt.loadFXML(this, "/assets/fxml/version/installer-item.fxml");
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblInstallerArtifact.setText(artifact);
lblInstallerVersion.setText(version);
}
public void onDelete() {
deleteCallback.accept(this);
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.JFXProgressBar;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
public class LaunchingStepsPane extends StackPane {
@FXML
private JFXProgressBar pgsTasks;
@FXML
private Label lblCurrentState;
@FXML
private Label lblSteps;
public LaunchingStepsPane() {
FXUtilsKt.loadFXML(this, "/assets/fxml/launching-steps.fxml");
FXUtilsKt.limitHeight(this, 200);
FXUtilsKt.limitWidth(this, 400);
}
public void setCurrentState(String currentState) {
lblCurrentState.setText(currentState);
}
public void setSteps(String steps) {
lblSteps.setText(steps);
}
public void setProgress(double progress) {
pgsTasks.setProgress(progress);
}
}

View File

@@ -50,11 +50,11 @@ import org.w3c.dom.Node;
*/
public final class LogWindow extends Stage {
private final ReadOnlyIntegerWrapper fatalProperty = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper errorProperty = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper warnProperty = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper infoProperty = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper debugProperty = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper fatal = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper error = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper warn = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper info = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyIntegerWrapper debug = new ReadOnlyIntegerWrapper(0);
private final LogWindowImpl impl = new LogWindowImpl();
private final CountDownLatch latch = new CountDownLatch(1);
public final EventManager<Event> onDone = new EventManager<>();
@@ -75,43 +75,43 @@ public final class LogWindow extends Stage {
}
public ReadOnlyIntegerProperty fatalProperty() {
return fatalProperty.getReadOnlyProperty();
return fatal.getReadOnlyProperty();
}
public int getFatal() {
return fatalProperty.get();
return fatal.get();
}
public ReadOnlyIntegerProperty errorProperty() {
return errorProperty.getReadOnlyProperty();
return error.getReadOnlyProperty();
}
public int getError() {
return errorProperty.get();
return error.get();
}
public ReadOnlyIntegerProperty warnProperty() {
return warnProperty.getReadOnlyProperty();
return warn.getReadOnlyProperty();
}
public int getWarn() {
return warnProperty.get();
return warn.get();
}
public ReadOnlyIntegerProperty infoProperty() {
return infoProperty.getReadOnlyProperty();
return info.getReadOnlyProperty();
}
public int getInfo() {
return infoProperty.get();
return info.get();
}
public ReadOnlyIntegerProperty debugProperty() {
return debugProperty.getReadOnlyProperty();
return debug.getReadOnlyProperty();
}
public int getDebug() {
return debugProperty.get();
return debug.get();
}
public void logLine(String line, Log4jLevel level) {
@@ -125,45 +125,45 @@ public final class LogWindow extends Stage {
switch (level) {
case FATAL:
fatalProperty.set(fatalProperty.get() + 1);
fatal.set(fatal.get() + 1);
break;
case ERROR:
errorProperty.set(errorProperty.get() + 1);
error.set(error.get() + 1);
break;
case WARN:
warnProperty.set(warnProperty.get() + 1);
warn.set(warn.get() + 1);
break;
case INFO:
infoProperty.set(infoProperty.get() + 1);
info.set(info.get() + 1);
break;
case DEBUG:
debugProperty.set(debugProperty.get() + 1);
debug.set(debug.get() + 1);
break;
}
}
public class LogWindowImpl extends StackPane {
private class LogWindowImpl extends StackPane {
@FXML
public WebView webView;
private WebView webView;
@FXML
public ToggleButton btnFatals;
private ToggleButton btnFatals;
@FXML
public ToggleButton btnErrors;
private ToggleButton btnErrors;
@FXML
public ToggleButton btnWarns;
private ToggleButton btnWarns;
@FXML
public ToggleButton btnInfos;
private ToggleButton btnInfos;
@FXML
public ToggleButton btnDebugs;
private ToggleButton btnDebugs;
@FXML
public ComboBox<String> cboLines;
private ComboBox<String> cboLines;
WebEngine engine;
Node body;
Document document;
public LogWindowImpl() {
LogWindowImpl() {
FXUtilsKt.loadFXML(this, "/assets/fxml/log.fxml");
engine = webView.getEngine();
@@ -194,11 +194,11 @@ public final class LogWindow extends Stage {
if (!flag)
cboLines.getSelectionModel().select(0);
btnFatals.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(fatalProperty.get()) + " fatals", fatalProperty));
btnErrors.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(errorProperty.get()) + " errors", errorProperty));
btnWarns.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(warnProperty.get()) + " warns", warnProperty));
btnInfos.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(infoProperty.get()) + " infos", infoProperty));
btnDebugs.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(debugProperty.get()) + " debugs", debugProperty));
btnFatals.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(fatal.get()) + " fatals", fatal));
btnErrors.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(error.get()) + " errors", error));
btnWarns.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(warn.get()) + " warns", warn));
btnInfos.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(info.get()) + " infos", info));
btnDebugs.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(debug.get()) + " debugs", debug));
btnFatals.selectedProperty().addListener(o -> specificChanged());
btnErrors.selectedProperty().addListener(o -> specificChanged());

View File

@@ -15,24 +15,29 @@
* 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
package org.jackhuang.hmcl.ui;
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.ui.loadFXML
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
class VersionsPageItem(val remoteVersion: RemoteVersion<*>) : StackPane() {
public final class MessageDialogPane extends StackPane {
private final String text;
private final JFXDialog dialog;
@FXML lateinit var lblSelfVersion: Label
@FXML lateinit var lblGameVersion: Label
@FXML
private JFXButton acceptButton;
@FXML
private Label content;
private var handler: () -> Unit = {}
public MessageDialogPane(String text, JFXDialog dialog) {
this.text = text;
this.dialog = dialog;
init {
loadFXML("/assets/fxml/download/versions-list-item.fxml")
lblSelfVersion.text = remoteVersion.selfVersion
lblGameVersion.text = remoteVersion.gameVersion
FXUtilsKt.loadFXML(this, "/assets/fxml/message-dialog.fxml");
content.setText(text);
acceptButton.setOnMouseClicked(e -> dialog.close());
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.JFXCheckBox;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.mod.ModInfo;
import java.util.function.Consumer;
public final class ModItem extends BorderPane {
private final Label lblModFileName = new Label();
private final Label lblModAuthor = new Label();
private final JFXCheckBox chkEnabled = new JFXCheckBox();
public ModItem(ModInfo info, Consumer<ModItem> deleteCallback) {
lblModFileName.setStyle("-fx-font-size: 15;");
lblModAuthor.setStyle("-fx-font-size: 10;");
BorderPane.setAlignment(chkEnabled, Pos.CENTER);
setLeft(chkEnabled);
VBox center = new VBox();
BorderPane.setAlignment(center, Pos.CENTER);
center.getChildren().addAll(lblModFileName, lblModAuthor);
setCenter(center);
JFXButton right = new JFXButton();
right.setOnMouseClicked(e -> deleteCallback.accept(this));
right.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(right, Pos.CENTER);
right.setGraphic(SVG.close("black", 15, 15));
setRight(right);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblModFileName.setText(info.getFileName());
lblModAuthor.setText(info.getName() + ", Version: " + info.getVersion() + ", Game: " + info.getGameVersion() + ", Authors: " + info.getAuthors());
chkEnabled.setSelected(info.isActive());
chkEnabled.selectedProperty().addListener((a, b, newValue) -> {
info.activeProperty().set(newValue);
});
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.util.StringConverter;
import org.jackhuang.hmcl.util.Lang;
import java.util.Optional;
/**
* @author huangyuhui
*/
public final class SafeIntStringConverter extends StringConverter<Integer> {
@Override
public Integer fromString(String string) {
return Optional.ofNullable(string).map(Lang::toIntOrNull).orElse(null);
}
@Override
public String toString(Integer object) {
return Optional.ofNullable(object).map(Object::toString).orElse("");
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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 javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
public final class VersionItem extends StackPane {
@FXML
private Pane icon;
@FXML
private VBox content;
@FXML
private StackPane header;
@FXML
private StackPane body;
@FXML
private JFXButton btnDelete;
@FXML
private JFXButton btnSettings;
@FXML
private JFXButton btnLaunch;
@FXML
private Label lblVersionName;
@FXML
private Label lblGameVersion;
@FXML
private ImageView iconView;
public VersionItem() {
FXUtilsKt.loadFXML(this, "/assets/fxml/version-item.fxml");
FXUtilsKt.limitWidth(this, 160);
FXUtilsKt.limitHeight(this, 156);
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
btnSettings.setGraphic(SVG.gear("black", 15, 15));
btnDelete.setGraphic(SVG.delete("black", 15, 15));
btnLaunch.setGraphic(SVG.launch("black", 15, 15));
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
FXUtilsKt.limitSize(iconView, 32, 32);
}
public void setVersionName(String versionName) {
lblVersionName.setText(versionName);
}
public void setGameVersion(String gameVersion) {
lblGameVersion.setText(gameVersion);
}
public void setImage(Image image) {
iconView.setImage(image);
}
public void setOnSettingsButtonClicked(EventHandler<? super MouseEvent> handler) {
btnSettings.setOnMouseClicked(handler);
}
public void setOnDeleteButtonClicked(EventHandler<? super MouseEvent> handler) {
btnDelete.setOnMouseClicked(handler);
}
public void setOnLaunchButtonClicked(EventHandler<? super MouseEvent> handler) {
btnLaunch.setOnMouseClicked(handler);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
public final class VersionListItem extends StackPane {
@FXML
private StackPane imageViewContainer;
@FXML
private Label lblVersionName;
@FXML
private Label lblGameVersion;
@FXML
private ImageView imageView;
private Runnable handler;
public VersionListItem(String versionName) {
this(versionName, "");
}
public VersionListItem(String versionName, String gameVersion) {
FXUtilsKt.loadFXML(this, "/assets/fxml/version-list-item.fxml");
lblVersionName.setText(versionName);
lblGameVersion.setText(gameVersion);
FXUtilsKt.limitSize(imageView, 32, 32);
FXUtilsKt.limitWidth(imageViewContainer, 32);
FXUtilsKt.limitHeight(imageViewContainer, 32);
}
public void onSettings() {
handler.run();
}
public void onSettingsButtonClicked(Runnable handler) {
this.handler = handler;
}
public void setVersionName(String versionName) {
lblVersionName.setText(versionName);
}
public void setGameVersion(String gameVersion) {
lblGameVersion.setText(gameVersion);
}
public void setImage(Image image, Rectangle2D viewport) {
imageView.setImage(image);
imageView.setViewport(viewport);
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebStage extends Stage {
private final WebView webView = new WebView();
public WebStage() {
setScene(new Scene(webView, 800, 480));
getScene().getStylesheets().addAll(FXUtilsKt.getStylesheets());
getIcons().add(new Image("/assets/img/icon.png"));
}
public WebView getWebView() {
return webView;
}
}

View File

@@ -24,5 +24,5 @@ import javafx.util.Duration;
public interface AnimationHandler {
Node getSnapshot();
Duration getDuration();
Pane getView();
Pane getCurrentRoot();
}

View File

@@ -15,10 +15,14 @@
* 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.wizard
package org.jackhuang.hmcl.ui.animation;
interface WizardPage {
fun onNavigate(settings: MutableMap<String, Any>) {}
fun cleanup(settings: MutableMap<String, Any>)
val title: String
}
import javafx.animation.KeyFrame;
import java.util.List;
@FunctionalInterface
public interface AnimationProducer {
List<KeyFrame> animate(AnimationHandler handler);
}

View File

@@ -0,0 +1,91 @@
/*
* 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.animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.util.Duration;
import java.util.Arrays;
import java.util.Collections;
public enum ContainerAnimations {
NONE(c -> Collections.emptyList()),
/**
* A fade between the old and new view
*/
FADE(c ->
Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getSnapshot().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(), new KeyValue(c.getSnapshot().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)))),
/**
* A zoom effect
*/
ZOOM_IN(c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getSnapshot().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getSnapshot().scaleXProperty(), 4, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().scaleYProperty(), 4, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
/**
* A zoom effect
*/
ZOOM_OUT(c ->
(Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getSnapshot().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getSnapshot().scaleXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().scaleYProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
/**
* A swipe effect
*/
SWIPE_LEFT(c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentRoot().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentRoot().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
/**
* A swipe effect
*/
SWIPE_RIGHT(c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentRoot().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentRoot().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getSnapshot().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
private AnimationProducer animationProducer;
ContainerAnimations(AnimationProducer animationProducer) {
this.animationProducer = animationProducer;
}
public AnimationProducer getAnimationProducer() {
return animationProducer;
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtilsKt;
public final class TransitionHandler implements AnimationHandler {
private final StackPane view;
private Timeline animation;
private Duration duration;
private final ImageView snapshot;
/**
* @param view A stack pane that contains another control that is [Parent]
*/
public TransitionHandler(StackPane view) {
this.view = view;
snapshot = new ImageView();
snapshot.setPreserveRatio(true);
snapshot.setSmooth(true);
}
@Override
public Node getSnapshot() {
return snapshot;
}
@Override
public StackPane getCurrentRoot() {
return view;
}
@Override
public Duration getDuration() {
return duration;
}
public void setContent(Node newView, AnimationProducer transition) {
setContent(newView, transition, Duration.millis(320));
}
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
this.duration = duration;
Timeline prev = animation;
if (prev != null)
prev.stop();
updateContent(newView);
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
snapshot.setImage(null);
snapshot.setX(0);
snapshot.setY(0);
snapshot.setVisible(false);
}));
nowAnimation.play();
animation = nowAnimation;
}
private void updateContent(Node newView) {
if (view.getWidth() > 0 && view.getHeight() > 0) {
Node content = view.getChildren().stream().findFirst().orElse(null);
WritableImage image;
if (content != null && content instanceof Parent) {
view.getChildren().setAll();
image = FXUtilsKt.takeSnapshot((Parent) content, view.getWidth(), view.getHeight());
view.getChildren().setAll(content);
} else
image = view.snapshot(new SnapshotParameters(), new WritableImage((int) view.getWidth(), (int) view.getHeight()));
snapshot.setImage(image);
snapshot.setFitWidth(view.getWidth());
snapshot.setFitHeight(view.getHeight());
} else
snapshot.setImage(null);
snapshot.setVisible(true);
snapshot.setOpacity(1.0);
view.getChildren().setAll(snapshot, newView);
snapshot.toFront();
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.DefaultProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
@DefaultProperty("content")
public class ComponentList extends StackPane {
private final VBox vbox = new VBox();
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);
private boolean hasSubtitle = false;
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) {
if (node instanceof ComponentList) {
node.getProperties().put("title", ((ComponentList) node).getTitle());
node.getProperties().put("subtitle", ((ComponentList) node).getSubtitle());
}
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");
vbox.getChildren().add(child);
}
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public String getSubtitle() {
return subtitle.get();
}
public StringProperty subtitleProperty() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle.set(subtitle);
}
public int getDepth() {
return depth.get();
}
public IntegerProperty depthProperty() {
return depth;
}
public void setDepth(int depth) {
this.depth.set(depth);
}
public boolean isHasSubtitle() {
return hasSubtitle;
}
public void setHasSubtitle(boolean hasSubtitle) {
this.hasSubtitle = hasSubtitle;
}
public ObservableList<Node> getContent() {
return content;
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.animation.*;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtilsKt;
import org.jackhuang.hmcl.ui.SVG;
/**
* @author
*/
public class ComponentListCell extends StackPane {
private final Node content;
private Animation expandAnimation;
private Rectangle clipRect;
private double animatedHeight;
private final BooleanProperty expanded = new SimpleBooleanProperty(this, "expanded", false);
public ComponentListCell(Node content) {
this.content = content;
updateLayout();
}
private void updateClip(double newHeight) {
if (clipRect != null)
clipRect.setHeight(newHeight);
}
@Override
protected void layoutChildren() {
super.layoutChildren();
if (clipRect == null)
clipRect = new Rectangle(0, 0, getWidth(), getHeight());
else {
clipRect.setX(0);
clipRect.setY(0);
clipRect.setHeight(getHeight());
clipRect.setWidth(getWidth());
}
}
private void updateLayout() {
if (content instanceof ComponentList) {
ComponentList list = (ComponentList) content;
content.getStyleClass().remove("options-list");
content.getStyleClass().add("options-sublist");
StackPane groupNode = new StackPane();
groupNode.getStyleClass().add("options-list-item-header");
Node expandIcon = SVG.expand("black", 10, 10);
JFXButton expandButton = new JFXButton();
expandButton.setGraphic(expandIcon);
expandButton.getStyleClass().add("options-list-item-expand-button");
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT);
VBox labelVBox = new VBox();
Label label = new Label();
label.textProperty().bind(list.titleProperty());
label.setMouseTransparent(true);
labelVBox.getChildren().add(label);
if (list.isHasSubtitle()) {
Label subtitleLabel = new Label();
subtitleLabel.textProperty().bind(list.subtitleProperty());
subtitleLabel.setMouseTransparent(true);
subtitleLabel.getStyleClass().add("subtitle-label");
labelVBox.getChildren().add(subtitleLabel);
}
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT);
groupNode.getChildren().setAll(labelVBox, expandButton);
VBox container = new VBox();
container.setStyle("-fx-padding: 8 0 0 0;");
FXUtilsKt.limitHeight(container, 0);
Rectangle clipRect = new Rectangle();
clipRect.widthProperty().bind(container.widthProperty());
clipRect.heightProperty().bind(container.heightProperty());
container.setClip(clipRect);
container.getChildren().setAll(content);
VBox holder = new VBox();
holder.getChildren().setAll(groupNode, container);
holder.getStyleClass().add("options-list-item-container");
expandButton.setOnMouseClicked(e -> {
if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {
expandAnimation.stop();
}
setExpanded(!isExpanded());
double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1);
double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1);
double contentHeight = isExpanded() ? newAnimatedHeight : 0;
if (isExpanded()) {
updateClip(newHeight);
}
animatedHeight = newAnimatedHeight;
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
new KeyValue(container.minHeightProperty(), contentHeight, FXUtilsKt.SINE),
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtilsKt.SINE)
));
if (!isExpanded()) {
expandAnimation.setOnFinished(e2 -> {
updateClip(newHeight);
animatedHeight = 0.0;
});
}
expandAnimation.play();
});
expandedProperty().addListener((a, b, newValue) -> {
expandIcon.setRotate(newValue ? 180 : 0);
});
getChildren().setAll(holder);
} else
getChildren().setAll(content);
}
public boolean isExpanded() {
return expanded.get();
}
public BooleanProperty expandedProperty() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded.set(expanded);
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.SVG;
import java.io.File;
public class FileItem extends BorderPane {
private Property<String> property;
private final Label x = new Label();
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
public FileItem() {
VBox left = new VBox();
Label name = new Label();
name.textProperty().bind(nameProperty());
x.getStyleClass().addAll("subtitle-label");
left.getChildren().addAll(name, x);
setLeft(left);
JFXButton right = new JFXButton();
right.setGraphic(SVG.pencil("black", 15, 15));
right.getStyleClass().add("toggle-icon4");
right.setOnMouseClicked(e -> onExplore());
setRight(right);
Tooltip tip = new Tooltip();
tip.textProperty().bind(tooltipProperty());
Tooltip.install(this, tip);
}
public void onExplore() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.titleProperty().bind(titleProperty());
File selectedDir = chooser.showDialog(Controllers.INSTANCE.getStage());
if (selectedDir != null)
property.setValue(selectedDir.getAbsolutePath());
chooser.titleProperty().unbind();
}
public void setProperty(Property<String> property) {
this.property = property;
x.textProperty().bind(property);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public String getTooltip() {
return tooltip.get();
}
public StringProperty tooltipProperty() {
return tooltip;
}
public void setTooltip(String tooltip) {
this.tooltip.set(tooltip);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.JFXComboBox;
import javafx.beans.NamedArg;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ListCell;
import javafx.scene.text.Font;
public class FontComboBox extends JFXComboBox<String> {
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 + "\";");
});
setCellFactory(listView -> new ListCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item);
setFont(new Font(item, fontSize));
}
}
});
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class IconedItem extends RipplerContainer {
private final Node icon;
private final String text;
public IconedItem(Node icon, String text) {
super(createHBox(icon, text));
this.icon = icon;
this.text = text;
}
private static HBox createHBox(Node icon, String text) {
HBox hBox = new HBox();
icon.setMouseTransparent(true);
Label textLabel = new Label(text);
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);
return hBox;
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.MultipleSelectionModel;
public final class NoneMultipleSelectionModel<T> extends MultipleSelectionModel<T> {
private static final NoneMultipleSelectionModel INSTANCE = new NoneMultipleSelectionModel();
private NoneMultipleSelectionModel() {}
@SuppressWarnings("unchecked")
public static <T> NoneMultipleSelectionModel<T> getInstance() {
return (NoneMultipleSelectionModel<T>) INSTANCE;
}
@Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}
@Override
public ObservableList<T> getSelectedItems() {
return FXCollections.emptyObservableList();
}
@Override
public void selectIndices(int index, int... indices) {
}
@Override
public void selectAll() {
}
@Override
public void clearAndSelect(int index) {
}
@Override
public void select(int index) {
}
@Override
public void select(T obj) {
}
@Override
public void clearSelection(int index) {
}
@Override
public void clearSelection() {
}
@Override
public boolean isSelected(int index) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void selectPrevious() {
}
@Override
public void selectNext() {
}
@Override
public void selectFirst() {
}
@Override
public void selectLast() {
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.validation.base.ValidatorBase;
import javafx.scene.control.TextInputControl;
import org.jackhuang.hmcl.util.StringUtils;
public class NumberValidator extends ValidatorBase {
private boolean nullable;
public NumberValidator() {
this(false);
}
public NumberValidator(boolean nullable) {
this.nullable = nullable;
}
@Override
protected void eval() {
if (srcControl.get() instanceof TextInputControl) {
evalTextInputField();
}
}
private void evalTextInputField() {
TextInputControl textField = ((TextInputControl) srcControl.get());
if (StringUtils.isBlank(textField.getText()))
hasErrors.set(false);
else
try {
Integer.parseInt(textField.getText());
hasErrors.set(false);
} catch (NumberFormatException e) {
hasErrors.set(true);
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.download;
import com.jfoenix.controls.JFXButton;
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.game.GameRepository;
import org.jackhuang.hmcl.ui.FXUtilsKt;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Lang;
import java.util.Map;
class AdditionalInstallersPage extends StackPane implements WizardPage {
private final InstallerWizardProvider provider;
private final WizardController controller;
private final GameRepository repository;
private final DownloadProvider downloadProvider;
@FXML private VBox list;
@FXML private JFXButton btnForge;
@FXML private JFXButton btnLiteLoader;
@FXML private JFXButton btnOptiFine;
@FXML private Label lblGameVersion;
@FXML private Label lblVersionName;
@FXML private Label lblForge;
@FXML private Label lblLiteLoader;
@FXML
private Label lblOptiFine;
@FXML private JFXButton btnInstall;
public AdditionalInstallersPage(InstallerWizardProvider provider, WizardController controller, GameRepository repository, DownloadProvider downloadProvider) {
this.provider = provider;
this.controller = controller;
this.repository = repository;
this.downloadProvider = downloadProvider;
FXUtilsKt.loadFXML(this, "/assets/fxml/download/additional-installers.fxml");
lblGameVersion.setText(provider.getGameVersion());
lblVersionName.setText(provider.getVersion().getId());
btnForge.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 0);
controller.onNext(new VersionsPage(controller, provider.getGameVersion(), downloadProvider, "forge", () -> { controller.onPrev(false); }));
});
btnLiteLoader.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 1);
controller.onNext(new VersionsPage(controller, provider.getGameVersion(), downloadProvider, "liteloader", () -> { controller.onPrev(false); }));
});
btnOptiFine.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 2);
controller.onNext(new VersionsPage(controller, provider.getGameVersion(), downloadProvider, "optifine", () -> { controller.onPrev(false); }));
});
}
@Override
public String getTitle() {
return "Choose a game version";
}
@Override
public void onNavigate(Map<String, Object> settings) {
lblGameVersion.setText("Current Game Version: " + provider.getGameVersion());
btnForge.setDisable(provider.getForge() != null);
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
lblForge.setText("Forge Versoin: " + Lang.nonNull(provider.getForge(), controller.getSettings().get("forge")));
else
lblForge.setText("Forge not installed");
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
lblLiteLoader.setText("LiteLoader Versoin: " + Lang.nonNull(provider.getLiteLoader(), controller.getSettings().get("liteloader")));
else
lblLiteLoader.setText("LiteLoader not installed");
btnOptiFine.setDisable(provider.getOptiFine() != null);
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
lblOptiFine.setText("OptiFine Versoin: " + Lang.nonNull(provider.getOptiFine(), controller.getSettings().get("optifine")));
else
lblOptiFine.setText("OptiFine not installed");
}
@Override public void cleanup(Map<String, Object> settings) {
settings.remove(INSTALLER_TYPE);
}
public void onInstall() {
controller.onFinish();
}
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
}

View File

@@ -0,0 +1,112 @@
/*
* 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.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
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 java.util.Map;
public final class InstallerWizardProvider implements WizardProvider {
private final Profile profile;
private final String gameVersion;
private final Version version;
private final String forge;
private final String liteLoader;
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;
}
public Profile getProfile() {
return profile;
}
public String getGameVersion() {
return gameVersion;
}
public Version getVersion() {
return version;
}
public String getForge() {
return forge;
}
public String getLiteLoader() {
return liteLoader;
}
public String getOptiFine() {
return optiFine;
}
@Override
public void start(Map<String, Object> settings) {
}
@Override
public Object finish(Map<String, Object> settings) {
Task ret = Task.empty();
if (settings.containsKey("forge"))
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "forge", (String) settings.get("forge")));
if (settings.containsKey("liteloader"))
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "liteloader", (String) settings.get("liteloader")));
if (settings.containsKey("optifine"))
ret = ret.with(profile.getDependency().installLibraryAsync(gameVersion, version, "optifine", (String) settings.get("optifine")));
return ret.with(Task.of(v -> {
profile.getRepository().refreshVersions();
}));
}
@Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
switch (step) {
case 0:
return new AdditionalInstallersPage(this, controller, profile.getRepository(), BMCLAPIDownloadProvider.INSTANCE);
default:
throw new IllegalStateException();
}
}
@Override
public boolean cancel() {
return true;
}
}

View File

@@ -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.download;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXSpinner;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.FXUtilsKt;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import java.util.Map;
public final class VersionsPage extends StackPane implements WizardPage, Refreshable {
private final WizardController controller;
private final String gameVersion;
private final DownloadProvider downloadProvider;
private final String libraryId;
private final Runnable callback;
@FXML
private JFXListView<VersionsPageItem> list;
@FXML private JFXSpinner spinner;
private final TransitionHandler transitionHandler = new TransitionHandler(this);
private final VersionList<?> versionList;
private TaskExecutor executor;
public VersionsPage(WizardController controller, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.controller = controller;
this.gameVersion = gameVersion;
this.downloadProvider = downloadProvider;
this.libraryId = libraryId;
this.callback = callback;
this.versionList = downloadProvider.getVersionListById(libraryId);
FXUtilsKt.loadFXML(this, "/assets/fxml/download/versions.fxml");
getChildren().setAll(spinner);
list.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
controller.getSettings().put(libraryId, newValue.getRemoteVersion().getSelfVersion());
callback.run();
});
refresh();
}
@Override
public void refresh() {
executor = versionList.refreshAsync(downloadProvider).subscribe(Schedulers.javafx(), v -> {
versionList.getVersions(gameVersion).stream()
.sorted(RemoteVersion.RemoteVersionComparator.INSTANCE)
.forEach(version -> {
list.getItems().add(new VersionsPageItem(version));
});
transitionHandler.setContent(list, ContainerAnimations.FADE.getAnimationProducer());
});
}
@Override
public String getTitle() {
return "Choose a game version";
}
@Override
public void cleanup(Map<String, Object> settings) {
settings.remove(libraryId);
if (executor != null)
executor.cancel();
}
}

View File

@@ -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.download;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.ui.FXUtilsKt;
/**
* @author huangyuhui
*/
public final class VersionsPageItem extends StackPane {
private final RemoteVersion<?> remoteVersion;
@FXML
private Label lblSelfVersion;
@FXML
private Label lblGameVersion;
public VersionsPageItem(RemoteVersion<?> remoteVersion) {
this.remoteVersion = remoteVersion;
FXUtilsKt.loadFXML(this, "/assets/fxml/download/versions-list-item.fxml");
lblSelfVersion.setText(remoteVersion.getSelfVersion());
lblGameVersion.setText(remoteVersion.getGameVersion());
}
public RemoteVersion<?> getRemoteVersion() {
return remoteVersion;
}
}

View File

@@ -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.export;
import javafx.scene.Node;
import kotlin.Suppress;
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.List;
import java.util.Map;
import static org.jackhuang.hmcl.game.HMCLModpackManager.MODPACK_PREDICATE;
public final class ExportWizardProvider implements WizardProvider {
private final Profile profile;
private final String version;
public ExportWizardProvider(Profile profile, String version) {
this.profile = profile;
this.version = version;
}
@Override
public void start(Map<String, Object> settings) {
}
@Override
public Object finish(Map<String, Object> settings) {
return new HMCLModpackExportTask(profile.getRepository(), version, (List<String>) settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION),
new Modpack(
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
null,
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
null
), (File) settings.get(ModpackInfoPage.MODPACK_FILE));
}
@Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
switch (step) {
case 0: return new ModpackInfoPage(controller, version);
case 1: return new ModpackFileSelectionPage(controller, profile, version, MODPACK_PREDICATE);
default: throw new IllegalArgumentException("step");
}
}
@Override
public boolean cancel() {
return true;
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.export;
import com.jfoenix.controls.JFXTreeView;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.MainKt;
import org.jackhuang.hmcl.game.ModAdviser;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.FXUtilsKt;
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author huangyuhui
*/
public final class ModpackFileSelectionPage extends StackPane implements WizardPage {
private final WizardController controller;
private final String version;
private final ModAdviser adviser;
@FXML
private JFXTreeView<String> treeView;
private CheckBoxTreeItem<String> rootNode;
public ModpackFileSelectionPage(WizardController controller, Profile profile, String version, ModAdviser adviser) {
this.controller = controller;
this.version = version;
this.adviser = adviser;
FXUtilsKt.loadFXML(this, "/assets/fxml/modpack/selection.fxml");
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft");
treeView.setRoot(rootNode);
treeView.setSelectionModel(NoneMultipleSelectionModel.getInstance());
}
private CheckBoxTreeItem<String> getTreeItem(File file, String basePath) {
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
if (basePath.length() > "minecraft/".length()) {
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory());
if (file.isFile() && Objects.equals(FileUtils.getNameWithoutExtension(file), version))
state = ModAdviser.ModSuggestion.HIDDEN;
if (file.isDirectory() && Objects.equals(file.getName(), version + "-natives"))
state = ModAdviser.ModSuggestion.HIDDEN;
if (state == ModAdviser.ModSuggestion.HIDDEN)
return null;
}
CheckBoxTreeItem<String> node = new CheckBoxTreeItem<>(StringUtils.substringAfterLast(basePath, "/"));
if (state == ModAdviser.ModSuggestion.SUGGESTED)
node.setSelected(true);
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null)
for (File it : files) {
CheckBoxTreeItem<String> subNode = getTreeItem(it, basePath + "/" + it.getName());
if (subNode != null) {
node.setSelected(subNode.isSelected() || node.isSelected());
if (!subNode.isSelected())
node.setIndeterminate(true);
node.getChildren().add(subNode);
}
}
if (!node.isSelected()) node.setIndeterminate(false);
// Empty folder need not to be displayed.
if (node.getChildren().isEmpty())
return null;
}
HBox graphic = new HBox();
CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().bindBidirectional(node.selectedProperty());
checkBox.indeterminateProperty().bindBidirectional(node.indeterminateProperty());
graphic.getChildren().add(checkBox);
if (TRANSLATION.containsKey(basePath)) {
Label comment = new Label();
comment.setText(TRANSLATION.get(basePath));
comment.setStyle("-fx-text-fill: gray;");
comment.setMouseTransparent(true);
graphic.getChildren().add(comment);
}
graphic.setPickOnBounds(false);
node.setExpanded("minecraft".equals(basePath));
node.setGraphic(graphic);
return node;
}
private void getFilesNeeded(CheckBoxTreeItem<String> node, String basePath, List<String> list) {
if (node == null) return;
if (node.isSelected()) {
if (basePath.length() > "minecraft/".length())
list.add(StringUtils.substringAfter(basePath, "minecraft/"));
for (TreeItem<String> child : node.getChildren()) {
if (child instanceof CheckBoxTreeItem)
getFilesNeeded(((CheckBoxTreeItem<String>) child), basePath + "/" + child.getValue(), list);
}
}
}
@Override
public void cleanup(Map<String, Object> settings) {
controller.getSettings().remove(MODPACK_FILE_SELECTION);
}
public void onNext() {
LinkedList<String> list = new LinkedList<>();
getFilesNeeded(rootNode, "minecraft", list);
controller.getSettings().put(MODPACK_FILE_SELECTION, list);
controller.onFinish();
}
@Override
public String getTitle() {
return MainKt.i18n("modpack.wizard.step.2.title");
}
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
private static final Map<String, String> TRANSLATION = Lang.mapOf(
new Pair<>("minecraft/servers.dat", MainKt.i18n("modpack.files.servers_dat")),
new Pair<>("minecraft/saves", MainKt.i18n("modpack.files.saves")),
new Pair<>("minecraft/mods", MainKt.i18n("modpack.files.mods")),
new Pair<>("minecraft/config", MainKt.i18n("modpack.files.config")),
new Pair<>("minecraft/liteconfig", MainKt.i18n("modpack.files.liteconfig")),
new Pair<>("minecraft/resourcepacks", MainKt.i18n("modpack.files.resourcepacks")),
new Pair<>("minecraft/resources", MainKt.i18n("modpack.files.resourcepacks")),
new Pair<>("minecraft/options.txt", MainKt.i18n("modpack.files.options_txt")),
new Pair<>("minecraft/optionsshaders.txt", MainKt.i18n("modpack.files.optionsshaders_txt")),
new Pair<>("minecraft/mods/VoxelMods", MainKt.i18n("modpack.files.mods.voxelmods")),
new Pair<>("minecraft/dumps", MainKt.i18n("modpack.files.dumps")),
new Pair<>("minecraft/blueprints", MainKt.i18n("modpack.files.blueprints")),
new Pair<>("minecraft/scripts", MainKt.i18n("modpack.files.scripts"))
);
}

View File

@@ -0,0 +1,111 @@
/*
* 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.export;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextArea;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.controls.JFXToggleButton;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.MainKt;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtilsKt;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import java.io.File;
import java.util.Map;
import java.util.Optional;
public final class ModpackInfoPage extends StackPane implements WizardPage {
private final WizardController controller;
@FXML
private Label lblVersionName;
@FXML
private JFXTextField txtModpackName;
@FXML
private JFXTextField txtModpackAuthor;@FXML
private JFXTextField txtModpackVersion;@FXML
private JFXTextArea txtModpackDescription;
@FXML
private JFXToggleButton chkIncludeLauncher;@FXML
private JFXButton btnNext;@FXML
private ScrollPane scroll;
public ModpackInfoPage(WizardController controller, String version) {
this.controller = controller;
FXUtilsKt.loadFXML(this, "/assets/fxml/modpack/info.fxml");
FXUtilsKt.smoothScrolling(scroll);
txtModpackName.setText(version);
txtModpackName.textProperty().addListener(e -> checkValidation());
txtModpackAuthor.textProperty().addListener(e -> checkValidation());
txtModpackVersion.textProperty().addListener(e -> checkValidation());
txtModpackAuthor.setText(Optional.ofNullable(Settings.INSTANCE.getSelectedAccount()).map(Account::getUsername).orElse(""));
lblVersionName.setText(version);
}
private void checkValidation() {
btnNext.setDisable(!txtModpackName.validate() || !txtModpackVersion.validate() || !txtModpackAuthor.validate());
}
public void onNext() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(MainKt.i18n("modpack.wizard.step.initialization.save"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(MainKt.i18n("modpack"), "*.zip"));
File file = fileChooser.showSaveDialog(Controllers.INSTANCE.getStage());
if (file == null) {
Controllers.INSTANCE.navigate(null);
return;
}
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
controller.getSettings().put(MODPACK_VERSION, txtModpackVersion.getText());
controller.getSettings().put(MODPACK_AUTHOR, txtModpackAuthor.getText());
controller.getSettings().put(MODPACK_FILE, file);
controller.getSettings().put(MODPACK_DESCRIPTION, txtModpackDescription.getText());
controller.getSettings().put(MODPACK_INCLUDE_LAUNCHER, chkIncludeLauncher.isSelected());
controller.onNext();
}
@Override
public void cleanup(Map<String, Object> settings) {
controller.getSettings().remove(MODPACK_NAME);
controller.getSettings().remove(MODPACK_VERSION);
controller.getSettings().remove(MODPACK_AUTHOR);
controller.getSettings().remove(MODPACK_DESCRIPTION);
controller.getSettings().remove(MODPACK_INCLUDE_LAUNCHER);
controller.getSettings().remove(MODPACK_FILE);
}
@Override
public String getTitle() {
return MainKt.i18n("modpack.wizard.step.1.title");
}
public static final String MODPACK_NAME = "modpack.name";
public static final String MODPACK_VERSION = "modpack.version";
public static final String MODPACK_AUTHOR = "modpack.author";
public static final String MODPACK_DESCRIPTION = "modpack.description";
public static final String MODPACK_INCLUDE_LAUNCHER = "modpack.include_launcher";
public static final String MODPACK_FILE = "modpack.file";
}

View File

@@ -15,12 +15,13 @@
* 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.wizard
package org.jackhuang.hmcl.ui.wizard;
import javafx.beans.property.StringProperty
import javafx.beans.property.StringProperty;
interface DecoratorPage {
val titleProperty: StringProperty
public interface DecoratorPage {
StringProperty titleProperty();
fun onClose() {}
}
default void onClose() {
}
}

View File

@@ -15,22 +15,26 @@
* 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.wizard;
import com.jfoenix.controls.JFXProgressBar
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import java.util.Map;
class LaunchingStepsPane(): StackPane() {
@FXML lateinit var pgsTasks: JFXProgressBar
@FXML lateinit var lblCurrentState: Label
@FXML lateinit var lblSteps: Label
init {
loadFXML("/assets/fxml/launching-steps.fxml")
/**
* @author huangyuhui
*/
public abstract class DeferredWizardResult {
private final boolean canAbort;
limitHeight(200.0)
limitWidth(400.0)
public DeferredWizardResult() {
this(false);
}
}
public DeferredWizardResult(boolean canAbort) {
this.canAbort = canAbort;
}
public abstract void start(Map<String, Object> settings, ResultProgressHandle progressHandle);
public void abort() {
}
}

View File

@@ -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.wizard
package org.jackhuang.hmcl.ui.wizard;
/**
* A controller for the progress bar shown in the user interface. Used in
@@ -24,21 +24,21 @@ package org.jackhuang.hmcl.ui.wizard
* will take a while and needs to happen on a background thread.
* @author Tim Boudreau
*/
interface ResultProgressHandle {
public interface ResultProgressHandle {
/**
* Set the current position and total number of steps. Note it is
* inadvisable to be holding any locks when calling this method, as it
* may immediately update the GUI using
* `EventQueue.invokeAndWait()`.
*
* @param currentStep the current step in the progress of computing the
* * result.
* *
* @param totalSteps the total number of steps. Must be greater than
* * or equal to currentStep.
*/
fun setProgress(currentStep: Int, totalSteps: Int)
void setProgress(int currentStep, int totalSteps);
/**
* Set the current position and total number of steps, and description
@@ -55,7 +55,7 @@ interface ResultProgressHandle {
* @param totalSteps the total number of steps. Must be greater than
* * or equal to currentStep.
*/
fun setProgress(description: String, currentStep: Int, totalSteps: Int)
void setProgress(String description, int currentStep, int totalSteps);
/**
* Set the status as "busy" - a rotating icon will be displayed instead
@@ -67,7 +67,7 @@ interface ResultProgressHandle {
* @param description Text to describe what is being done, which can
* * be displayed in the UI.
*/
fun setBusy(description: String)
void setBusy(String description);
/**
* Call this method when the computation is complete, and pass in the
@@ -78,7 +78,7 @@ interface ResultProgressHandle {
* called, a runtime exception may be thrown.
* @param result the Object which was computed, if any.
*/
fun finished(result: Any)
void finished(Object result);
/**
* Call this method if computation fails. The message may be some text
@@ -93,12 +93,12 @@ interface ResultProgressHandle {
* @param canNavigateBack whether or not the Prev button should be
* * enabled.
*/
fun failed(message: String, canNavigateBack: Boolean)
void failed(String message, boolean canNavigateBack);
/**
* Returns true if the computation is still running, i.e., if neither finished or failed have been called.
*
* @return true if there is no result yet.
*/
val isRunning: Boolean
}
boolean isRunning();
}

View File

@@ -15,19 +15,18 @@
* 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.wizard
package org.jackhuang.hmcl.ui.wizard;
import javafx.scene.Node
import org.jackhuang.hmcl.task.Task
import javafx.scene.Node;
import org.jackhuang.hmcl.task.Task;
interface WizardDisplayer {
fun onStart()
fun onEnd()
fun onCancel()
import java.util.Map;
fun navigateTo(page: Node, nav: Navigation.NavigationDirection)
fun handleDeferredWizardResult(settings: Map<String, Any>, deferredResult: DeferredWizardResult)
fun handleTask(settings: Map<String, Any>, task: Task)
}
public interface WizardDisplayer {
void onStart();
void onEnd();
void onCancel();
void navigateTo(Node page, Navigation.NavigationDirection nav);
void handleDeferredWizardResult(Map<String, Object> settings, DeferredWizardResult deferredWizardResult);
void handleTask(Map<String, Object> settings, Task task);
}

View File

@@ -15,15 +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.ui.wizard
package org.jackhuang.hmcl.ui.wizard;
enum class WizardNavigationResult {
public enum WizardNavigationResult {
PROCEED {
override val deferredComputation = false
@Override
public boolean getDeferredComputation() {
return true;
}
},
DENY {
override val deferredComputation = false
@Override
public boolean getDeferredComputation() {
return false;
}
};
abstract val deferredComputation: Boolean
}
public abstract boolean getDeferredComputation();
}

View File

@@ -15,10 +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.wizard
package org.jackhuang.hmcl.ui.wizard;
abstract class DeferredWizardResult(val canAbort: Boolean = false) {
import java.util.Map;
abstract fun start(settings: Map<String, Any>, progressHandle: ResultProgressHandle)
open fun abort() { }
}
public interface WizardPage {
default void onNavigate(Map<String, Object> settings) {
}
void cleanup(Map<String, Object> settings);
String getTitle();
}

View File

@@ -15,14 +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.wizard;
import javafx.util.StringConverter
import javafx.scene.Node;
class SafeIntStringConverter : StringConverter<Int>() {
/** {@inheritDoc} */
override fun fromString(value: String?) = value?.toIntOrNull()
import java.util.Map;
/** {@inheritDoc} */
override fun toString(value: Int?) = value?.toString() ?: ""
}
public interface WizardProvider {
void start(Map<String, Object> settings);
Object finish(Map<String, Object> settings);
Node createPage(WizardController controller, int step, Map<String, Object> settings);
boolean cancel();
}

View File

@@ -97,6 +97,6 @@ class Main : Application() {
Schedulers.shutdown()
}
val RESOURCE_BUNDLE = Settings.locale.resourceBundle
val RESOURCE_BUNDLE = Settings.INSTANCE.locale.resourceBundle
}
}

View File

@@ -1,134 +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.setting
import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.util.JavaVersion
import java.util.*
class Config {
@SerializedName("last")
var selectedProfile: String = ""
set(value) {
field = value
Settings.save()
}
@SerializedName("bgpath")
var bgpath: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("commonpath")
var commonpath: String = Main.getMinecraftDirectory().absolutePath
set(value) {
field = value
Settings.save()
}
@SerializedName("proxyType")
var proxyType: Int = 0
set(value) {
field = value
Settings.save()
}
@SerializedName("proxyHost")
var proxyHost: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("proxyPort")
var proxyPort: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("proxyUserName")
var proxyUserName: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("proxyPassword")
var proxyPassword: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("theme")
var theme: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("java")
var java: List<JavaVersion>? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("localization")
var localization: String? = null
set(value) {
field = value
Settings.save()
}
@SerializedName("downloadtype")
var downloadtype: Int = 0
set(value) {
field = value
Settings.save()
}
@SerializedName("configurations")
var configurations: MutableMap<String, Profile> = TreeMap()
set(value) {
field = value
Settings.save()
}
@SerializedName("accounts")
var accounts: MutableMap<String, MutableMap<Any, Any>> = TreeMap()
set(value) {
field = value
Settings.save()
}
@SerializedName("selectedAccount")
var selectedAccount: String = ""
set(value) {
field = value
Settings.save()
}
@SerializedName("fontFamily")
var fontFamily: String? = "Consolas"
set(value) {
field = value
Settings.save()
}
@SerializedName("fontSize")
var fontSize: Double = 12.0
set(value) {
field = value
Settings.save()
}
@SerializedName("logLines")
var logLines: Int = 100
set(value) {
field = value
Settings.save()
}
}

View File

@@ -1,349 +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.setting
import com.google.gson.GsonBuilder
import javafx.beans.InvalidationListener
import javafx.scene.text.Font
import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Logging.LOG
import java.io.File
import java.io.IOException
import java.net.Authenticator
import java.net.InetSocketAddress
import java.net.PasswordAuthentication
import java.net.Proxy
import java.util.*
import java.util.logging.Level
object Settings {
val GSON = GsonBuilder()
.registerTypeAdapter(VersionSetting::class.java, VersionSetting.Serializer.INSTANCE)
.registerTypeAdapter(Profile::class.java, Profile.Serializer.INSTANCE)
.registerTypeAdapter(File::class.java, FileTypeAdapter.INSTANCE)
.setPrettyPrinting().create()
const val DEFAULT_PROFILE = "Default"
const val HOME_PROFILE = "Home"
val SETTINGS_FILE: File = File("hmcl.json").absoluteFile
private val SETTINGS: Config
private val accounts = mutableMapOf<String, Account>()
init {
SETTINGS = initSettings();
loop@for ((name, settings) in SETTINGS.accounts) {
val factory = Accounts.ACCOUNT_FACTORY[settings["type"] ?: ""]
if (factory == null) {
SETTINGS.accounts.remove(name)
continue@loop
}
val account: Account
try {
account = factory.fromStorage(settings)
} catch (e: Exception) {
SETTINGS.accounts.remove(name)
// storage is malformed, delete.
continue@loop
}
if (account.username != name) {
SETTINGS.accounts.remove(name)
continue
}
accounts[name] = account
}
save()
if (!getProfileMap().containsKey(DEFAULT_PROFILE))
getProfileMap().put(DEFAULT_PROFILE, Profile());
for ((name, profile) in getProfileMap().entries) {
profile.name = name
profile.addPropertyChangedListener(InvalidationListener { save() })
}
ignoreException {
Runtime.getRuntime().addShutdownHook(Thread(this::save))
}
}
private fun initSettings(): Config {
var c = Config()
if (SETTINGS_FILE.exists())
try {
val str = SETTINGS_FILE.readText()
if (str.trim() == "")
LOG.finer("Settings file is empty, use the default settings.")
else {
val d = GSON.fromJson(str, Config::class.java)
if (d != null)
c = d
}
LOG.finest("Initialized settings.")
} catch (e: Exception) {
LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e)
}
else {
LOG.config("No settings file here, may be first loading.")
if (!c.configurations.containsKey(HOME_PROFILE))
c.configurations[HOME_PROFILE] = Profile(HOME_PROFILE, Main.getMinecraftDirectory())
}
return c
}
fun save() {
try {
SETTINGS.accounts.clear()
for ((name, account) in accounts) {
val storage = account.toStorage()
storage["type"] = Accounts.getAccountType(account)
SETTINGS.accounts[name] = storage
}
SETTINGS_FILE.writeText(GSON.toJson(SETTINGS))
} catch (ex: IOException) {
LOG.log(Level.SEVERE, "Failed to save config", ex)
}
}
val commonPathProperty = object : ImmediateStringProperty(this, "commonPath", SETTINGS.commonpath) {
override fun invalidated() {
super.invalidated()
SETTINGS.commonpath = get()
}
}
var commonPath: String by commonPathProperty
var locale: Locales.SupportedLocale = Locales.getLocaleByName(SETTINGS.localization)
set(value) {
field = value
SETTINGS.localization = Locales.getNameByLocale(value)
}
var proxy: Proxy = Proxy.NO_PROXY
var proxyType: Proxy.Type? = Proxies.getProxyType(SETTINGS.proxyType)
set(value) {
field = value
SETTINGS.proxyType = Proxies.PROXIES.indexOf(value)
loadProxy()
}
var proxyHost: String? get() = SETTINGS.proxyHost; set(value) { SETTINGS.proxyHost = value }
var proxyPort: String? get() = SETTINGS.proxyPort; set(value) { SETTINGS.proxyPort = value }
var proxyUser: String? get() = SETTINGS.proxyUserName; set(value) { SETTINGS.proxyUserName = value }
var proxyPass: String? get() = SETTINGS.proxyPassword; set(value) { SETTINGS.proxyPassword = value }
private fun loadProxy() {
val host = proxyHost
val port = proxyPort?.toIntOrNull()
if (host == null || host.isBlank() || port == null)
proxy = Proxy.NO_PROXY
else {
System.setProperty("http.proxyHost", proxyHost)
System.setProperty("http.proxyPort", proxyPort)
if (proxyType == Proxy.Type.DIRECT)
proxy = Proxy.NO_PROXY
else
proxy = Proxy(proxyType, InetSocketAddress(host, port))
val user = proxyUser
val pass = proxyPass
if (user != null && user.isNotBlank() && pass != null && pass.isNotBlank()) {
System.setProperty("http.proxyUser", user)
System.setProperty("http.proxyPassword", pass)
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(user, pass.toCharArray())
}
})
}
}
}
init { loadProxy() }
var font: Font
get() = Font.font(SETTINGS.fontFamily, SETTINGS.fontSize)
set(value) {
SETTINGS.fontFamily = value.family
SETTINGS.fontSize = value.size
}
var logLines: Int
get() = maxOf(SETTINGS.logLines, 100)
set(value) {
SETTINGS.logLines = value
}
var downloadProvider: DownloadProvider
get() = when (SETTINGS.downloadtype) {
0 -> MojangDownloadProvider.INSTANCE
1 -> BMCLAPIDownloadProvider.INSTANCE
else -> MojangDownloadProvider.INSTANCE
}
set(value) {
SETTINGS.downloadtype = when (value) {
MojangDownloadProvider.INSTANCE -> 0
BMCLAPIDownloadProvider.INSTANCE -> 1
else -> 0
}
}
/****************************************
* ACCOUNTS *
****************************************/
val selectedAccountProperty = object : ImmediateObjectProperty<Account?>(this, "selectedAccount", getAccount(SETTINGS.selectedAccount)) {
override fun get(): Account? {
val a = super.get()
if (a == null || !accounts.containsKey(a.username)) {
val acc = if (accounts.isEmpty()) null else accounts.values.first()
set(acc)
return acc
} else return a
}
override fun set(newValue: Account?) {
if (newValue == null || accounts.containsKey(newValue.username)) {
super.set(newValue)
}
}
override fun invalidated() {
super.invalidated()
SETTINGS.selectedAccount = value?.username ?: ""
}
}
var selectedAccount: Account? by selectedAccountProperty
fun addAccount(account: Account) {
accounts[account.username] = account
}
fun getAccount(name: String): Account? {
return accounts[name]
}
fun getAccounts(): Map<String, Account> {
return Collections.unmodifiableMap(accounts)
}
fun deleteAccount(name: String) {
accounts.remove(name)
selectedAccountProperty.get()
}
/****************************************
* PROFILES *
****************************************/
var selectedProfile: Profile
get() {
if (!hasProfile(SETTINGS.selectedProfile)) {
SETTINGS.selectedProfile = DEFAULT_PROFILE
Schedulers.computation().schedule { onProfileChanged() }
}
return getProfile(SETTINGS.selectedProfile)
}
set(value) {
if (hasProfile(value.name) && value.name != SETTINGS.selectedProfile) {
SETTINGS.selectedProfile = value.name
Schedulers.computation().schedule { onProfileChanged() }
}
}
fun getProfile(name: String?): Profile {
var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE]
if (p == null)
if (getProfileMap().containsKey(DEFAULT_PROFILE))
p = getProfileMap()[DEFAULT_PROFILE]!!
else {
p = Profile()
getProfileMap().put(DEFAULT_PROFILE, p)
}
return p
}
fun hasProfile(name: String?): Boolean {
return getProfileMap().containsKey(name ?: DEFAULT_PROFILE)
}
fun getProfileMap(): MutableMap<String, Profile> {
return SETTINGS.configurations
}
fun getProfiles(): Collection<Profile> {
return getProfileMap().values.filter { t -> t.name.isNotBlank() }
}
fun putProfile(ver: Profile?): Boolean {
if (ver == null || ver.name.isBlank() || getProfileMap().containsKey(ver.name))
return false
getProfileMap().put(ver.name, ver)
return true
}
fun deleteProfile(ver: Profile): Boolean {
return deleteProfile(ver.name)
}
fun deleteProfile(ver: String): Boolean {
if (DEFAULT_PROFILE == ver) {
return false
}
val flag = getProfileMap().remove(ver) != null
if (flag)
Schedulers.computation().schedule { onProfileLoading() }
return flag
}
internal fun onProfileChanged() {
selectedProfile.repository.refreshVersions()
EventBus.EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
}
/**
* Start profiles loading process.
* Invoked by loading GUI phase.
*/
fun onProfileLoading() {
EventBus.EVENT_BUS.fireEvent(ProfileLoadingEvent(SETTINGS))
onProfileChanged()
}
}

View File

@@ -76,7 +76,7 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane(
icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 - 32.0 }, header.boundsInParentProperty(), icon.heightProperty()))
chkSelected.properties["account"] = account
chkSelected.isSelected = Settings.selectedAccount == account
chkSelected.isSelected = Settings.INSTANCE.selectedAccount == account
lblUser.text = account.username
lblType.text = accountType(account)

View File

@@ -44,7 +44,8 @@ import org.jackhuang.hmcl.util.onChangeAndOperate
import org.jackhuang.hmcl.util.taskResult
class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
private val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
override fun titleProperty() = titleProperty
@FXML lateinit var scrollPane: ScrollPane
@FXML lateinit var masonryPane: JFXMasonryPane
@@ -74,7 +75,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
txtPassword.setOnAction { onCreationAccept() }
txtUsername.setOnAction { onCreationAccept() }
Settings.selectedAccountProperty.onChangeAndOperate { account ->
Settings.INSTANCE.selectedAccountProperty().onChangeAndOperate { account ->
masonryPane.children.forEach { node ->
if (node is AccountItem) {
node.chkSelected.isSelected = account?.username == node.lblUser.text
@@ -84,7 +85,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
loadAccounts()
if (Settings.getAccounts().isEmpty())
if (Settings.INSTANCE.getAccounts().isEmpty())
addNewAccount()
}
@@ -92,12 +93,12 @@ class AccountsPage() : StackPane(), DecoratorPage {
val children = mutableListOf<Node>()
var i = 0
val group = ToggleGroup()
for ((_, account) in Settings.getAccounts()) {
for ((_, account) in Settings.INSTANCE.getAccounts()) {
children += buildNode(++i, account, group)
}
group.selectedToggleProperty().onChange {
if (it != null)
Settings.selectedAccount = it.properties["account"] as Account
Settings.INSTANCE.selectedAccount = it.properties["account"] as Account
}
masonryPane.resetChildren(children)
Platform.runLater {
@@ -109,7 +110,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node {
return AccountItem(i, account, group).apply {
btnDelete.setOnMouseClicked {
Settings.deleteAccount(account.username)
Settings.INSTANCE.deleteAccount(account.username)
Platform.runLater(this@AccountsPage::loadAccounts)
}
}
@@ -135,7 +136,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
else -> throw UnsupportedOperationException()
}
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.proxy)
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.proxy)
account
} catch (e: Exception) {
e
@@ -143,7 +144,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
}.subscribe(Schedulers.javafx()) {
val account: Any = it["create_account"]
if (account is Account) {
Settings.addAccount(account)
Settings.INSTANCE.addAccount(account)
dialog.close()
loadAccounts()
} else if (account is InvalidCredentialsException) {

View File

@@ -1,40 +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 javafx.scene.layout.StackPane
import javafx.scene.layout.VBox
import javafx.scene.paint.Color
import javafx.scene.shape.Rectangle
import javafx.scene.text.Text
class ClassTitle(val text: String) : StackPane() {
init {
val vbox = VBox()
vbox.children += Text(text).apply {
}
vbox.children += Rectangle().apply {
widthProperty().bind(vbox.widthProperty())
height = 1.0
fill = Color.GRAY
}
children.setAll(vbox)
styleClass += "class-title"
}
}

View File

@@ -47,7 +47,7 @@ object Controllers {
decorator.showPage(null)
leftPaneController = LeftPaneController(decorator.leftPane)
Settings.onProfileLoading()
Settings.INSTANCE.onProfileLoading()
task { JavaVersion.initialize() }.start()
decorator.isCustomMaximize = false

View File

@@ -388,7 +388,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva
titleLabel.text = prefix + content.title
if (content is DecoratorPage)
titleLabel.textProperty().bind(content.titleProperty)
titleLabel.textProperty().bind(content.titleProperty())
}
var category: String? = null
@@ -418,6 +418,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva
return dialog
}
@JvmOverloads
fun startWizard(wizardProvider: WizardProvider, category: String? = null) {
this.category = category
wizardController.provider = wizardProvider

View File

@@ -1,49 +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 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 java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicReference
object DialogController {
fun logIn(account: Account): AuthInfo? {
if (account is YggdrasilAccount) {
val latch = CountDownLatch(1)
val res = AtomicReference<AuthInfo>(null)
runOnUiThread {
val pane = YggdrasilAccountLoginPane(account, success = {
res.set(it)
latch.countDown()
Controllers.closeDialog()
}, failed = {
latch.countDown()
Controllers.closeDialog()
})
pane.dialog = Controllers.dialog(pane)
}
latch.await()
return res.get() ?: throw SilentException()
}
return null
}
}

View File

@@ -44,6 +44,8 @@ import javafx.util.Duration
import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Logging.LOG
import org.jackhuang.hmcl.util.ReflectionHelper.call
import org.jackhuang.hmcl.util.ReflectionHelper.construct
import java.io.File
import java.io.IOException
import java.util.logging.Level
@@ -178,7 +180,7 @@ fun unbindEnum(comboBox: JFXComboBox<*>) {
* return value of `interpolate()` is `endValue` only when the
* input `fraction` is 1.0, and `startValue` otherwise.
*/
val SINE: Interpolator = object : Interpolator() {
@JvmField val SINE: Interpolator = object : Interpolator() {
override fun curve(t: Double): Double {
return Math.sin(t * Math.PI / 2)
}
@@ -234,9 +236,8 @@ fun inputDialog(title: String, contentText: String, headerText: String? = null,
fun Node.installTooltip(openDelay: Double = 1000.0, visibleDelay: Double = 5000.0, closeDelay: Double = 200.0, tooltip: Tooltip) {
try {
Class.forName("javafx.scene.control.Tooltip\$TooltipBehavior")
.construct(Duration(openDelay), Duration(visibleDelay), Duration(closeDelay), false)!!
.call("install", this, tooltip)
call(construct(Class.forName("javafx.scene.control.Tooltip\$TooltipBehavior"), Duration(openDelay), Duration(visibleDelay), Duration(closeDelay), false),
"install", this, tooltip);
} catch (e: Throwable) {
LOG.log(Level.SEVERE, "Cannot install tooltip by reflection", e)
Tooltip.install(this, tooltip)

View File

@@ -1,86 +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 javafx.fxml.FXML
import javafx.scene.control.ScrollPane
import javafx.scene.layout.VBox
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask
import org.jackhuang.hmcl.game.GameVersion.minecraftVersion
import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.ui.download.InstallWizardProvider
import org.jackhuang.hmcl.util.task
import java.util.*
class InstallerController {
private lateinit var profile: Profile
private lateinit var versionId: String
private lateinit var version: Version
@FXML lateinit var scrollPane: ScrollPane
@FXML lateinit var contentPane: VBox
private var forge: String? = null
private var liteloader: String? = null
private var optifine: String? = null
fun initialize() {
scrollPane.smoothScrolling()
}
fun loadVersion(profile: Profile, versionId: String) {
this.profile = profile
this.versionId = versionId
this.version = profile.repository.getVersion(versionId).resolve(profile.repository)
contentPane.children.clear()
forge = null
liteloader = null
optifine = null
for (library in version.libraries) {
val removeAction = { _: InstallerItem ->
val newList = LinkedList(version.libraries)
newList.remove(library)
VersionJsonSaveTask(profile.repository, version.setLibraries(newList))
.with(task { profile.repository.refreshVersions() })
.with(task(Schedulers.javafx()) { loadVersion(this.profile, this.versionId) })
.start()
}
if (library.groupId.equals("net.minecraftforge", ignoreCase = true) && library.artifactId.equals("forge", ignoreCase = true)) {
contentPane.children += InstallerItem("Forge", library.version, removeAction)
forge = library.version
} else if (library.groupId.equals("com.mumfrey", ignoreCase = true) && library.artifactId.equals("liteloader", ignoreCase = true)) {
contentPane.children += InstallerItem("LiteLoader", library.version, removeAction)
liteloader = library.version
} else if ((library.groupId.equals("net.optifine", ignoreCase = true) || library.groupId.equals("optifine", ignoreCase = true)) && library.artifactId.equals("optifine", ignoreCase = true)) {
contentPane.children += InstallerItem("OptiFine", library.version, removeAction)
optifine = library.version
}
}
}
fun onAdd() {
// TODO: if minecraftVersion returns null.
val gameVersion = minecraftVersion(profile.repository.getVersionJar(version)) ?: return
Controllers.decorator.startWizard(InstallWizardProvider(profile, gameVersion, version, forge, liteloader, optifine))
}
}

View File

@@ -1,24 +0,0 @@
package org.jackhuang.hmcl.ui
import com.jfoenix.effects.JFXDepthManager
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.layout.BorderPane
class InstallerItem(artifact: String, version: String, private val deleteCallback: (InstallerItem) -> Unit) : BorderPane() {
@FXML lateinit var lblInstallerArtifact: Label
@FXML lateinit var lblInstallerVersion: Label
init {
loadFXML("/assets/fxml/version/installer-item.fxml")
style = "-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"
JFXDepthManager.setDepth(this, 1)
lblInstallerArtifact.text = artifact
lblInstallerVersion.text = version
}
fun onDelete() {
deleteCallback(this)
}
}

View File

@@ -19,10 +19,10 @@ package org.jackhuang.hmcl.ui
import javafx.scene.layout.VBox
import javafx.scene.paint.Paint
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.event.ProfileChangedEvent
import org.jackhuang.hmcl.event.ProfileLoadingEvent
import org.jackhuang.hmcl.game.AccountHelper
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings
@@ -65,29 +65,27 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
Controllers.decorator.showPage(ProfilePage(null))
}
Settings.selectedAccountProperty.onChangeAndOperate {
Settings.INSTANCE.selectedAccountProperty().onChangeAndOperate {
if (it == null) {
accountItem.lblVersionName.text = "mojang@mojang.com"
accountItem.lblGameVersion.text = "Yggdrasil"
accountItem.setVersionName("mojang@mojang.com")
accountItem.setGameVersion("Yggdrasil")
} else {
accountItem.lblVersionName.text = it.username
accountItem.lblGameVersion.text = accountType(it)
accountItem.setVersionName(it.username)
accountItem.setGameVersion(accountType(it))
}
if (it is YggdrasilAccount) {
accountItem.imageView.image = AccountHelper.getSkin(it, 4.0)
accountItem.imageView.viewport = AccountHelper.getViewport(4.0)
accountItem.setImage(AccountHelper.getSkin(it, 4.0), AccountHelper.getViewport(4.0))
} else {
accountItem.imageView.image = DEFAULT_ICON
accountItem.imageView.viewport = null
accountItem.setImage(DEFAULT_ICON, null)
}
}
if (Settings.getAccounts().isEmpty())
if (Settings.INSTANCE.getAccounts().isEmpty())
Controllers.navigate(AccountsPage())
}
fun onProfileChanged(event: ProfileChangedEvent) {
val profile = event.value
val profile = event.profile
profilePane.children
.filter { it is RipplerContainer && it.properties["profile"] is Pair<*, *> }
@@ -96,7 +94,7 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
fun onProfilesLoading() {
val list = LinkedList<RipplerContainer>()
Settings.getProfiles().forEach { profile ->
Settings.INSTANCE.profiles.forEach { profile ->
val item = VersionListItem(profile.name)
val ripplerContainer = RipplerContainer(item)
item.onSettingsButtonClicked {
@@ -107,7 +105,7 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
// clean selected property
profilePane.children.forEach { if (it is RipplerContainer) it.selected = false }
ripplerContainer.selected = true
Settings.selectedProfile = profile
Settings.INSTANCE.selectedProfile = profile
}
ripplerContainer.properties["profile"] = profile.name to item
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty())

View File

@@ -21,13 +21,14 @@ import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXMasonryPane
import javafx.application.Platform
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.fxml.FXML
import javafx.scene.Node
import javafx.scene.image.Image
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.event.ProfileChangedEvent
import org.jackhuang.hmcl.event.ProfileLoadingEvent
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.game.GameVersion.minecraftVersion
import org.jackhuang.hmcl.game.LauncherHelper
@@ -43,7 +44,8 @@ import org.jackhuang.hmcl.util.plusAssign
* @see /assets/fxml/main.fxml
*/
class MainPage : StackPane(), DecoratorPage {
override val titleProperty = SimpleStringProperty(this, "title", i18n("launcher.title.main"))
private val titleProperty = SimpleStringProperty(this, "title", i18n("launcher.title.main"))
override fun titleProperty() = titleProperty
@FXML lateinit var btnRefresh: JFXButton
@FXML lateinit var btnAdd: JFXButton
@@ -57,30 +59,31 @@ class MainPage : StackPane(), DecoratorPage {
EventBus.EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") }
btnRefresh.setOnMouseClicked { Settings.selectedProfile.repository.refreshVersions() }
btnRefresh.setOnMouseClicked { Settings.INSTANCE.selectedProfile.repository.refreshVersions() }
}
private fun buildNode(i: Int, profile: Profile, version: String, game: String): Node {
return VersionItem().apply {
lblGameVersion.text = game
lblVersionName.text = version
btnLaunch.setOnMouseClicked {
if (Settings.selectedAccount == null) {
setGameVersion(game)
setVersionName(version)
setOnLaunchButtonClicked {
if (Settings.INSTANCE.selectedAccount == null) {
Controllers.dialog(i18n("login.no_Player007"))
} else
LauncherHelper.INSTANCE.launch(version)
}
btnDelete.setOnMouseClicked {
setOnDeleteButtonClicked {
profile.repository.removeVersionFromDisk(version)
Platform.runLater { loadVersions() }
}
btnSettings.setOnMouseClicked {
setOnSettingsButtonClicked {
Controllers.decorator.showPage(Controllers.versionPane)
Controllers.versionPane.load(version, profile)
}
val iconFile = profile.repository.getVersionIcon(version)
if (iconFile.exists())
iconView.image = Image("file:" + iconFile.absolutePath)
setImage(Image("file:" + iconFile.absolutePath))
}
}
@@ -89,11 +92,11 @@ class MainPage : StackPane(), DecoratorPage {
}
fun onProfileChanged(event: ProfileChangedEvent) = runOnUiThread {
val profile = event.value
val profile = event.profile
loadVersions(profile)
}
private fun loadVersions(profile: Profile = Settings.selectedProfile) {
private fun loadVersions(profile: Profile = Settings.INSTANCE.selectedProfile) {
val children = mutableListOf<Node>()
var i = 0
profile.repository.versions.forEach { version ->

View File

@@ -1,37 +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.JFXDialog
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
class MessageDialogPane(val text: String, val dialog: JFXDialog): StackPane() {
@FXML lateinit var acceptButton: JFXButton
@FXML lateinit var content: Label
init {
loadFXML("/assets/fxml/message-dialog.fxml")
content.text = text
acceptButton.setOnMouseClicked {
dialog.close()
}
}
}

View File

@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXSpinner
import com.jfoenix.controls.JFXTabPane
import javafx.fxml.FXML
import javafx.scene.control.ScrollPane
@@ -38,6 +39,7 @@ class ModController {
@FXML lateinit var rootPane: StackPane
@FXML lateinit var modPane: VBox
@FXML lateinit var contentPane: StackPane
@FXML lateinit var spinner: JFXSpinner
lateinit var parentTab: JFXTabPane
private lateinit var modManager: ModManager
private lateinit var versionId: String
@@ -67,7 +69,7 @@ class ModController {
this.versionId = versionId
task {
synchronized(contentPane) {
runOnUiThread { rootPane.children -= contentPane }
runOnUiThread { rootPane.children -= contentPane; spinner.isVisible = true }
modManager.refreshMods(versionId)
// Surprisingly, if there are a great number of mods, this processing will cause a UI pause.
@@ -89,7 +91,7 @@ class ModController {
styleClass += "disabled"
}
}
runOnUiThread { rootPane.children += contentPane }
runOnUiThread { rootPane.children += contentPane; spinner.isVisible = false }
it["list"] = list
}
}.subscribe(Schedulers.javafx()) { variables ->

View File

@@ -1,66 +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.JFXCheckBox
import com.jfoenix.effects.JFXDepthManager
import javafx.geometry.Pos
import javafx.scene.control.Label
import javafx.scene.layout.BorderPane
import javafx.scene.layout.VBox
import org.jackhuang.hmcl.mod.ModInfo
import org.jackhuang.hmcl.util.onChange
class ModItem(info: ModInfo, private val deleteCallback: (ModItem) -> Unit) : BorderPane() {
val lblModFileName = Label().apply { style = "-fx-font-size: 15;" }
val lblModAuthor = Label().apply { style = "-fx-font-size: 10;" }
val chkEnabled = JFXCheckBox().apply { BorderPane.setAlignment(this, Pos.CENTER) }
init {
left = chkEnabled
center = VBox().apply {
BorderPane.setAlignment(this, Pos.CENTER)
children += lblModFileName
children += lblModAuthor
}
right = JFXButton().apply {
setOnMouseClicked { onDelete() }
styleClass += "toggle-icon4"
BorderPane.setAlignment(this, Pos.CENTER)
graphic = SVG.close("black", 15.0, 15.0)
}
style = "-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"
JFXDepthManager.setDepth(this, 1)
lblModFileName.text = info.fileName
lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.gameVersion}, Authors: ${info.authors}"
chkEnabled.isSelected = info.isActive
chkEnabled.selectedProperty().onChange {
info.activeProperty().set(it)
}
}
fun onDelete() {
deleteCallback(this)
}
}

View File

@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXTextField
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.fxml.FXML
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.i18n
@@ -34,8 +35,11 @@ import java.io.File
* @param profile null if creating a new profile.
*/
class ProfilePage(private val profile: Profile?): StackPane(), DecoratorPage {
override val titleProperty = SimpleStringProperty(this, "title",
private val titleProperty = SimpleStringProperty(this, "title",
if (profile == null) i18n("ui.newProfileWindow.title") else i18n("ui.label.profile") + " - " + profile.name)
override fun titleProperty() = titleProperty
private val locationProperty = SimpleStringProperty(this, "location",
profile?.gameDir?.absolutePath ?: "")
@FXML lateinit var txtProfileName: JFXTextField
@@ -61,7 +65,7 @@ class ProfilePage(private val profile: Profile?): StackPane(), DecoratorPage {
fun onDelete() {
if (profile != null) {
Settings.deleteProfile(profile)
Settings.INSTANCE.deleteProfile(profile)
Controllers.navigate(null)
}
}
@@ -75,10 +79,10 @@ class ProfilePage(private val profile: Profile?): StackPane(), DecoratorPage {
if (locationProperty.get().isNullOrBlank()) {
gameDir.onExplore()
}
Settings.putProfile(Profile(txtProfileName.text, File(locationProperty.get())))
Settings.INSTANCE.putProfile(Profile(txtProfileName.text, File(locationProperty.get())))
}
Settings.onProfileLoading()
Settings.INSTANCE.onProfileLoading()
Controllers.navigate(null)
}
}

View File

@@ -49,17 +49,17 @@ object SVG {
return svg
}
fun gear(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height)
fun back(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height)
fun close(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height)
fun dotsVertical(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height)
fun delete(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height)
fun accountEdit(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height)
fun expand(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height)
fun collapse(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height)
fun navigate(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = 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)
fun launch(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = 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)
fun pencil(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height)
fun refresh(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height)
fun folderOpen(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height)
@JvmStatic fun gear(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height)
@JvmStatic fun back(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height)
@JvmStatic fun close(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height)
@JvmStatic fun dotsVertical(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height)
@JvmStatic fun delete(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height)
@JvmStatic fun accountEdit(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height)
@JvmStatic fun expand(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height)
@JvmStatic fun collapse(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height)
@JvmStatic fun navigate(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = 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)
@JvmStatic fun launch(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = 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)
@JvmStatic fun pencil(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height)
@JvmStatic fun refresh(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height)
@JvmStatic fun folderOpen(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height)
}

View File

@@ -38,7 +38,9 @@ import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.onChange
class SettingsPage : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.launcher"))
private val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.launcher"))
override fun titleProperty() = titleProperty
@FXML lateinit var txtProxyHost: JFXTextField
@FXML lateinit var txtProxyPort: JFXTextField
@@ -59,56 +61,56 @@ class SettingsPage : StackPane(), DecoratorPage {
cboLanguage.limitWidth(400.0)
cboDownloadSource.limitWidth(400.0)
txtProxyHost.text = Settings.proxyHost
txtProxyHost.textProperty().onChange { Settings.proxyHost = it }
txtProxyHost.text = Settings.INSTANCE.proxyHost
txtProxyHost.textProperty().onChange { Settings.INSTANCE.proxyHost = it }
txtProxyPort.text = Settings.proxyPort
txtProxyPort.textProperty().onChange { Settings.proxyPort = it }
txtProxyPort.text = Settings.INSTANCE.proxyPort
txtProxyPort.textProperty().onChange { Settings.INSTANCE.proxyPort = it }
txtProxyUsername.text = Settings.proxyUser
txtProxyUsername.textProperty().onChange { Settings.proxyUser = it }
txtProxyUsername.text = Settings.INSTANCE.proxyUser
txtProxyUsername.textProperty().onChange { Settings.INSTANCE.proxyUser = it }
txtProxyPassword.text = Settings.proxyPass
txtProxyPassword.textProperty().onChange { Settings.proxyPass = it }
txtProxyPassword.text = Settings.INSTANCE.proxyPass
txtProxyPassword.textProperty().onChange { Settings.INSTANCE.proxyPass = it }
cboDownloadSource.selectionModel.select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.downloadProvider))
cboDownloadSource.selectionModel.select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.INSTANCE.downloadProvider))
cboDownloadSource.selectionModel.selectedIndexProperty().onChange {
Settings.downloadProvider = DownloadProviders.getDownloadProvider(it)
Settings.INSTANCE.downloadProvider = DownloadProviders.getDownloadProvider(it)
}
cboFont.selectionModel.select(Settings.font.family)
cboFont.selectionModel.select(Settings.INSTANCE.font.family)
cboFont.valueProperty().onChange {
val font = Font.font(it, Settings.font.size)
Settings.font = font
lblDisplay.style = "-fx-font: ${Settings.font.size} \"${font.family}\";"
val font = Font.font(it, Settings.INSTANCE.font.size)
Settings.INSTANCE.font = font
lblDisplay.style = "-fx-font: ${Settings.INSTANCE.font.size} \"${font.family}\";"
}
txtFontSize.text = Settings.font.size.toString()
txtFontSize.text = Settings.INSTANCE.font.size.toString()
txtFontSize.validators += Validator { it.toDoubleOrNull() != null }
txtFontSize.textProperty().onChange {
if (txtFontSize.validate()) {
val font = Font.font(Settings.font.family, it!!.toDouble())
Settings.font = font
lblDisplay.style = "-fx-font: ${font.size} \"${Settings.font.family}\";"
val font = Font.font(Settings.INSTANCE.font.family, it!!.toDouble())
Settings.INSTANCE.font = font
lblDisplay.style = "-fx-font: ${font.size} \"${Settings.INSTANCE.font.family}\";"
}
}
lblDisplay.style = "-fx-font: ${Settings.font.size} \"${Settings.font.family}\";"
lblDisplay.style = "-fx-font: ${Settings.INSTANCE.font.size} \"${Settings.INSTANCE.font.family}\";"
val list = FXCollections.observableArrayList<Label>()
for (locale in Locales.LOCALES) {
list += Label(locale.getName(Settings.locale.resourceBundle))
list += Label(locale.getName(Settings.INSTANCE.locale.resourceBundle))
}
cboLanguage.items = list
cboLanguage.selectionModel.select(Locales.LOCALES.indexOf(Settings.locale))
cboLanguage.selectionModel.select(Locales.LOCALES.indexOf(Settings.INSTANCE.locale))
cboLanguage.selectionModel.selectedIndexProperty().onChange {
Settings.locale = Locales.getLocale(it)
Settings.INSTANCE.locale = Locales.getLocale(it)
}
cboProxyType.selectionModel.select(Proxies.PROXIES.indexOf(Settings.proxyType))
cboProxyType.selectionModel.select(Proxies.PROXIES.indexOf(Settings.INSTANCE.proxyType))
cboProxyType.selectionModel.selectedIndexProperty().onChange {
Settings.proxyType = Proxies.getProxyType(it)
Settings.INSTANCE.proxyType = Proxies.getProxyType(it)
}
fileCommonLocation.setProperty(Settings.commonPathProperty)
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty())
}

View File

@@ -1,89 +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.JFXRadioButton
import javafx.beans.binding.Bindings
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ToggleGroup
import javafx.scene.effect.BlurType
import javafx.scene.effect.DropShadow
import javafx.scene.image.ImageView
import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox
import javafx.scene.paint.Color
import java.util.concurrent.Callable
class VersionItem() : StackPane() {
@FXML lateinit var icon: Pane
@FXML lateinit var content: VBox
@FXML lateinit var header: StackPane
@FXML lateinit var body: StackPane
@FXML lateinit var btnDelete: JFXButton
@FXML lateinit var btnSettings: JFXButton
@FXML lateinit var btnLaunch: JFXButton
@FXML lateinit var lblVersionName: Label
@FXML lateinit var lblGameVersion: Label
@FXML lateinit var iconView: ImageView
init {
loadFXML("/assets/fxml/version-item.fxml")
limitWidth(160.0)
limitHeight(156.0)
effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0)
btnSettings.graphic = SVG.gear("black", 15.0, 15.0)
btnDelete.graphic = SVG.delete("black", 15.0, 15.0)
btnLaunch.graphic = SVG.launch("black", 15.0, 15.0)
// create content
//val headerColor = getDefaultColor(i % 12)
//header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor
// create image view
icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 - 16.0 }, header.boundsInParentProperty(), icon.heightProperty()))
iconView.limitSize(32.0, 32.0)
}
/*private fun getDefaultColor(i: Int): String {
var color = "#FFFFFF"
when (i) {
0 -> color = "#8F3F7E"
1 -> color = "#B5305F"
2 -> color = "#CE584A"
3 -> color = "#DB8D5C"
4 -> color = "#DA854E"
5 -> color = "#E9AB44"
6 -> color = "#FEE435"
7 -> color = "#99C286"
8 -> color = "#01A05E"
9 -> color = "#4A8895"
10 -> color = "#16669B"
11 -> color = "#2F65A5"
12 -> color = "#4E6A9C"
else -> {
}
}
return color
}*/
}

View File

@@ -1,52 +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 javafx.fxml.FXML
import javafx.scene.control.Button
import javafx.scene.control.Label
import javafx.scene.image.ImageView
import javafx.scene.layout.StackPane
class VersionListItem(versionName: String, gameVersion: String = "") : StackPane() {
@FXML lateinit var lblVersionName: Label
@FXML lateinit var lblGameVersion: Label
@FXML lateinit var imageView: ImageView
@FXML lateinit var imageViewContainer: StackPane
private var handler: () -> Unit = {}
init {
loadFXML("/assets/fxml/version-list-item.fxml")
lblVersionName.text = versionName
lblGameVersion.text = gameVersion
imageView.limitSize(32.0, 32.0)
imageViewContainer.limitWidth(32.0)
imageViewContainer.limitHeight(32.0)
}
fun onSettings() {
handler()
}
fun onSettingsButtonClicked(handler: () -> Unit) {
this.handler = handler
}
}

View File

@@ -35,7 +35,8 @@ import org.jackhuang.hmcl.ui.export.ExportWizardProvider
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
class VersionPage : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", null)
private val titleProperty: StringProperty = SimpleStringProperty(this, "title", null)
override fun titleProperty() = titleProperty
@FXML lateinit var versionSettingsController: VersionSettingsController
@FXML lateinit var modTab: Tab

View File

@@ -57,7 +57,7 @@ class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, privat
taskResult("login") {
try {
val account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password)
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.proxy)
account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.proxy)
} catch (e: Exception) {
e
}

View File

@@ -1,101 +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.animation
import javafx.animation.Interpolator
import javafx.animation.KeyFrame
import javafx.animation.KeyValue
import javafx.util.Duration
typealias AnimationProducer = (AnimationHandler) -> List<KeyFrame>
enum class ContainerAnimations(val animationProducer: AnimationProducer) {
/**
* None
*/
NONE({
emptyList()
}),
/**
* A fade between the old and new view
*/
FADE({ c ->
listOf(
KeyFrame(Duration.ZERO,
KeyValue(c.snapshot.opacityProperty(), 1.0, Interpolator.EASE_BOTH)),
KeyFrame(c.duration,
KeyValue(c.snapshot.opacityProperty(), 0.0, Interpolator.EASE_BOTH))
)
}),
/**
* A zoom effect
*/
ZOOM_IN({ c ->
listOf(
KeyFrame(Duration.ZERO,
KeyValue(c.snapshot.scaleXProperty(), 1, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.scaleYProperty(), 1, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.opacityProperty(), 1.0, Interpolator.EASE_BOTH)),
KeyFrame(c.duration,
KeyValue(c.snapshot.scaleXProperty(), 4, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.scaleYProperty(), 4, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.opacityProperty(), 0, Interpolator.EASE_BOTH))
)
}),
/**
* A zoom effect
*/
ZOOM_OUT({ c ->
listOf(
KeyFrame(Duration.ZERO,
KeyValue(c.snapshot.scaleXProperty(), 1, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.scaleYProperty(), 1, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.opacityProperty(), 1.0, Interpolator.EASE_BOTH)),
KeyFrame(c.duration,
KeyValue(c.snapshot.scaleXProperty(), 0, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.scaleYProperty(), 0, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.opacityProperty(), 0, Interpolator.EASE_BOTH))
)
}),
/**
* A swipe effect
*/
SWIPE_LEFT({ c ->
listOf(
KeyFrame(Duration.ZERO,
KeyValue(c.view.translateXProperty(), c.view.getWidth(), Interpolator.EASE_BOTH),
KeyValue(c.snapshot.translateXProperty(), -c.view.getWidth(), Interpolator.EASE_BOTH)),
KeyFrame(c.duration,
KeyValue(c.view.translateXProperty(), 0, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.translateXProperty(), -c.view.getWidth(), Interpolator.EASE_BOTH)
))
}),
/**
* A swipe effect
*/
SWIPE_RIGHT({ c ->
listOf(
KeyFrame(Duration.ZERO,
KeyValue(c.view.translateXProperty(), -c.view.getWidth(), Interpolator.EASE_BOTH),
KeyValue(c.snapshot.translateXProperty(), c.view.getWidth(), Interpolator.EASE_BOTH)),
KeyFrame(c.duration,
KeyValue(c.view.translateXProperty(), 0, Interpolator.EASE_BOTH),
KeyValue(c.snapshot.translateXProperty(), c.view.getWidth(), Interpolator.EASE_BOTH))
)
})
}

View File

@@ -1,90 +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.animation
import javafx.animation.KeyFrame
import javafx.animation.Timeline
import javafx.event.EventHandler
import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.SnapshotParameters
import javafx.scene.image.ImageView
import javafx.scene.image.WritableImage
import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane
import javafx.util.Duration
import org.jackhuang.hmcl.ui.takeSnapshot
/**
* @param view A stack pane that contains another control that is [Parent]
*/
class TransitionHandler(private val view: StackPane): AnimationHandler {
private var animation: Timeline? = null
override fun getSnapshot() = ImageView().apply {
isPreserveRatio = true
isSmooth = true
}
override fun getView() = view
private lateinit var duration: Duration
override fun getDuration() = duration
fun setContent(newView: Node, transition: AnimationProducer, duration: Duration = Duration.millis(320.0)) {
this.duration = duration
val prevAnimation = animation
if (prevAnimation != null)
prevAnimation.stop()
updateContent(newView)
val nowAnimation = Timeline().apply {
keyFrames.addAll(transition(this@TransitionHandler))
keyFrames.add(KeyFrame(duration, EventHandler {
snapshot.image = null
snapshot.x = 0.0
snapshot.y = 0.0
snapshot.isVisible = false
}))
}
nowAnimation.play()
animation = nowAnimation
}
private fun updateContent(newView: Node) {
if (view.width > 0 && view.height > 0) {
val content = view.children.firstOrNull()
val image: WritableImage
if (content != null && content is Parent) {
view.children.setAll()
image = takeSnapshot(content, view.width, view.height)
view.children.setAll(content)
} else image = view.snapshot(SnapshotParameters(), WritableImage(view.width.toInt(), view.height.toInt()))
snapshot.image = image
snapshot.fitWidth = view.width
snapshot.fitHeight = view.height
} else
snapshot.image = null
snapshot.isVisible = true
snapshot.opacity = 1.0
view.children.setAll(snapshot, newView)
snapshot.toFront()
}
}

View File

@@ -1,80 +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.construct
import javafx.beans.DefaultProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList
import javafx.scene.Node
import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox
import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
import kotlin.collections.plusAssign
import kotlin.collections.set
@DefaultProperty("content")
open class ComponentList: StackPane() {
val vbox = VBox()
val content: ObservableList<Node> = FXCollections.observableArrayList<Node>().apply {
addListener { change: ListChangeListener.Change<out Node> ->
while (change.next()) {
for (i in change.from until change.to) {
addChildren(change.list[i])
}
}
}
}
init {
children.setAll(vbox)
styleClass += "options-list"
}
fun addChildren(node: Node) {
if (node is ComponentList) {
node.properties["title"] = node.title
node.properties["subtitle"] = node.subtitle
}
vbox.children += StackPane().apply {
children += ComponentListCell(node)
if (vbox.children.isEmpty())
styleClass += "options-list-item-ahead"
else {
styleClass += "options-list-item"
}
}
}
val titleProperty = SimpleStringProperty(this, "title", "Group")
var title: String by titleProperty
val subtitleProperty = SimpleStringProperty(this, "subtitle", "")
var subtitle: String by subtitleProperty
var hasSubtitle: Boolean = false
val depthProperty = SimpleIntegerProperty(this, "depth", 0)
var depth: Int by depthProperty
}

View File

@@ -1,164 +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.construct
import com.jfoenix.controls.JFXButton
import javafx.animation.Animation
import javafx.animation.KeyFrame
import javafx.animation.KeyValue
import javafx.animation.Timeline
import javafx.beans.property.SimpleBooleanProperty
import javafx.geometry.Pos
import javafx.scene.Node
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox
import javafx.scene.shape.Rectangle
import javafx.util.Duration
import org.jackhuang.hmcl.ui.SINE
import org.jackhuang.hmcl.ui.SVG
import org.jackhuang.hmcl.ui.limitHeight
import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.setValue
class ComponentListCell(private val content: Node) : StackPane() {
var expandAnimation: Animation? = null
private var clipRect: Rectangle? = null
private var animatedHeight = 0.0
private val expandedProperty = SimpleBooleanProperty(this, "expanded", false)
var expanded: Boolean by expandedProperty
init {
updateLayout()
}
private fun updateClip(newHeight: Double) {
clipRect?.height = newHeight
}
override fun layoutChildren() {
super.layoutChildren()
if (clipRect == null) {
clipRect = Rectangle(0.0, 0.0, width, height)
//clip = clipRect
} else {
clipRect?.x = 0.0
clipRect?.y = 0.0
clipRect?.height = height
clipRect?.width = width
}
}
private fun updateLayout() {
if (content is ComponentList) {
content.styleClass -= "options-list"
content.styleClass += "options-sublist"
val groupNode = StackPane()
groupNode.styleClass += "options-list-item-header"
val expandIcon = SVG.expand("black", 10.0, 10.0)
val expandButton = JFXButton()
expandButton.graphic = expandIcon
expandButton.styleClass += "options-list-item-expand-button"
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT)
val labelVBox = VBox()
Label().apply {
textProperty().bind(content.titleProperty)
isMouseTransparent = true
labelVBox.children += this
}
if (content.hasSubtitle)
Label().apply {
textProperty().bind(content.subtitleProperty)
isMouseTransparent = true
styleClass += "subtitle-label"
labelVBox.children += this
}
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT)
groupNode.children.setAll(
labelVBox,
expandButton)
val container = VBox().apply {
style += "-fx-padding: 8 0 0 0;"
limitHeight(0.0)
val clipRect = Rectangle()
clipRect.widthProperty().bind(widthProperty())
clipRect.heightProperty().bind(heightProperty())
clip = clipRect
children.setAll(content)
}
val holder = VBox()
holder.children.setAll(groupNode, container)
holder.styleClass += "options-list-item-container"
expandButton.setOnMouseClicked {
if (expandAnimation != null && expandAnimation!!.status == Animation.Status.RUNNING) {
expandAnimation!!.stop()
}
expanded = !expanded
val newAnimatedHeight = content.prefHeight(-1.0) * (if (expanded) 1.0 else -1.0)
val newHeight = if (expanded) height + newAnimatedHeight else prefHeight(-1.0)
val contentHeight = if (expanded) newAnimatedHeight else 0.0
if (expanded) {
updateClip(newHeight)
}
animatedHeight = newAnimatedHeight
expandAnimation = Timeline(KeyFrame(Duration(320.0),
KeyValue(container.minHeightProperty(), contentHeight, SINE),
KeyValue(container.maxHeightProperty(), contentHeight, SINE)
))
if (!expanded) {
expandAnimation?.setOnFinished {
updateClip(newHeight)
animatedHeight = 0.0
}
}
expandAnimation?.play()
}
expandedProperty.onChange {
if (it) {
expandIcon.rotate = 180.0
} else {
expandIcon.rotate = 0.0
}
}
children.setAll(holder)
} else {
children.setAll(content)
}
}
}

View File

@@ -1,76 +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.construct
import com.jfoenix.controls.JFXButton
import javafx.beans.property.Property
import javafx.beans.property.SimpleStringProperty
import javafx.scene.control.Label
import javafx.scene.control.Tooltip
import javafx.scene.layout.BorderPane
import javafx.scene.layout.VBox
import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.ui.Controllers
import org.jackhuang.hmcl.ui.SVG
import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
class FileItem : BorderPane() {
val nameProperty = SimpleStringProperty(this, "name")
var name: String by nameProperty
val titleProperty = SimpleStringProperty(this, "title")
var title: String by titleProperty
val tooltipProperty = SimpleStringProperty(this, "tooltip")
var tooltip: String by tooltipProperty
private lateinit var property: Property<String>
private val x = Label()
init {
left = VBox().apply {
children += Label().apply { textProperty().bind(nameProperty) }
children += x.apply { styleClass += "subtitle-label" }
}
right = JFXButton().apply {
graphic = SVG.pencil("black", 15.0, 15.0)
styleClass += "toggle-icon4"
setOnMouseClicked { onExplore() }
}
Tooltip.install(this, Tooltip().apply {
textProperty().bind(tooltipProperty)
})
}
fun onExplore() {
val chooser = DirectoryChooser()
chooser.titleProperty().bind(titleProperty)
val selectedDir = chooser.showDialog(Controllers.stage)
if (selectedDir != null)
property.value = selectedDir.absolutePath
chooser.titleProperty().unbind()
}
fun setProperty(property: Property<String>) {
this.property = property
x.textProperty().bind(property)
}
}

View File

@@ -1,46 +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.construct
import com.jfoenix.controls.JFXComboBox
import javafx.beans.NamedArg
import javafx.collections.FXCollections
import javafx.scene.control.ListCell
import javafx.scene.text.Font
import javafx.util.Callback
import org.jackhuang.hmcl.util.onChange
class FontComboBox(@NamedArg("fontSize") fontSize: Double = 12.0, @NamedArg("enableStyle") enableStyle: Boolean = false) : JFXComboBox<String>(FXCollections.observableArrayList(Font.getFamilies())) {
init {
valueProperty().onChange {
if (enableStyle)
style = "-fx-font-family: \"$it\";"
}
cellFactory = Callback {
object : ListCell<String>() {
override fun updateItem(item: String?, empty: Boolean) {
super.updateItem(item, empty)
item?.apply {
text = item
font = Font(item, fontSize)
}
}
}
}
}
}

View File

@@ -1,31 +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.construct
import javafx.geometry.Pos
import javafx.scene.Node
import javafx.scene.control.Label
import javafx.scene.layout.HBox
class IconedItem(val icon: Node, val text: String)
: RipplerContainer(HBox().apply {
children += icon.apply { isMouseTransparent = true }
children += Label(text).apply { alignment = Pos.CENTER; isMouseTransparent = true }
style += "-fx-padding: 10 16 10 16; -fx-spacing: 10; -fx-font-size: 14; "
alignment = Pos.CENTER_LEFT
})

View File

@@ -1,40 +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.construct
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.scene.control.MultipleSelectionModel
class NoneMultipleSelectionModel<T> : MultipleSelectionModel<T>() {
override fun isEmpty() = true
override fun selectAll() = Unit
override fun selectIndices(index: Int, vararg indices: Int) = Unit
override fun select(obj: T) = Unit
override fun select(index: Int) = Unit
override fun selectLast() = Unit
override fun selectFirst() = Unit
override fun selectNext() = Unit
override fun clearSelection(index: Int) = Unit
override fun clearSelection() = Unit
override fun clearAndSelect(index: Int) = Unit
override fun selectPrevious() = Unit
override fun isSelected(index: Int) = false
override fun getSelectedItems(): ObservableList<T> = FXCollections.emptyObservableList<T>()
override fun getSelectedIndices(): ObservableList<Int> = FXCollections.emptyObservableList<Int>()
}

View File

@@ -1,46 +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.construct
import com.jfoenix.validation.base.ValidatorBase
import javafx.scene.control.TextInputControl
class NumberValidator @JvmOverloads constructor(val nullable: Boolean = false) : ValidatorBase() {
override fun eval() {
if (this.srcControl.get() is TextInputControl) {
this.evalTextInputField()
}
}
private fun evalTextInputField() {
val textField = this.srcControl.get() as TextInputControl
if (textField.text.isBlank())
hasErrors.set(false)
else
try {
Integer.parseInt(textField.text)
this.hasErrors.set(false)
} catch (var3: Exception) {
this.hasErrors.set(true)
}
}
}

View File

@@ -1,103 +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.download
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXListView
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.game.GameRepository
import org.jackhuang.hmcl.ui.loadFXML
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardPage
class AdditionalInstallersPage(private val provider: InstallWizardProvider, private val controller: WizardController, private val repository: GameRepository, private val downloadProvider: DownloadProvider): StackPane(), WizardPage {
@FXML lateinit var list: VBox
@FXML lateinit var btnForge: JFXButton
@FXML lateinit var btnLiteLoader: JFXButton
@FXML lateinit var btnOptiFine: JFXButton
@FXML lateinit var lblGameVersion: Label
@FXML lateinit var lblVersionName: Label
@FXML lateinit var lblForge: Label
@FXML lateinit var lblLiteLoader: Label
@FXML lateinit var lblOptiFine: Label
@FXML lateinit var btnInstall: JFXButton
init {
loadFXML("/assets/fxml/download/additional-installers.fxml")
lblGameVersion.text = provider.gameVersion
lblVersionName.text = provider.version.id
btnForge.setOnMouseClicked {
controller.settings[INSTALLER_TYPE] = 0
controller.onNext(VersionsPage(controller, provider.gameVersion, downloadProvider, "forge") { controller.onPrev(false) })
}
btnLiteLoader.setOnMouseClicked {
controller.settings[INSTALLER_TYPE] = 1
controller.onNext(VersionsPage(controller, provider.gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) })
}
btnOptiFine.setOnMouseClicked {
controller.settings[INSTALLER_TYPE] = 2
controller.onNext(VersionsPage(controller, provider.gameVersion, downloadProvider, "optifine") { controller.onPrev(false) })
}
}
override val title: String
get() = "Choose a game version"
override fun onNavigate(settings: MutableMap<String, Any>) {
lblGameVersion.text = "Current Game Version: ${provider.gameVersion}"
btnForge.isDisable = provider.forge != null
if (provider.forge != null || controller.settings.containsKey("forge"))
lblForge.text = "Forge Versoin: ${provider.forge ?: controller.settings["forge"]}"
else
lblForge.text = "Forge not installed"
btnLiteLoader.isDisable = provider.liteloader != null
if (provider.liteloader != null || controller.settings.containsKey("liteloader"))
lblLiteLoader.text = "LiteLoader Versoin: ${provider.liteloader ?: controller.settings["liteloader"]}"
else
lblLiteLoader.text = "LiteLoader not installed"
btnOptiFine.isDisable = provider.optifine != null
if (provider.optifine != null || controller.settings.containsKey("optifine"))
lblOptiFine.text = "OptiFine Versoin: ${provider.optifine ?: controller.settings["optifine"]}"
else
lblOptiFine.text = "OptiFine not installed"
}
override fun cleanup(settings: MutableMap<String, Any>) {
settings.remove(INSTALLER_TYPE)
}
fun onInstall() {
controller.onFinish()
}
companion object {
val INSTALLER_TYPE = "INSTALLER_TYPE"
}
}

View File

@@ -31,11 +31,11 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider
import org.jackhuang.hmcl.util.task
import java.io.File
class DownloadWizardProvider(): WizardProvider() {
class DownloadWizardProvider(): WizardProvider {
lateinit var profile: Profile
override fun start(settings: MutableMap<String, Any>) {
profile = Settings.selectedProfile
profile = Settings.INSTANCE.selectedProfile
settings[PROFILE] = profile
}

View File

@@ -41,8 +41,7 @@ class InstallTypePage(private val controller: WizardController): StackPane(), Wi
settings.remove(INSTALL_TYPE)
}
override val title: String
get() = "Select an operation"
override fun getTitle() = "Select an operation"
companion object {
const val INSTALL_TYPE: String = "INSTALL_TYPE"

View File

@@ -1,44 +0,0 @@
package org.jackhuang.hmcl.ui.download
import javafx.scene.Node
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.mod.Modpack
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardProvider
import org.jackhuang.hmcl.util.task
import java.io.File
class InstallWizardProvider(val profile: Profile, val gameVersion: String, val version: Version, val forge: String? = null, val liteloader: String? = null, val optifine: String? = null): WizardProvider() {
override fun start(settings: MutableMap<String, Any>) {
}
override fun finish(settings: MutableMap<String, Any>): Any? {
var ret = task {}
if (settings.containsKey("forge"))
ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "forge", settings["forge"] as String))
if (settings.containsKey("liteloader"))
ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "liteloader", settings["liteloader"] as String))
if (settings.containsKey("optifine"))
ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "optifine", settings["optifine"] as String))
return ret.with(task { profile.repository.refreshVersions() })
}
override fun createPage(controller: WizardController, step: Int, settings: MutableMap<String, Any>): Node {
return when (step) {
0 -> AdditionalInstallersPage(this, controller, profile.repository, BMCLAPIDownloadProvider.INSTANCE)
else -> throw IllegalStateException()
}
}
override fun cancel(): Boolean {
return true
}
}

View File

@@ -70,8 +70,7 @@ class InstallersPage(private val controller: WizardController, private val repos
}
}
override val title: String
get() = "Choose a game version"
override fun getTitle() = "Choose a game version"
override fun onNavigate(settings: MutableMap<String, Any>) {
lblGameVersion.text = "Current Game Version: ${controller.settings["game"]}"

View File

@@ -36,7 +36,8 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage
import org.jackhuang.hmcl.util.onInvalidated
class ModpackPage(private val controller: WizardController): StackPane(), WizardPage {
override val title: String = i18n("modpack.task.install")
private val title: String = i18n("modpack.task.install")
override fun getTitle() = title
@FXML lateinit var borderPane: Region
@FXML lateinit var lblName: Label

View File

@@ -1,75 +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.download
import com.jfoenix.controls.JFXListView
import com.jfoenix.controls.JFXSpinner
import javafx.fxml.FXML
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.task.TaskExecutor
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
import org.jackhuang.hmcl.ui.animation.TransitionHandler
import org.jackhuang.hmcl.ui.loadFXML
import org.jackhuang.hmcl.ui.wizard.Refreshable
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardPage
import org.jackhuang.hmcl.util.onChange
class VersionsPage(private val controller: WizardController, private val gameVersion: String, private val downloadProvider: DownloadProvider, private val libraryId: String, private val callback: () -> Unit): StackPane(), WizardPage, Refreshable {
@FXML lateinit var list: JFXListView<VersionsPageItem>
@FXML lateinit var spinner: JFXSpinner
val transitionHandler = TransitionHandler(this)
private val versionList = downloadProvider.getVersionListById(libraryId)
private var executor: TaskExecutor? = null
init {
loadFXML("/assets/fxml/download/versions.fxml")
children.setAll(spinner)
list.selectionModel.selectedItemProperty().onChange {
controller.settings[libraryId] = it!!.remoteVersion.selfVersion
callback()
}
refresh()
}
override fun refresh() {
executor = versionList.refreshAsync(downloadProvider).subscribe(Schedulers.javafx()) {
val versions = ArrayList(versionList.getVersions(gameVersion))
versions.sortWith(RemoteVersion.RemoteVersionComparator.INSTANCE)
for (version in versions) {
list.items.add(VersionsPageItem(version))
}
transitionHandler.setContent(list, ContainerAnimations.FADE.animationProducer)
}
}
override val title: String
get() = "Choose a game version"
override fun cleanup(settings: MutableMap<String, Any>) {
settings.remove(libraryId)
executor?.cancel()
}
}

View File

@@ -1,58 +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.export
import javafx.scene.Node
import org.jackhuang.hmcl.game.HMCLModpackExportTask
import org.jackhuang.hmcl.game.HMCLModpackManager.MODPACK_PREDICATE
import org.jackhuang.hmcl.mod.Modpack
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardProvider
import java.io.File
class ExportWizardProvider(private val profile: Profile, private val version: String) : WizardProvider() {
override fun start(settings: MutableMap<String, Any>) {
}
override fun finish(settings: MutableMap<String, Any>): Any? {
@Suppress("UNCHECKED_CAST")
return HMCLModpackExportTask(profile.repository, version, settings[ModpackFileSelectionPage.MODPACK_FILE_SELECTION] as List<String>,
Modpack(
settings[ModpackInfoPage.MODPACK_NAME] as String,
settings[ModpackInfoPage.MODPACK_AUTHOR] as String,
settings[ModpackInfoPage.MODPACK_VERSION] as String,
null,
settings[ModpackInfoPage.MODPACK_DESCRIPTION] as String,
null
), settings[ModpackInfoPage.MODPACK_FILE] as File)
}
override fun createPage(controller: WizardController, step: Int, settings: MutableMap<String, Any>): Node {
return when(step) {
0 -> ModpackInfoPage(controller, version)
1 -> ModpackFileSelectionPage(controller, profile, version, MODPACK_PREDICATE)
else -> throw IllegalArgumentException("step")
}
}
override fun cancel(): Boolean {
return true
}
}

View File

@@ -1,143 +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.export
import com.jfoenix.controls.JFXTreeView
import javafx.fxml.FXML
import javafx.scene.control.CheckBox
import javafx.scene.control.CheckBoxTreeItem
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.i18n
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel
import org.jackhuang.hmcl.ui.loadFXML
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardPage
import java.io.File
import java.util.*
class ModpackFileSelectionPage(private val controller: WizardController, profile: Profile, private val version: String, private val adviser: ModAdviser): StackPane(), WizardPage {
override val title: String = i18n("modpack.wizard.step.2.title")
@FXML lateinit var treeView: JFXTreeView<String>
private val rootNode: CheckBoxTreeItem<String>?
init {
loadFXML("/assets/fxml/modpack/selection.fxml")
rootNode = getTreeItem(profile.repository.getRunDirectory(version), "minecraft")
treeView.root = rootNode
treeView.selectionModel = NoneMultipleSelectionModel<TreeItem<String>>()
}
private fun getTreeItem(file: File, basePath: String): CheckBoxTreeItem<String>? {
var state = ModAdviser.ModSuggestion.SUGGESTED
if (basePath.length > "minecraft/".length) {
state = adviser.advise(basePath.substringAfter("minecraft/") + (if (file.isDirectory) "/" else ""), file.isDirectory)
if (file.isFile && file.nameWithoutExtension == version)
state = ModAdviser.ModSuggestion.HIDDEN
if (file.isDirectory && file.name == version + "-natives")
state = ModAdviser.ModSuggestion.HIDDEN
if (state == ModAdviser.ModSuggestion.HIDDEN)
return null
}
val node = CheckBoxTreeItem<String>(basePath.substringAfterLast("/"))
if (state == ModAdviser.ModSuggestion.SUGGESTED)
node.isSelected = true
if (file.isDirectory) {
file.listFiles()?.forEach {
val subNode = getTreeItem(it, basePath + "/" + it.name)
if (subNode != null) {
node.isSelected = subNode.isSelected or node.isSelected
if (!subNode.isSelected)
node.isIndeterminate = true
node.children += subNode
}
}
if (!node.isSelected) node.isIndeterminate = false
// Empty folder need not to be displayed.
if (node.children.isEmpty())
return null
}
return node.apply {
graphic = HBox().apply {
val checkbox = CheckBox()
checkbox.selectedProperty().bindBidirectional(node.selectedProperty())
checkbox.indeterminateProperty().bindBidirectional(node.indeterminateProperty())
children += checkbox
if (TRANSLATION.containsKey(basePath))
children += Label().apply {
text = TRANSLATION[basePath]
style = "-fx-text-fill: gray;"
isMouseTransparent = true
}
isPickOnBounds = false
isExpanded = basePath == "minecraft"
}
}
}
private fun getFilesNeeded(node: CheckBoxTreeItem<String>?, basePath: String, list: MutableList<String>) {
if (node == null)
return
if (node.isSelected) {
if (basePath.length > "minecraft/".length)
list += basePath.substring("minecraft/".length)
for (child in node.children)
getFilesNeeded(child as? CheckBoxTreeItem<String>?, basePath + "/" + child.value, list)
return
}
}
override fun cleanup(settings: MutableMap<String, Any>) {
controller.settings.remove(MODPACK_FILE_SELECTION)
}
fun onNext() {
val list = LinkedList<String>()
getFilesNeeded(rootNode, "minecraft", list)
controller.settings[MODPACK_FILE_SELECTION] = list
controller.onFinish()
}
companion object {
val MODPACK_FILE_SELECTION = "modpack.accepted"
private val TRANSLATION = mapOf(
"minecraft/servers.dat" to i18n("modpack.files.servers_dat"),
"minecraft/saves" to i18n("modpack.files.saves"),
"minecraft/mods" to i18n("modpack.files.mods"),
"minecraft/config" to i18n("modpack.files.config"),
"minecraft/liteconfig" to i18n("modpack.files.liteconfig"),
"minecraft/resourcepacks" to i18n("modpack.files.resourcepacks"),
"minecraft/resources" to i18n("modpack.files.resourcepacks"),
"minecraft/options.txt" to i18n("modpack.files.options_txt"),
"minecraft/optionsshaders.txt" to i18n("modpack.files.optionsshaders_txt"),
"minecraft/mods/VoxelMods" to i18n("modpack.files.mods.voxelmods"),
"minecraft/dumps" to i18n("modpack.files.dumps"),
"minecraft/blueprints" to i18n("modpack.files.blueprints"),
"minecraft/scripts" to i18n("modpack.files.scripts")
)
}
}

View File

@@ -1,100 +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.export
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXTextArea
import com.jfoenix.controls.JFXTextField
import com.jfoenix.controls.JFXToggleButton
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.layout.StackPane
import javafx.stage.FileChooser
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.Controllers
import org.jackhuang.hmcl.ui.loadFXML
import org.jackhuang.hmcl.ui.smoothScrolling
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardPage
import org.jackhuang.hmcl.util.onInvalidated
class ModpackInfoPage(private val controller: WizardController, version: String): StackPane(), WizardPage {
override val title: String = i18n("modpack.wizard.step.1.title")
@FXML lateinit var lblVersionName: Label
@FXML lateinit var txtModpackName: JFXTextField
@FXML lateinit var txtModpackAuthor: JFXTextField
@FXML lateinit var txtModpackVersion: JFXTextField
@FXML lateinit var txtModpackDescription: JFXTextArea
@FXML lateinit var chkIncludeLauncher: JFXToggleButton
@FXML lateinit var btnNext: JFXButton
@FXML lateinit var scroll: ScrollPane
init {
loadFXML("/assets/fxml/modpack/info.fxml")
scroll.smoothScrolling()
txtModpackName.text = version
txtModpackName.textProperty().onInvalidated(this::checkValidation)
txtModpackAuthor.textProperty().onInvalidated(this::checkValidation)
txtModpackVersion.textProperty().onInvalidated(this::checkValidation)
txtModpackAuthor.text = Settings.selectedAccount?.username ?: ""
lblVersionName.text = version
}
private fun checkValidation() {
btnNext.isDisable = !txtModpackName.validate() || !txtModpackVersion.validate() || !txtModpackAuthor.validate()
}
fun onNext() {
val fileChooser = FileChooser()
fileChooser.title = i18n("modpack.wizard.step.initialization.save")
fileChooser.extensionFilters += FileChooser.ExtensionFilter(i18n("modpack"), "*.zip")
val file = fileChooser.showSaveDialog(Controllers.stage)
if (file == null) {
Controllers.navigate(null)
return
}
controller.settings[MODPACK_NAME] = txtModpackName.text
controller.settings[MODPACK_VERSION] = txtModpackVersion.text
controller.settings[MODPACK_AUTHOR] = txtModpackAuthor.text
controller.settings[MODPACK_FILE] = file
controller.settings[MODPACK_DESCRIPTION] = txtModpackDescription.text
controller.settings[MODPACK_INCLUDE_LAUNCHER] = chkIncludeLauncher.isSelected
controller.onNext()
}
override fun cleanup(settings: MutableMap<String, Any>) {
controller.settings.remove(MODPACK_NAME)
controller.settings.remove(MODPACK_VERSION)
controller.settings.remove(MODPACK_AUTHOR)
controller.settings.remove(MODPACK_DESCRIPTION)
controller.settings.remove(MODPACK_INCLUDE_LAUNCHER)
controller.settings.remove(MODPACK_FILE)
}
companion object {
const val MODPACK_NAME = "modpack.name"
const val MODPACK_VERSION = "modpack.version"
const val MODPACK_AUTHOR = "modpack.author"
const val MODPACK_DESCRIPTION = "modpack.description"
const val MODPACK_INCLUDE_LAUNCHER = "modpack.include_launcher"
const val MODPACK_FILE = "modpack.file"
}
}

View File

@@ -67,8 +67,7 @@ interface AbstractWizardDisplayer : WizardDisplayer {
running = false
}
override val isRunning: Boolean
get() = running
override fun isRunning() = running
})

View File

@@ -1,27 +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.wizard
import javafx.scene.Node
abstract class WizardProvider {
abstract fun start(settings: MutableMap<String, Any>)
abstract fun finish(settings: MutableMap<String, Any>): Any?
abstract fun createPage(controller: WizardController, step: Int, settings: MutableMap<String, Any>): Node
abstract fun cancel(): Boolean
}

View File

@@ -1,143 +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.util
import sun.misc.Unsafe
import java.lang.reflect.AccessibleObject
import java.lang.reflect.Executable
import java.lang.reflect.Method
import java.security.AccessController
import java.security.PrivilegedExceptionAction
private val unsafe: Unsafe = AccessController.doPrivileged(PrivilegedExceptionAction {
val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe")
theUnsafe.isAccessible = true
theUnsafe.get(null) as Unsafe
})
private val objectFieldOffset = unsafe.objectFieldOffset(
AccessibleObject::class.java.getDeclaredField("override"))
private fun AccessibleObject.setAccessibleForcibly() =
unsafe.putBoolean(this, objectFieldOffset, true)
fun getMethod(obj: Any, methodName: String): Method? =
getMethod(obj.javaClass, methodName)
fun getMethod(cls: Class<*>, methodName: String): Method? =
try {
cls.getDeclaredMethod(methodName).apply { setAccessibleForcibly() }
} catch (ex: Throwable) {
null
}
/**
* Call a field, method or constructor by reflection.
*
* @param name the field or method name of [clazz], "new" if you are looking for a constructor.
* @param args the arguments of the method, empty if you are looking for a field or non-argument method.
*/
fun Any.call(name: String, vararg args: Any?): Any? {
@Suppress("UNCHECKED_CAST")
return javaClass.call(name, this, *args)
}
/**
* Call a constructor by reflection.
*
* @param args the arguments of the method, empty if you are looking for a field or non-argument method.
*/
fun Class<*>.construct(vararg args: Any?) = call("new", null, *args)
/**
* Call a field, method or constructor by reflection.
*
* @param name the field or method name of [clazz], "new" if you are looking for a constructor.
* @param obj null for constructors or static/object methods/fields.
* @param args the arguments of the method, empty if you are looking for a field or non-argument method.
*/
fun Class<*>.call(name: String, obj: Any? = null, vararg args: Any?): Any? {
try {
if (args.isEmpty())
try {
return getDeclaredField(name).get(obj)
} catch(ignored: NoSuchFieldException) {
}
if (name == "new")
declaredConstructors.forEach {
if (checkParameter(it, *args)) return it.newInstance(*args)
}
else
return forMethod(name, *args)!!.invoke(obj, *args)
throw RuntimeException()
} catch(e: Exception) {
throw IllegalArgumentException("Cannot find `$name` in Class `${this.name}`, please check your code.", e)
}
}
fun Class<*>.forMethod(name: String, vararg args: Any?): Method? =
declaredMethods.filter { it.name == name }.filter { checkParameter(it, *args) }.firstOrNull()
fun checkParameter(exec: Executable, vararg args: Any?): Boolean {
val cArgs = exec.parameterTypes
if (args.size == cArgs.size) {
for (i in 0 until args.size) {
val arg = args[i]
// primitive variable cannot be null
if (if (arg != null) !isInstance(cArgs[i], arg) else cArgs[i].isPrimitive)
return false
}
exec.setAccessibleForcibly()
return true
} else
return false
}
fun isInstance(superClass: Class<*>, obj: Any): Boolean {
if (superClass.isInstance(obj)) return true
else if (PRIMITIVES[superClass.name] == obj.javaClass) return true
return false
}
fun isInstance(superClass: Class<*>, clazz: Class<*>): Boolean {
for (i in clazz.interfaces)
if (isInstance(superClass, i) || PRIMITIVES[superClass.name] == clazz)
return true
return isSubClass(superClass, clazz)
}
fun isSubClass(superClass: Class<*>, clazz: Class<*>): Boolean {
var clz: Class<*>? = clazz
do {
if (superClass == clz) return true
clz = clz?.superclass
} while (clz != null)
return false
}
fun <T> Class<T>.objectInstance() = call("INSTANCE")
val PRIMITIVES = mapOf(
"byte" to java.lang.Byte::class.java,
"short" to java.lang.Short::class.java,
"int" to java.lang.Integer::class.java,
"long" to java.lang.Long::class.java,
"char" to java.lang.Character::class.java,
"float" to java.lang.Float::class.java,
"double" to java.lang.Double::class.java,
"boolean" to java.lang.Boolean::class.java
)

View File

@@ -9,7 +9,7 @@
xmlns:fx="http://javafx.com/fxml"
fx:id="rootPane"
fx:controller="org.jackhuang.hmcl.ui.ModController">
<JFXSpinner style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
<JFXSpinner fx:id="spinner" style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
<StackPane fx:id="contentPane">
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
<VBox fx:id="modPane" spacing="10" style="-fx-padding: 20 20 70 20;">

View File

@@ -43,7 +43,8 @@ public final class ModManager {
Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> {
if (modFile.isDirectory() && VersionNumber.parseVersion(modFile.getName()) != null)
Optional.ofNullable(modFile.listFiles()).map(Arrays::stream).ifPresent(x -> x.forEach(puter));
puter.accept(modFile);
else
puter.accept(modFile);
}));
return modCache.get(id);
}

View File

@@ -21,7 +21,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
@@ -58,7 +57,7 @@ public abstract class Task {
/**
* True if requires all {@link #getDependents} finishing successfully.
*
* <p>
* **Note** if this field is set false, you are not supposed to invoke [run]
*
* @defaultValue true
@@ -69,7 +68,7 @@ public abstract class Task {
/**
* True if requires all {@link #getDependencies} finishing successfully.
*
* <p>
* **Note** if this field is set false, you are not supposed to invoke [run]
*
* @defaultValue false
@@ -77,7 +76,7 @@ public abstract class Task {
public boolean isRelyingOnDependencies() {
return true;
}
private String name = getClass().toString();
public String getName() {
@@ -99,8 +98,8 @@ public abstract class Task {
}
/**
* @see Thread#isInterrupted
* @throws InterruptedException if current thread is interrupted
* @see Thread#isInterrupted
*/
public abstract void execute() throws Exception;
@@ -128,10 +127,10 @@ public abstract class Task {
private long lastTime = Long.MIN_VALUE;
private final AtomicReference<Double> progressUpdate = new AtomicReference<>();
private final ReadOnlyDoubleWrapper progressProperty = new ReadOnlyDoubleWrapper(this, "progress", 0);
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", 0);
public ReadOnlyDoubleProperty getProgressProperty() {
return progressProperty.getReadOnlyProperty();
public ReadOnlyDoubleProperty progressProperty() {
return progress.getReadOnlyProperty();
}
protected void updateProgress(int progress, int total) {
@@ -149,18 +148,18 @@ public abstract class Task {
}
protected void updateProgressImmediately(double progress) {
Properties.updateAsync(progressProperty, progress, progressUpdate);
Properties.updateAsync(this.progress, progress, progressUpdate);
}
private final AtomicReference<String> messageUpdate = new AtomicReference<>();
private final ReadOnlyStringWrapper messageProperty = new ReadOnlyStringWrapper(this, "message", null);
private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", null);
public final ReadOnlyStringProperty getMessageProperty() {
return messageProperty.getReadOnlyProperty();
public final ReadOnlyStringProperty messageProperty() {
return message.getReadOnlyProperty();
}
protected final void updateMessage(String newMessage) {
Properties.updateAsync(messageProperty, newMessage, messageUpdate);
Properties.updateAsync(message, newMessage, messageUpdate);
}
public final void run() throws Exception {
@@ -173,11 +172,11 @@ public abstract class Task {
}
private void doSubTask(Task task) throws Exception {
messageProperty.bind(task.messageProperty);
progressProperty.bind(task.progressProperty);
message.bind(task.message);
progress.bind(task.progress);
task.run();
messageProperty.unbind();
progressProperty.unbind();
message.unbind();
progress.unbind();
}
public final TaskExecutor executor() {
@@ -228,6 +227,11 @@ public abstract class Task {
return new CoupleTask<>(this, b, false);
}
public static Task empty() {
return of(s -> {
});
}
public static Task of(ExceptionalRunnable<?> runnable) {
return of(s -> runnable.run());
}

View File

@@ -256,6 +256,14 @@ public final class Lang {
}
}
public static Integer toIntOrNull(String string) {
try {
return Integer.parseInt(string);
} catch (NumberFormatException e) {
return null;
}
}
public static <T> T nonNull(T... t) {
for (T a : t) if (a != null) return a;
return null;

View File

@@ -117,6 +117,10 @@ public final class ReflectionHelper {
}
}
public static Object construct(Class<?> clazz, Object... args) {
return call(clazz, "new", null, args);
}
public static Object call(Object obj, String name, Object... args) {
return call(obj.getClass(), name, obj, args);
}
@@ -136,12 +140,7 @@ public final class ReflectionHelper {
}
public static boolean isInstance(Class<?> superClass, Object obj) {
if (superClass.isInstance(obj))
return true;
else if (PRIMITIVES.get(superClass.getName()) == obj.getClass())
return true;
else
return false;
return superClass.isInstance(obj) || PRIMITIVES.get(superClass.getName()) == obj.getClass();
}
public static Optional<Method> forMethod(Class<?> cls, String name, Object... args) {