diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Events.kt b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java similarity index 56% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/Events.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java index 606a092a2..7341e79ac 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Events.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java @@ -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. - *

- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * @param source [org.jackhuang.hmcl.setting.Settings] - * * - * @param Profile the new profile. - * * + *
+ * 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. - *

- * 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; + } + +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/WebStage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java similarity index 63% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/WebStage.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java index c6a4d2e09..6bd5250c6 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/WebStage.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java @@ -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. + *
+ * 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); } -} \ No newline at end of file + +} + diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index ccc4d22e7..ba643cae2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -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) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java new file mode 100644 index 000000000..c8a5039ba --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -0,0 +1,244 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 java = null; + + @SerializedName("localization") + private String localization; + + @SerializedName("downloadtype") + private int downloadType = 0; + + @SerializedName("configurations") + private Map configurations = new TreeMap<>(); + + @SerializedName("accounts") + private Map> 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 getJava() { + return java; + } + + public void setJava(List 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 getConfigurations() { + return configurations; + } + + public void setConfigurations(Map configurations) { + this.configurations = configurations; + Settings.INSTANCE.save(); + } + + public Map> getAccounts() { + return accounts; + } + + public void setAccounts(Map> 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java new file mode 100644 index 000000000..a8357b267 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -0,0 +1,443 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 accounts = new HashMap<>(); + + { + for (Map.Entry> entry : SETTINGS.getAccounts().entrySet()) { + String name = entry.getKey(); + Map 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 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 entry : accounts.entrySet()) { + String name = entry.getKey(); + Account account = entry.getValue(); + Map 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 selectedAccount = new ImmediateObjectProperty(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 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 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 getProfileMap() { + return SETTINGS.getConfigurations(); + } + + public Collection 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(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.java new file mode 100644 index 000000000..109732871 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.java @@ -0,0 +1,49 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java new file mode 100644 index 000000000..44edde63a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java @@ -0,0 +1,58 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java new file mode 100644 index 000000000..df0f719bd --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java @@ -0,0 +1,90 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 removeAction = x -> { + LinkedList 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)); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java new file mode 100644 index 000000000..b616acae4 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -0,0 +1,52 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 deleteCallback; + + @FXML + private Label lblInstallerArtifact; + + @FXML + private Label lblInstallerVersion; + + public InstallerItem(String artifact, String version, Consumer 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); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LaunchingStepsPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LaunchingStepsPane.java new file mode 100644 index 000000000..c0f008cdd --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LaunchingStepsPane.java @@ -0,0 +1,51 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 77b384299..58ba20198 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -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 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 cboLines; + private ComboBox 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()); diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MessageDialogPane.java similarity index 52% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/MessageDialogPane.java index 10d65b526..cf211c6d0 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MessageDialogPane.java @@ -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()); } -} \ No newline at end of file +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java new file mode 100644 index 000000000..859de8e2a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java @@ -0,0 +1,63 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 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); + }); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SafeIntStringConverter.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SafeIntStringConverter.java new file mode 100644 index 000000000..9124e9fd5 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SafeIntStringConverter.java @@ -0,0 +1,38 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 { + @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(""); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java new file mode 100644 index 000000000..105a17d0c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java @@ -0,0 +1,93 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 handler) { + btnSettings.setOnMouseClicked(handler); + } + + public void setOnDeleteButtonClicked(EventHandler handler) { + btnDelete.setOnMouseClicked(handler); + } + + public void setOnLaunchButtonClicked(EventHandler handler) { + btnLaunch.setOnMouseClicked(handler); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java new file mode 100644 index 000000000..b73ee7a6e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java @@ -0,0 +1,73 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java new file mode 100644 index 000000000..3050182bf --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationHandler.java index 6b567af6e..2dc98c13a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationHandler.java @@ -24,5 +24,5 @@ import javafx.util.Duration; public interface AnimationHandler { Node getSnapshot(); Duration getDuration(); - Pane getView(); + Pane getCurrentRoot(); } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardPage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationProducer.java similarity index 77% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardPage.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationProducer.java index 8895c8fd8..769312445 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardPage.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationProducer.java @@ -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) {} - fun cleanup(settings: MutableMap) - val title: String -} \ No newline at end of file +import javafx.animation.KeyFrame; + +import java.util.List; + +@FunctionalInterface +public interface AnimationProducer { + + List animate(AnimationHandler handler); +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java new file mode 100644 index 000000000..5bad93864 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java @@ -0,0 +1,91 @@ +/* + * Hello Minecraft! Launcher. + * Copyright c 2017 huangyuhui + * + * 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.java new file mode 100644 index 000000000..b8b710e67 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.java @@ -0,0 +1,109 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java new file mode 100644 index 000000000..5d92f9972 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -0,0 +1,113 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 content = FXCollections.observableArrayList(); + + public ComponentList() { + getChildren().setAll(vbox); + content.addListener((ListChangeListener) 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 getContent() { + return content; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java new file mode 100644 index 000000000..0c4efae89 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -0,0 +1,166 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java new file mode 100644 index 000000000..4ba1084ce --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java @@ -0,0 +1,110 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 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 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); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java new file mode 100644 index 000000000..4b3c4f84b --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java @@ -0,0 +1,49 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 { + + 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() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (item != null) { + setText(item); + setFont(new Font(item, fontSize)); + } + } + }); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java new file mode 100644 index 000000000..b421bda87 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java @@ -0,0 +1,46 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NoneMultipleSelectionModel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NoneMultipleSelectionModel.java new file mode 100644 index 000000000..91418fbbc --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NoneMultipleSelectionModel.java @@ -0,0 +1,96 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 extends MultipleSelectionModel { + private static final NoneMultipleSelectionModel INSTANCE = new NoneMultipleSelectionModel(); + private NoneMultipleSelectionModel() {} + + @SuppressWarnings("unchecked") + public static NoneMultipleSelectionModel getInstance() { + return (NoneMultipleSelectionModel) INSTANCE; + } + + @Override + public ObservableList getSelectedIndices() { + return FXCollections.emptyObservableList(); + } + + @Override + public ObservableList 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() { + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberValidator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberValidator.java new file mode 100644 index 000000000..e876c6bb8 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberValidator.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java new file mode 100644 index 000000000..3c8d62a26 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java @@ -0,0 +1,116 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 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 settings) { + settings.remove(INSTALLER_TYPE); + } + + public void onInstall() { + controller.onFinish(); + } + + public static final String INSTALLER_TYPE = "INSTALLER_TYPE"; +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java new file mode 100644 index 000000000..2d9ea284a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java @@ -0,0 +1,112 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 settings) { + } + + @Override + public Object finish(Map 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 settings) { + switch (step) { + case 0: + return new AdditionalInstallersPage(this, controller, profile.getRepository(), BMCLAPIDownloadProvider.INSTANCE); + default: + throw new IllegalStateException(); + } + } + + @Override + public boolean cancel() { + return true; + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java new file mode 100644 index 000000000..2e39c636d --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -0,0 +1,95 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 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 settings) { + settings.remove(libraryId); + if (executor != null) + executor.cancel(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java new file mode 100644 index 000000000..a58261958 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java @@ -0,0 +1,47 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java new file mode 100644 index 000000000..96a30769c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java @@ -0,0 +1,75 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 settings) { + } + + @Override + public Object finish(Map settings) { + return new HMCLModpackExportTask(profile.getRepository(), version, (List) 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 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java new file mode 100644 index 000000000..e05185fea --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java @@ -0,0 +1,168 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 treeView; + private CheckBoxTreeItem 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 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 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 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 node, String basePath, List list) { + if (node == null) return; + if (node.isSelected()) { + if (basePath.length() > "minecraft/".length()) + list.add(StringUtils.substringAfter(basePath, "minecraft/")); + for (TreeItem child : node.getChildren()) { + if (child instanceof CheckBoxTreeItem) + getFilesNeeded(((CheckBoxTreeItem) child), basePath + "/" + child.getValue(), list); + } + } + } + + @Override + public void cleanup(Map settings) { + controller.getSettings().remove(MODPACK_FILE_SELECTION); + } + + public void onNext() { + LinkedList 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 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")) + ); +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java new file mode 100644 index 000000000..a24028e7f --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java @@ -0,0 +1,111 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 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"; +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/DecoratorPage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DecoratorPage.java similarity index 79% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/DecoratorPage.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DecoratorPage.java index 5b06ea433..79da0413d 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/DecoratorPage.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DecoratorPage.java @@ -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() {} -} \ No newline at end of file + default void onClose() { + } +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LaunchingStepsPane.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DeferredWizardResult.java similarity index 62% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LaunchingStepsPane.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DeferredWizardResult.java index 877495692..c8eef109e 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LaunchingStepsPane.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DeferredWizardResult.java @@ -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); } -} \ No newline at end of file + public DeferredWizardResult(boolean canAbort) { + this.canAbort = canAbort; + } + + public abstract void start(Map settings, ResultProgressHandle progressHandle); + + public void abort() { + } +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/ResultProgressHandle.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/ResultProgressHandle.java similarity index 91% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/ResultProgressHandle.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/ResultProgressHandle.java index ffc6ce2b9..a15857168 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/ResultProgressHandle.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/ResultProgressHandle.java @@ -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 -} \ No newline at end of file + boolean isRunning(); +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.java similarity index 62% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.java index 5fc69bd46..4f9f2ecf7 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.java @@ -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, deferredResult: DeferredWizardResult) - - fun handleTask(settings: Map, task: Task) -} \ No newline at end of file +public interface WizardDisplayer { + void onStart(); + void onEnd(); + void onCancel(); + void navigateTo(Node page, Navigation.NavigationDirection nav); + void handleDeferredWizardResult(Map settings, DeferredWizardResult deferredWizardResult); + void handleTask(Map settings, Task task); +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardNavigationResult.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardNavigationResult.java similarity index 70% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardNavigationResult.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardNavigationResult.java index 5790c995d..1a5620264 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/WizardNavigationResult.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardNavigationResult.java @@ -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 -} \ No newline at end of file + public abstract boolean getDeferredComputation(); +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/DeferredWizardResult.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java similarity index 76% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/DeferredWizardResult.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java index 84a1aea20..c1558fa72 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/DeferredWizardResult.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java @@ -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, progressHandle: ResultProgressHandle) - open fun abort() { } -} \ No newline at end of file +public interface WizardPage { + default void onNavigate(Map settings) { + } + + void cleanup(Map settings); + + String getTitle(); +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SafeIntStringConverter.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardProvider.java similarity index 69% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SafeIntStringConverter.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardProvider.java index a0f14a990..8bb6fc4f1 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SafeIntStringConverter.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardProvider.java @@ -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() { - /** {@inheritDoc} */ - override fun fromString(value: String?) = value?.toIntOrNull() +import java.util.Map; - /** {@inheritDoc} */ - override fun toString(value: Int?) = value?.toString() ?: "" -} \ No newline at end of file +public interface WizardProvider { + void start(Map settings); + Object finish(Map settings); + Node createPage(WizardController controller, int step, Map settings); + boolean cancel(); +} diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt index ce7e97713..d876e8f7a 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt @@ -97,6 +97,6 @@ class Main : Application() { Schedulers.shutdown() } - val RESOURCE_BUNDLE = Settings.locale.resourceBundle + val RESOURCE_BUNDLE = Settings.INSTANCE.locale.resourceBundle } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt deleted file mode 100644 index 4702fbab6..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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? = 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 = TreeMap() - set(value) { - field = value - Settings.save() - } - @SerializedName("accounts") - var accounts: MutableMap> = 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() - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt deleted file mode 100644 index 1e8933690..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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() - - 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(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 { - 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 { - return SETTINGS.configurations - } - - fun getProfiles(): Collection { - 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() - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt index a7ad975b8..b6f4ba786 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt @@ -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) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt index 24a308bb0..fbd1183cf 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt @@ -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() 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) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ClassTitle.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ClassTitle.kt deleted file mode 100644 index f205fbe7d..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ClassTitle.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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" - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt index 9198c0eef..e42207fa7 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt @@ -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 diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt index 0551136e4..642de09f8 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt @@ -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 diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/DialogController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/DialogController.kt deleted file mode 100644 index 1a0ae392b..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/DialogController.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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(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 - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt index c96083587..418c25ad5 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt @@ -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) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt deleted file mode 100644 index 9d82ffab8..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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)) - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerItem.kt deleted file mode 100644 index 0e3849deb..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerItem.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt index 8a6a29982..7187e825e 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt @@ -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() - 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()) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt index 3edc2ae07..c935ad448 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt @@ -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() += 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() var i = 0 profile.repository.versions.forEach { version -> diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MessageDialogPane.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MessageDialogPane.kt deleted file mode 100644 index 96a170269..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MessageDialogPane.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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() - } - } - -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt index a7b07e976..f47413117 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt @@ -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 -> diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt deleted file mode 100644 index a5780de2b..000000000 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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) - } -} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt index cb32e2703..2fde385ee 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt @@ -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) } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt index 183c08b7e..19e5ea7d4 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt @@ -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) } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt index f2324e833..fd7a9b8b9 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt @@ -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