Compare commits

...
28 Commits
Author SHA1 Message Date
huanghongxun 5f686f7205 Fix crash when modpack to be installed is malformed. 2018-09-26 13:13:44 +08:00
huanghongxun 518969d2f2 Fix JsonParseException 2018-09-26 13:13:21 +08:00
huanghongxun 01ff1327f7 Make head displayed more precisely 2018-09-25 12:30:21 +08:00
huanghongxun 4d366397a8 Merge pull request #457 from andylizi/add-helmet-support
支持在头像中显示头盔层
2018-09-24 17:28:17 +08:00
huanghongxun 80f79d20a5 Update message UI 2018-09-24 17:26:02 +08:00
huanghongxun 1611d11544 Fix crash when yggdrasil server gives response with malformed UUID 2018-09-24 17:24:13 +08:00
andylizi c8e8b25c85 Adjust faceOffset in skin preview 2018-09-24 15:34:48 +08:00
andylizi 92cfc3c1d3 Make skin preview more accurate 2018-09-24 13:47:59 +08:00
huanghongxun c4cebe72b4 Show bubble when updates available 2018-09-24 01:51:14 +08:00
andylizi 6d346a3ad6 Render helmet layer in skin preview. Fixed #456 2018-09-23 22:53:23 +08:00
huanghongxun 6e2696dea3 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-09-22 22:26:14 +08:00
huanghongxun ba67c968cf Change version selection list look 2018-09-22 22:25:53 +08:00
huanghongxun 332719f49a Fix closing page 2018-09-22 21:40:16 +08:00
yushijinhun fb18975c5c Fix param table in readme 2018-09-22 21:08:03 +08:00
yushijinhun 08a7c27353 Fix config file is checked before created 2018-09-22 21:05:50 +08:00
yushijinhun 4c9cb99a94 Add hmcl.self_integrity_check.disable option 2018-09-22 21:05:50 +08:00
huanghongxun cbecca5f55 Merge pull request #455 from yushijinhun/javafx
重构
2018-09-22 18:12:18 +08:00
yushijinhun 4a0831a155 Replace Properties with InvocationDispatcher & Make config saving async 2018-09-22 14:21:17 +08:00
yushijinhun fa38c53915 Remove Constants.SYSTEM_CHARSET 2018-09-22 12:54:15 +08:00
yushijinhun e7c2170ce3 Fix getSteve/AlexSkin ignores scaleRatio param 2018-09-22 12:53:02 +08:00
yushijinhun 9b69fe8c56 Remove Constants.DEFAULT_ICON 2018-09-22 12:52:30 +08:00
yushijinhun ed91771594 Remove truePredicate/falsePredicate/emptyConsumer 2018-09-22 12:39:57 +08:00
yushijinhun b33e014f71 Move GSON const to JsonUtils 2018-09-22 12:37:03 +08:00
yushijinhun 19632ac162 Move JsonUtils->gson, JarUtils->io, Properties->javafx 2018-09-22 12:33:18 +08:00
yushijinhun 74e3a9e2d1 Use http.agent to set useragent 2018-09-22 12:30:33 +08:00
yushijinhun b15291dc57 Refactor util package structure 2018-09-22 12:12:06 +08:00
huanghongxun 1b5d4c1b42 Improve UI details 2018-09-22 00:18:55 +08:00
huanghongxun 923924638e Add a close button to close all pages 2018-09-20 22:54:41 +08:00
215 changed files with 1072 additions and 1307 deletions

No files matched your search

@@ -26,6 +26,8 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
@@ -78,9 +80,6 @@ public final class Launcher extends Application {
try {
Logging.start(LOG_DIRECTORY);
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
LOG.info("*** " + Metadata.TITLE + " ***");
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
@@ -17,16 +17,12 @@
*/
package org.jackhuang.hmcl;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -40,6 +36,7 @@ public final class Main {
public static void main(String[] args) {
System.setProperty("java.net.useSystemProxies", "true");
System.setProperty("http.agent", "HMCL/" + Metadata.VERSION);
checkJavaFX();
checkDirectoryPath();
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl;
import org.jackhuang.hmcl.util.JarUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
/**
* Stores metadata about this application.
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.game;
import javafx.geometry.Rectangle2D;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
@@ -30,8 +30,10 @@ import org.jackhuang.hmcl.task.Scheduler;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.*;
@@ -91,9 +93,19 @@ public final class AccountHelper {
return scale(url, scaleRatio);
}
public static Rectangle2D getViewport(double scaleRatio) {
double size = 8.0 * scaleRatio;
return new Rectangle2D(size, size, size, size);
public static Image getHead(Image skin, int scaleRatio) {
final int size = 8 * scaleRatio;
final int faceOffset = (int) Math.round(scaleRatio * 4d / 9d);
BufferedImage image = SwingFXUtils.fromFXImage(skin, null);
BufferedImage head = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = head.createGraphics();
g2d.drawImage(image, faceOffset, faceOffset, size - faceOffset, size - faceOffset,
size, size, size + size, size + size, null);
if (image.getHeight() > 32) {
g2d.drawImage(image, 0, 0, size, size,
40 * scaleRatio, 8 * scaleRatio, 48 * scaleRatio, 16 * scaleRatio, null);
}
return SwingFXUtils.toFXImage(head, null);
}
private static class SkinLoadTask extends Task {
@@ -159,11 +171,11 @@ public final class AccountHelper {
}
public static Image getSteveSkin(double scaleRatio) {
return scale("/assets/img/steve.png", 4);
return scale("/assets/img/steve.png", scaleRatio);
}
public static Image getAlexSkin(double scaleRatio) {
return scale("/assets/img/alex.png", 4);
return scale("/assets/img/alex.png", scaleRatio);
}
public static Image getDefaultSkin(UUID uuid, double scaleRatio) {
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.game;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
@@ -26,8 +27,8 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
@@ -144,10 +145,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
return setting;
}
public File getVersionIcon(String id) {
public File getVersionIconFile(String id) {
return new File(getVersionRoot(id), "icon.png");
}
public Image getVersionIconImage(String id) {
if (id == null)
return new Image("/assets/img/grass.png");
Version version = getVersion(id);
File iconFile = getVersionIconFile(id);
if (iconFile.exists())
return new Image("file:" + iconFile.getAbsolutePath());
else if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
return new Image("/assets/img/furnace.png");
else
return new Image("/assets/img/grass.png");
}
public boolean saveVersionSetting(String id) {
if (!versionSettings.containsKey(id))
return false;
@@ -19,9 +19,9 @@ package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.Zipper;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.Zipper;
import java.io.File;
import java.util.ArrayList;
@@ -73,8 +73,8 @@ public class HMCLModpackExportTask extends Task {
Version mv = repository.getResolvedVersion(version);
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
zip.putTextFile(Constants.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
zip.putTextFile(Constants.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
zip.putTextFile(JsonUtils.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
zip.putTextFile(JsonUtils.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
}
}
}
@@ -27,9 +27,9 @@ import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackInstallTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
@@ -65,7 +65,7 @@ public final class HMCLModpackInstallTask extends Task {
ModpackConfiguration<Modpack> config = null;
try {
if (json.exists()) {
config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
}.getType());
if (!MODPACK_TYPE.equals(config.getType()))
@@ -89,7 +89,7 @@ public final class HMCLModpackInstallTask extends Task {
@Override
public void execute() throws Exception {
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
Version version = Constants.GSON.fromJson(json, Version.class).setId(name).setJar(null);
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
dependencies.add(new VersionJsonSaveTask(repository, version));
dependencies.add(new MinecraftInstanceTask<>(zipFile, "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
@@ -19,10 +19,10 @@ package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import java.io.File;
import java.io.IOException;
@@ -41,6 +41,13 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.ManagedProcess;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.io.IOException;
@@ -281,13 +288,13 @@ public final class LauncherHelper {
}
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
org.jackhuang.hmcl.util.platform.Platform.IS_64_BIT) {
final JavaVersion java32 = java;
// First find if same java version but whose platform is 64-bit installed.
Optional<JavaVersion> java64 = JavaVersion.getJREs().stream()
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.Platform.PLATFORM)
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
.max(Comparator.comparing(JavaVersion::getVersionNumber));
@@ -296,7 +303,7 @@ public final class LauncherHelper {
// Then find if other java version which satisfies requirements installed.
java64 = JavaVersion.getJREs().stream()
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.Platform.PLATFORM)
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
.filter(javaVersion -> {
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;
if (newJavaRequiredFinal) return javaVersion.getParsedVersion() >= JavaVersion.JAVA_8;
@@ -313,7 +320,7 @@ public final class LauncherHelper {
}
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
// 1.5 * 1024 is an inaccurate number.
// Actual memory limit depends on operating system and memory.
@@ -25,9 +25,9 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.FinalizedCallback;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -64,7 +64,7 @@ public final class ModpackHelper {
throw new FileNotFoundException(file.getPath());
else
try {
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
}.getType());
} catch (JsonParseException e) {
throw new IOException("Malformed modpack configuration");
@@ -35,11 +35,11 @@ import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.upgrade.UpdateChannel;
import org.jackhuang.hmcl.util.EnumOrdinalDeserializer;
import org.jackhuang.hmcl.util.FileTypeAdapter;
import org.jackhuang.hmcl.util.ObservableHelper;
import org.jackhuang.hmcl.util.gson.EnumOrdinalDeserializer;
import org.jackhuang.hmcl.util.gson.FileTypeAdapter;
import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import java.io.File;
import java.lang.reflect.Modifier;
@@ -142,9 +142,6 @@ public final class Config implements Cloneable, Observable {
@SerializedName("updateChannel")
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
@SerializedName("enableMainPageGameList")
private BooleanProperty enableMainPageGameList = new SimpleBooleanProperty(false);
@SerializedName("_version")
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
@@ -443,15 +440,4 @@ public final class Config implements Cloneable, Observable {
this.updateChannel.set(updateChannel);
}
public boolean isEnableMainPageGameList() {
return enableMainPageGameList.get();
}
public BooleanProperty enableMainPageGameListProperty() {
return enableMainPageGameList;
}
public void setEnableMainPageGameList(boolean enableMainPageGameList) {
this.enableMainPageGameList.set(enableMainPageGameList);
}
}
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.setting;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.OperatingSystem;
import java.io.IOException;
import java.nio.file.Files;
@@ -28,6 +27,10 @@ import java.nio.file.Paths;
import java.util.Map;
import java.util.logging.Level;
import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Logging.LOG;
@@ -68,7 +71,7 @@ public final class ConfigHolder {
Settings.init();
if (newlyCreated) {
saveConfig();
saveConfigSync();
// hide the config file on windows
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
@@ -122,22 +125,26 @@ public final class ConfigHolder {
return new Config();
}
private static void saveConfig() throws IOException {
LOG.info("Saving config");
private static InvocationDispatcher<String> configWriter = InvocationDispatcher.runOn(Lang::thread, content -> {
try {
Files.write(configLocation, configInstance.toJson().getBytes(UTF_8));
writeToConfig(content);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to save config", e);
throw e;
}
});
private static void writeToConfig(String content) throws IOException {
LOG.info("Saving config");
synchronized (configLocation) {
Files.write(configLocation, content.getBytes(UTF_8));
}
}
static void markConfigDirty() {
// TODO: change this to async
try {
saveConfig();
} catch (IOException ignored) {
// ignore it as it has been logged
}
configWriter.accept(configInstance.toJson());
}
private static void saveConfigSync() throws IOException {
writeToConfig(configInstance.toJson());
}
}
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import java.util.LinkedList;
import java.util.List;
@@ -31,6 +31,9 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
import org.jackhuang.hmcl.util.javafx.ImmediateStringProperty;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import java.io.File;
import java.lang.reflect.Type;
@@ -22,9 +22,10 @@ import com.google.gson.stream.JsonWriter;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import java.io.File;
import java.io.IOException;
@@ -22,6 +22,12 @@ import javafx.beans.InvalidationListener;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.LaunchOptions;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty;
import org.jackhuang.hmcl.util.javafx.ImmediateIntegerProperty;
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
import org.jackhuang.hmcl.util.javafx.ImmediateStringProperty;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.profile.ProfileList;
import org.jackhuang.hmcl.ui.versions.GameList;
import org.jackhuang.hmcl.ui.versions.VersionPage;
import org.jackhuang.hmcl.util.FutureCallback;
import org.jackhuang.hmcl.util.JavaVersion;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import java.util.function.Consumer;
@@ -31,18 +31,24 @@ import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.SelectionModelSelectedItemProperty;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
@@ -201,6 +207,14 @@ public final class FXUtils {
return region.getMaxHeight();
}
public static Node limitingSize(Node node, double width, double height) {
StackPane pane = new StackPane(node);
pane.setAlignment(Pos.CENTER);
FXUtils.setLimitWidth(pane, width);
FXUtils.setLimitHeight(pane, height);
return pane;
}
public static void smoothScrolling(ScrollPane scrollPane) {
JFXScrollPane.smoothScrolling(scrollPane);
}
@@ -465,4 +479,11 @@ public final class FXUtils {
return "Interpolator.SINE";
}
};
public static Runnable withJFXPopupClosing(Runnable runnable, JFXPopup popup) {
return () -> {
runnable.run();
popup.hide();
};
}
}
@@ -1,241 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.VersionNumber;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameVersionListPage extends StackPane {
private final JFXSpinner spinner;
private final StackPane contentPane;
private final JFXMasonryPane masonryPane;
private Profile profile;
public GameVersionListPage() {
spinner = new JFXSpinner();
spinner.getStyleClass().setAll("first-spinner");
contentPane = new StackPane();
ScrollPane scrollPane = new ScrollPane();
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setFitToWidth(true);
masonryPane = new JFXMasonryPane();
masonryPane.setHSpacing(3);
masonryPane.setVSpacing(3);
masonryPane.setCellWidth(182);
masonryPane.setCellHeight(153);
scrollPane.setContent(masonryPane);
VBox vBox = new VBox();
vBox.setPadding(new Insets(15));
vBox.setPickOnBounds(false);
vBox.setAlignment(Pos.BOTTOM_RIGHT);
vBox.setSpacing(15);
JFXButton btnRefresh = new JFXButton();
btnRefresh.setPrefWidth(40);
btnRefresh.setPrefHeight(40);
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
btnRefresh.getStyleClass().setAll("jfx-button-raised-round");
btnRefresh.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
btnRefresh.setOnMouseClicked(e -> profile.getRepository().refreshVersionsAsync().start());
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
vBox.getChildren().setAll(btnRefresh);
contentPane.getChildren().setAll(scrollPane, vBox);
getChildren().setAll(spinner);
Profiles.selectedProfileProperty().addListener((o, a, b) -> this.profile = b);
profile = Profiles.getSelectedProfile();
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
if (event.getSource() == profile.getRepository())
loadVersions((HMCLGameRepository) event.getSource());
});
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
if (event.getSource() == profile.getRepository())
// This will occupy 0.5s. Too slow!
JFXUtilities.runInFX(this::loadingVersions);
});
if (profile.getRepository().isLoaded())
loadVersions(profile.getRepository());
else
profile.getRepository().refreshVersionsAsync().start();
}
private String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
private Node buildNode(HMCLGameRepository repository, Version version, Callable<String> gameCallable) {
Profile profile = repository.getProfile();
String id = version.getId();
VersionItem item = new VersionItem();
item.setUpdate(repository.isModpack(id));
Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> {
String game = vars.get("game");
item.setGameVersion(game);
StringBuilder libraries = new StringBuilder();
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
analyzer.getForge().ifPresent(library -> libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n"));
analyzer.getLiteLoader().ifPresent(library -> libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n"));
analyzer.getOptiFine().ifPresent(library -> libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n"));
item.setLibraries(libraries.toString());
});
item.setVersionName(id);
item.setOnLaunchButtonClicked(e -> Versions.launch(profile, id));
item.setOnScriptButtonClicked(e -> Versions.generateLaunchScript(profile, id));
item.setOnSettingsButtonClicked(e -> {
Controllers.getVersionPage().load(id, profile);
Controllers.navigate(Controllers.getVersionPage());
});
item.setOnUpdateButtonClicked(event -> {
FileChooser chooser = new FileChooser();
chooser.setTitle(i18n("modpack.choose"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
AtomicReference<Region> region = new AtomicReference<>();
try {
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
executor.start();
} catch (UnsupportedModpackException e) {
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (MismatchedModpackTypeException e) {
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (IOException e) {
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
}
}
});
item.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.SECONDARY) {
JFXListView<String> versionList = new JFXListView<>();
JFXPopup versionPopup = new JFXPopup(versionList);
versionList.getStyleClass().add("option-list-view");
FXUtils.setLimitWidth(versionList, 150);
versionList.getItems().setAll(Lang.immutableListOf(
i18n("version.manage.rename"),
i18n("version.manage.remove"),
i18n("modpack.export"),
i18n("folder.game")
));
versionList.setOnMouseClicked(e -> {
versionPopup.hide();
switch (versionList.getSelectionModel().getSelectedIndex()) {
case 0:
Versions.renameVersion(profile, id);
break;
case 1:
Versions.deleteVersion(profile, id);
break;
case 2:
Versions.exportVersion(profile, id);
break;
case 3:
FXUtils.openFolder(repository.getRunDirectory(id));
break;
default:
break;
}
});
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
Versions.launch(profile, id);
}
});
File iconFile = repository.getVersionIcon(id);
if (iconFile.exists())
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
return item;
}
private void loadingVersions() {
getChildren().setAll(spinner);
masonryPane.getChildren().clear();
}
private void loadVersions(HMCLGameRepository repository) {
List<Node> children = repository.getVersions().parallelStream()
.filter(version -> !version.isHidden())
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
.collect(Collectors.toList());
JFXUtilities.runInFX(() -> {
if (profile == repository.getProfile()) {
masonryPane.getChildren().setAll(children);
getChildren().setAll(contentPane);
}
});
}
}
@@ -19,9 +19,7 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.application.Platform;
import javafx.beans.binding.When;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.HMCLGameRepository;
@@ -63,16 +61,7 @@ public final class LeftPaneController extends AdvancedListBox {
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
IconedItem launcherSettingsItem = new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), "iconed-item");
launcherSettingsItem.getLabel().textProperty().bind(
new When(UpdateChecker.outdatedProperty())
.then(i18n("update.found"))
.otherwise(i18n("settings.launcher")));
launcherSettingsItem.getLabel().textFillProperty().bind(
new When(UpdateChecker.outdatedProperty())
.then(Color.RED)
.otherwise(Color.BLACK));
launcherSettingsItem.getLabel().setText(i18n("settings.launcher"));
launcherSettingsItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
@@ -34,10 +34,10 @@ import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -20,6 +20,12 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.fxml.FXML;
@@ -28,20 +34,24 @@ import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.versions.GameItem;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.util.VersionNumber;
import org.jackhuang.hmcl.upgrade.RemoteVersion;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.List;
import java.util.stream.Collectors;
@@ -51,34 +61,63 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class MainPage extends StackPane implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page"));
private final VBox menu = new VBox();
private final PopupMenu menu = new PopupMenu();
private final JFXPopup popup = new JFXPopup(menu);
private InvalidationListener updateListener;
@FXML
private StackPane main;
@FXML
private StackPane updatePane;
@FXML
private JFXButton btnLaunch;
@FXML
private JFXButton btnMenu;
@FXML
private JFXButton closeButton;
@FXML
private Label lblCurrentGame;
@FXML
private Label lblIcon;
@FXML
private TwoLineListItem lblLatestVersion;
@FXML
private Rectangle separator;
private Profile profile;
{
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
FXUtils.onChangeAndOperate(ConfigHolder.config().enableMainPageGameListProperty(), newValue -> {
if (newValue)
getChildren().setAll(new GameVersionListPage());
else
getChildren().setAll(main);
});
btnLaunch.setClip(new Rectangle(-100, -100, 310, 200));
btnMenu.setClip(new Rectangle(211, -100, 100, 200));
menu.setMaxHeight(400);
btnLaunch.setClip(new Rectangle(-100, -100, 280, 200));
btnMenu.setClip(new Rectangle(181, -100, 100, 200));
menu.setMinWidth(200);
updatePane.visibleProperty().bind(UpdateChecker.outdatedProperty());
closeButton.setGraphic(SVG.close(Theme.whiteFillBinding(), 10, 10));
closeButton.setOnMouseClicked(event -> {
Duration duration = Duration.millis(320);
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(updatePane.translateXProperty(), 0, Interpolator.EASE_IN)),
new KeyFrame(duration,
new KeyValue(updatePane.translateXProperty(), 260, Interpolator.EASE_IN)),
new KeyFrame(duration, e -> {
updatePane.visibleProperty().unbind();
updatePane.setVisible(false);
}));
nowAnimation.play();
});
lblIcon.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20));
updateListener = any -> {
if (UpdateChecker.isOutdated()) {
lblLatestVersion.setTitle(i18n("update.bubble.title", UpdateChecker.getLatestVersion().getVersion()));
}
};
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
updateListener.invalidated(null);
StackPane graphic = new StackPane();
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
@@ -111,17 +150,26 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
private void loadVersions(HMCLGameRepository repository) {
List<IconedMenuItem> children = repository.getVersions().parallelStream()
List<Node> children = repository.getVersions().parallelStream()
.filter(version -> !version.isHidden())
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
.map(version -> new IconedMenuItem(null, version.getId(), () -> {
repository.getProfile().setSelectedVersion(version.getId());
popup.hide();
}))
.map(version -> {
StackPane pane = new StackPane();
GameItem item = new GameItem(repository.getProfile(), version.getId());
pane.getChildren().setAll(item);
pane.getStyleClass().setAll("menu-container");
item.setMouseTransparent(true);
RipplerContainer container = new RipplerContainer(pane);
container.setOnMouseClicked(e -> {
repository.getProfile().setSelectedVersion(version.getId());
popup.hide();
});
return container;
})
.collect(Collectors.toList());
JFXUtilities.runInFX(() -> {
if (profile == repository.getProfile())
menu.getChildren().setAll(children);
menu.getContent().setAll(children);
});
}
@@ -136,6 +184,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
popup.show(btnMenu, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -btnMenu.getHeight());
}
@FXML
private void onUpgrade() {
RemoteVersion target = UpdateChecker.getLatestVersion();
if (target == null) {
return;
}
UpdateHandler.updateFrom(target);
}
public String getTitle() {
return title.get();
}
@@ -154,4 +154,8 @@ public final class SVG {
public static Node triangle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M1,21H23L12,2", fill, width, height);
}
public static Node home(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z", fill, width, height);
}
}
@@ -46,8 +46,8 @@ import java.util.Objects;
import java.util.Optional;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.SelectionModelSelectedItemProperty.selectedItemPropertyFor;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.SelectionModelSelectedItemProperty.selectedItemPropertyFor;
public final class SettingsPage extends SettingsView implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));
@@ -64,8 +64,6 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
// ====
chkEnableGameList.selectedProperty().bindBidirectional(config().enableMainPageGameListProperty());
cboFont.initValue(Settings.instance().getFont());
cboFont.valueProperty().addListener((a, b, newValue) -> {
Font font = Font.font(newValue, Settings.instance().getFont().getSize());
@@ -64,7 +64,6 @@ public abstract class SettingsView extends StackPane {
protected final JFXCheckBox chkProxyAuthentication;
protected final GridPane authPane;
protected final Pane proxyPane;
protected final JFXToggleButton chkEnableGameList;
public SettingsView() {
scroll = new ScrollPane();
@@ -165,23 +164,6 @@ public abstract class SettingsView extends StackPane {
settingsPane.getContent().add(backgroundItem);
}
{
BorderPane borderPane = new BorderPane();
Label left = new Label(i18n("settings.launcher.enable_game_list"));
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
borderPane.setLeft(left);
chkEnableGameList = new JFXToggleButton();
chkEnableGameList.setSize(8);
FXUtils.setLimitHeight(chkEnableGameList, 20);
borderPane.setRight(chkEnableGameList);
// Do not uncomment this line,
// since option for user to enable game list in main page is unnecessary
// settingsPane.addChildren(borderPane);
}
{
BorderPane downloadSourcePane = new BorderPane();
{
@@ -1,231 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
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.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import org.jackhuang.hmcl.setting.Theme;
import java.util.Optional;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
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 btnSettings;
@FXML
private JFXButton btnUpdate;
@FXML
private JFXButton btnLaunch;
@FXML
private JFXButton btnScript;
@FXML
private Label lblVersionName;
@FXML
private Label lblGameVersion;
@FXML
private Label lblLibraries;
@FXML
private ImageView iconView;
private EventHandler<? super MouseEvent> launchClickedHandler = null;
private void initializeComponents() {
setPickOnBounds(false);
FXUtils.setLimitWidth(this, 160);
FXUtils.setLimitHeight(this, 149);
content = new VBox();
{
header = new StackPane();
VBox.setVgrow(header, Priority.ALWAYS);
header.setPickOnBounds(false);
header.setPadding(new Insets(8));
header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87);");
{
VBox headerContent = new VBox();
headerContent.setPadding(new Insets(8, 8, 0, 8));
{
lblVersionName = new Label();
lblVersionName.setStyle("-fx-font-size: 15;");
lblVersionName.setTextAlignment(TextAlignment.JUSTIFY);
lblVersionName.setWrapText(true);
headerContent.getChildren().add(lblVersionName);
lblGameVersion = new Label();
lblGameVersion.setStyle("-fx-font-size: 11;");
lblGameVersion.setTextAlignment(TextAlignment.JUSTIFY);
lblGameVersion.setWrapText(true);
headerContent.getChildren().add(lblGameVersion);
lblLibraries = new Label();
lblLibraries.setStyle("-fx-font-size: 10; -fx-text-fill: gray;");
lblLibraries.setTextAlignment(TextAlignment.JUSTIFY);
lblLibraries.setWrapText(true);
headerContent.getChildren().add(lblLibraries);
}
header.getChildren().add(headerContent);
}
content.getChildren().add(header);
body = new StackPane();
body.setStyle("-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;");
body.setMinHeight(40);
body.setPickOnBounds(false);
{
BorderPane bodyContent = new BorderPane();
{
HBox hbox = new HBox();
hbox.setSpacing(8);
{
btnSettings = new JFXButton();
btnSettings.getStyleClass().add("toggle-icon4");
FXUtils.setLimitWidth(btnSettings, 30);
FXUtils.setLimitHeight(btnSettings, 30);
hbox.getChildren().add(btnSettings);
btnUpdate = new JFXButton();
btnUpdate.getStyleClass().add("toggle-icon4");
FXUtils.setLimitWidth(btnUpdate, 30);
FXUtils.setLimitHeight(btnUpdate, 30);
hbox.getChildren().add(btnUpdate);
}
bodyContent.setLeft(hbox);
}
{
HBox hbox = new HBox();
hbox.setSpacing(8);
{
btnScript = new JFXButton();
btnScript.getStyleClass().add("toggle-icon4");
FXUtils.setLimitWidth(btnScript, 30);
FXUtils.setLimitHeight(btnScript, 30);
hbox.getChildren().add(btnScript);
btnLaunch = new JFXButton();
btnLaunch.getStyleClass().add("toggle-icon4");
FXUtils.setLimitWidth(btnLaunch, 30);
FXUtils.setLimitHeight(btnLaunch, 30);
hbox.getChildren().add(btnLaunch);
}
bodyContent.setRight(hbox);
}
body.getChildren().setAll(bodyContent);
}
content.getChildren().add(body);
}
getChildren().add(content);
icon = new StackPane();
StackPane.setAlignment(icon, Pos.TOP_RIGHT);
icon.setPickOnBounds(false);
{
iconView = new ImageView();
StackPane.setAlignment(iconView, Pos.CENTER_RIGHT);
StackPane.setMargin(iconView, new Insets(0, 12, 0, 0));
iconView.setImage(new Image("/assets/img/icon.png"));
icon.getChildren().add(iconView);
}
getChildren().add(icon);
}
public VersionItem() {
initializeComponents();
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
btnSettings.setGraphic(SVG.gear(Theme.blackFillBinding(), 15, 15));
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 15, 15));
btnLaunch.setGraphic(SVG.launch(Theme.blackFillBinding(), 15, 15));
btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15));
JFXUtilities.runInFX(() -> {
FXUtils.installTooltip(btnSettings, i18n("version.settings"));
FXUtils.installTooltip(btnUpdate, i18n("version.update"));
FXUtils.installTooltip(btnLaunch, i18n("version.launch"));
FXUtils.installTooltip(btnScript, i18n("version.launch_script"));
});
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
FXUtils.limitSize(iconView, 32, 32);
setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY)
if (e.getClickCount() == 2)
Optional.ofNullable(launchClickedHandler).ifPresent(h -> h.handle(e));
});
}
public void setUpdate(boolean update) {
btnUpdate.setVisible(update);
}
public void setVersionName(String versionName) {
lblVersionName.setText(versionName);
}
public void setGameVersion(String gameVersion) {
lblGameVersion.setText(gameVersion);
}
public void setLibraries(String libraries) {
lblLibraries.setText(libraries);
}
public void setImage(Image image) {
iconView.setImage(image);
}
public void setOnSettingsButtonClicked(EventHandler<? super MouseEvent> handler) {
btnSettings.setOnMouseClicked(handler);
}
public void setOnScriptButtonClicked(EventHandler<? super MouseEvent> handler) {
btnScript.setOnMouseClicked(handler);
}
public void setOnLaunchButtonClicked(EventHandler<? super MouseEvent> handler) {
launchClickedHandler = handler;
btnLaunch.setOnMouseClicked(handler);
}
public void setOnUpdateButtonClicked(EventHandler<? super MouseEvent> handler) {
btnUpdate.setOnMouseClicked(handler);
}
}
@@ -39,18 +39,18 @@ public class AccountAdvancedListItem extends AdvancedListItem {
titleProperty().set(i18n("account.missing"));
subtitleProperty().set(i18n("account.missing.add"));
imageProperty().set(new Image("/assets/img/craft_table.png"));
viewportProperty().set(null);
} else {
titleProperty().set(account.getCharacter());
subtitleProperty().set(accountSubtitle(account));
imageProperty().set(AccountHelper.getDefaultSkin(account.getUUID(), 4));
viewportProperty().set(AccountHelper.getViewport(4));
final int scaleRatio = 4;
Image defaultSkin = AccountHelper.getDefaultSkin(account.getUUID(), scaleRatio);
imageProperty().set(AccountHelper.getHead(defaultSkin, scaleRatio));
if (account instanceof YggdrasilAccount) {
AccountHelper.loadSkinAsync((YggdrasilAccount) account).subscribe(Schedulers.javafx(), () -> {
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
imageProperty().set(image);
Image image = AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio);
imageProperty().set(AccountHelper.getHead(image, scaleRatio));
});
}
}
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.MappedObservableList;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.account;
import javafx.beans.property.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.ToggleGroup;
@@ -41,7 +40,6 @@ public class AccountListItem extends Control {
private final StringProperty subtitle = new SimpleStringProperty();
private final BooleanProperty selected = new SimpleBooleanProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
private final ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>();
public AccountListItem(ToggleGroup toggleGroup, Account account) {
this.account = account;
@@ -60,13 +58,11 @@ public class AccountListItem extends Control {
subtitle.set(subtitleString.toString());
selected.set(Accounts.selectedAccountProperty().get() == account);
viewport.set(AccountHelper.getViewport(4));
if (account instanceof YggdrasilAccount) {
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
this.image.set(image);
} else {
this.image.set(AccountHelper.getDefaultSkin(account.getUUID(), 4));
}
final int scaleRatio = 4;
Image image = account instanceof YggdrasilAccount ?
AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio) :
AccountHelper.getDefaultSkin(account.getUUID(), scaleRatio);
this.image.set(AccountHelper.getHead(image, scaleRatio));
}
@Override
@@ -98,10 +94,6 @@ public class AccountListItem extends Control {
return image;
}
public ObjectProperty<Rectangle2D> viewportProperty() {
return viewport;
}
public void refresh() {
if (account instanceof YggdrasilAccount) {
// progressBar.setVisible(true);
@@ -110,8 +102,9 @@ public class AccountListItem extends Control {
// progressBar.setVisible(false);
if (isDependentsSucceeded) {
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
this.image.set(image);
final int scaleRatio = 4;
Image image = AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio);
this.image.set(AccountHelper.getHead(image, scaleRatio));
}
}).start();
}
@@ -59,7 +59,6 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
imageView.viewportProperty().bind(skinnable.viewportProperty());
imageViewContainer.getChildren().setAll(imageView);
TwoLineListItem item = new TwoLineListItem();
@@ -42,7 +42,6 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import java.util.List;
@@ -190,6 +189,7 @@ public class AddAccountPane extends StackPane {
}
private class Selector extends BorderPane implements CharacterSelector {
private final AdvancedListBox listBox = new AdvancedListBox();
private final JFXButton cancel = new JFXButton();
@@ -221,20 +221,17 @@ public class AddAccountPane extends StackPane {
for (GameProfile profile : names) {
Image image;
final int scaleRatio = 4;
try {
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4);
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, scaleRatio);
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
image = null;
image = AccountHelper.getDefaultSkin(profile.getId(), scaleRatio);
}
ImageView portraitView = new ImageView();
portraitView.setSmooth(false);
if (image == null) {
portraitView.setImage(Constants.DEFAULT_ICON.get());
} else {
portraitView.setImage(image);
portraitView.setViewport(AccountHelper.getViewport(4));
}
portraitView.setImage(AccountHelper.getHead(image, scaleRatio));
FXUtils.limitSize(portraitView, 32, 32);
IconedItem accountItem = new IconedItem(portraitView, profile.getName());
@@ -31,7 +31,7 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
@@ -28,7 +28,7 @@ import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.MappedObservableList;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
@@ -30,7 +30,6 @@ import javafx.scene.image.Image;
public class AdvancedListItem extends Control {
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
private final ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>();
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
@@ -38,10 +37,6 @@ public class AdvancedListItem extends Control {
return image;
}
public ObjectProperty<Rectangle2D> viewportProperty() {
return viewport;
}
public StringProperty titleProperty() {
return title;
}
@@ -54,7 +54,6 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
FXUtils.limitSize(imageView, 32, 32);
imageView.setPreserveRatio(true);
imageView.imageProperty().bind(skinnable.imageProperty());
imageView.viewportProperty().bind(skinnable.viewportProperty());
imageViewContainer.getChildren().setAll(imageView);
VBox vbox = new VBox();
@@ -90,7 +89,7 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
JFXButton settings = new JFXButton();
FXUtils.setLimitWidth(settings, 40);
settings.getStyleClass().setAll("toggle-icon4");
settings.setGraphic(SVG.dotsVertical(Theme.blackFillBinding(), -1, -1));
settings.setGraphic(SVG.gear(Theme.blackFillBinding(), -1, -1));
right.getChildren().setAll(settings);
root.setRight(right);
@@ -17,6 +17,8 @@
*/
package org.jackhuang.hmcl.ui.construct;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import javafx.beans.DefaultProperty;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
@@ -32,7 +34,6 @@ import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.util.MappedObservableList;
@DefaultProperty("content")
public class ComponentList extends Control {
@@ -0,0 +1,19 @@
package org.jackhuang.hmcl.ui.construct;
import javafx.geometry.Insets;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class MenuSeparator extends StackPane {
public MenuSeparator() {
Rectangle rect = new Rectangle();
rect.widthProperty().bind(widthProperty().add(-14));
rect.setHeight(1);
rect.setFill(Color.GRAY);
maxHeightProperty().set(10);
setPadding(new Insets(3));
getChildren().setAll(rect);
}
}
@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleObjectProperty;
@@ -43,6 +44,9 @@ public class Navigator extends StackPane {
public Navigator(Node init) {
stack.push(init);
getChildren().setAll(init);
Platform.runLater(() ->
fireEvent(new NavigationEvent(this, init, NavigationEvent.NAVIGATED)));
}
public void navigate(Node node) {
@@ -66,11 +70,18 @@ public class Navigator extends StackPane {
close(stack.peek());
}
public void clear() {
while (stack.size() > 1)
close(stack.peek());
}
@SuppressWarnings("unchecked")
public void close(Node from) {
FXUtils.checkFxUserThread();
stack.remove(from);
if (stack.peek() != from)
throw new IllegalStateException();
stack.pop();
Node node = stack.peek();
fireEvent(new NavigationEvent(this, from, NavigationEvent.NAVIGATING));
setContent(node);
@@ -0,0 +1,66 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;
public class PopupMenu extends Control {
private final ObservableList<Node> content = FXCollections.observableArrayList();
public PopupMenu() {
}
public ObservableList<Node> getContent() {
return content;
}
@Override
protected Skin<?> createDefaultSkin() {
return new PopupMenuSkin();
}
private class PopupMenuSkin extends SkinBase<PopupMenu> {
protected PopupMenuSkin() {
super(PopupMenu.this);
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
VBox content = new VBox();
content.getStyleClass().add("menu");
Bindings.bindContent(content.getChildren(), PopupMenu.this.getContent());
scrollPane.setContent(content);
FXUtils.smoothScrolling(scrollPane);
getChildren().setAll(scrollPane);
}
}
}
@@ -33,6 +33,7 @@ import java.util.Collections;
import java.util.List;
public class TwoLineListItem extends StackPane {
private static final String DEFAULT_STYLE_CLASS = "two-line-list-item";
private final StringProperty title = new SimpleStringProperty(this, "title");
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
@@ -64,6 +65,8 @@ public class TwoLineListItem extends StackPane {
VBox vbox = new VBox();
vbox.getChildren().setAll(lblTitle, lblSubtitle);
getChildren().setAll(vbox);
getStyleClass().add(DEFAULT_STYLE_CLASS);
}
public String getTitle() {
@@ -45,6 +45,7 @@ public class DecoratorControl extends Control {
private final BooleanProperty canRefresh = new SimpleBooleanProperty(false);
private final BooleanProperty canBack = new SimpleBooleanProperty(false);
private final BooleanProperty canClose = new SimpleBooleanProperty(false);
private final BooleanProperty showCloseAsHome = new SimpleBooleanProperty(false);
private final Stage primaryStage;
private StackPane drawerWrapper;
@@ -174,6 +175,10 @@ public class DecoratorControl extends Control {
return canClose;
}
public BooleanProperty showCloseAsHomeProperty() {
return showCloseAsHome;
}
public ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonActionProperty() {
return onBackNavButtonAction;
}
@@ -204,10 +204,12 @@ public class DecoratorController {
if (navigator.getCurrentPage() instanceof DecoratorPage) {
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
page.onForceToClose();
} else {
navigator.close();
if (page.canForceToClose()) {
page.onForceToClose();
return;
}
}
navigator.clear();
}
private void back() {
@@ -249,14 +251,15 @@ public class DecoratorController {
if (to instanceof DecoratorPage) {
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
decorator.canCloseProperty().set(((DecoratorPage) to).canForceToClose());
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).canForceToClose());
} else {
decorator.drawerTitleProperty().unbind();
decorator.drawerTitleProperty().set("");
decorator.canCloseProperty().set(false);
decorator.showCloseAsHomeProperty().set(true);
}
decorator.canBackProperty().set(navigator.canGoBack());
decorator.canCloseProperty().set(navigator.canGoBack());
if (navigator.canGoBack()) {
decorator.setContentBackground(new Background(new BackgroundFill(Color.rgb(244, 244, 244, 0.5), CornerRadii.EMPTY, Insets.EMPTY)));
@@ -201,6 +201,13 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
if (newValue) navLeft.getChildren().setAll(backNavButton, closeNavButton);
else navLeft.getChildren().setAll(backNavButton);
});
skinnable.showCloseAsHomeProperty().addListener((a, b, newValue) -> {
if (newValue)
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
else
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
});
}
navBar.setLeft(navLeft);
@@ -28,7 +28,7 @@ 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 org.jackhuang.hmcl.util.Zipper;
import org.jackhuang.hmcl.util.io.Zipper;
import java.io.File;
import java.nio.file.Files;
@@ -31,8 +31,8 @@ import org.jackhuang.hmcl.ui.FXUtils;
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.StringUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.util.LinkedList;
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.MappedObservableList;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -9,9 +9,9 @@ import org.jackhuang.hmcl.mod.Datapack;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.MappedObservableList;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.io.File;
import java.io.IOException;
@@ -38,11 +38,7 @@ public class GameAdvancedListItem extends AdvancedListItem {
public GameAdvancedListItem() {
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
FXUtils.runLaterIf(() -> !Objects.nonNull(Profiles.getSelectedProfile()), () -> {
File iconFile = Profiles.getSelectedProfile().getRepository().getVersionIcon(version);
if (iconFile.exists())
imageProperty().set(new Image("file:" + iconFile.getAbsolutePath()));
else
imageProperty().set(new Image("/assets/img/grass.png"));
imageProperty().set(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
if (version != null) {
titleProperty().set(version);
@@ -0,0 +1,84 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.versions;
import javafx.beans.property.*;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.setting.Profile;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameItem extends Control {
private final Profile profile;
private final String version;
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
public GameItem(Profile profile, String id) {
this.profile = profile;
this.version = id;
String game = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown");
StringBuilder libraries = new StringBuilder(game);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
analyzer.getForge().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
analyzer.getLiteLoader().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
analyzer.getOptiFine().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
title.set(id);
subtitle.set(libraries.toString());
image.set(profile.getRepository().getVersionIconImage(version));
}
@Override
protected Skin<?> createDefaultSkin() {
return new GameItemSkin(this);
}
public Profile getProfile() {
return profile;
}
public String getVersion() {
return version;
}
public StringProperty titleProperty() {
return title;
}
public StringProperty subtitleProperty() {
return subtitle;
}
public ObjectProperty<Image> imageProperty() {
return image;
}
private static String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
}
@@ -0,0 +1,36 @@
package org.jackhuang.hmcl.ui.versions;
import javafx.geometry.Pos;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
public class GameItemSkin extends SkinBase<GameItem> {
public GameItemSkin(GameItem skinnable) {
super(skinnable);
HBox center = new HBox();
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
StackPane imageViewContainer = new StackPane();
FXUtils.setLimitWidth(imageViewContainer, 32);
FXUtils.setLimitHeight(imageViewContainer, 32);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
imageViewContainer.getChildren().setAll(imageView);
TwoLineListItem item = new TwoLineListItem();
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
getChildren().setAll(center);
}
}
@@ -34,8 +34,8 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
import org.jackhuang.hmcl.util.VersionNumber;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.List;
import java.util.stream.Collectors;
@@ -21,27 +21,16 @@ import javafx.beans.property.*;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.Controllers;
import java.io.File;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameListItem extends Control {
private final Profile profile;
private final String version;
private final boolean isModpack;
private final ToggleGroup toggleGroup;
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
private final BooleanProperty selected = new SimpleBooleanProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
public GameListItem(ToggleGroup toggleGroup, Profile profile, String id) {
this.profile = profile;
@@ -49,23 +38,7 @@ public class GameListItem extends Control {
this.toggleGroup = toggleGroup;
this.isModpack = profile.getRepository().isModpack(id);
String game = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown");
StringBuilder libraries = new StringBuilder(game);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
analyzer.getForge().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
analyzer.getLiteLoader().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
analyzer.getOptiFine().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
title.set(id);
subtitle.set(libraries.toString());
selected.set(id.equals(profile.getSelectedVersion()));
File iconFile = profile.getRepository().getVersionIcon(version);
if (iconFile.exists())
image.set(new Image("file:" + iconFile.getAbsolutePath()));
else
image.set(new Image("/assets/img/grass.png"));
}
@Override
@@ -85,22 +58,10 @@ public class GameListItem extends Control {
return version;
}
public StringProperty titleProperty() {
return title;
}
public StringProperty subtitleProperty() {
return subtitle;
}
public BooleanProperty selectedProperty() {
return selected;
}
public ObjectProperty<Image> imageProperty() {
return image;
}
public void checkSelection() {
selected.set(version.equals(profile.getSelectedVersion()));
}
@@ -141,8 +102,4 @@ public class GameListItem extends Control {
public void update() {
Versions.updateVersion(profile, version);
}
private static String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
}
@@ -19,27 +19,19 @@ package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jackhuang.hmcl.ui.construct.MenuSeparator;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -57,48 +49,22 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
chkSelected.setToggleGroup(skinnable.getToggleGroup());
root.setLeft(chkSelected);
HBox center = new HBox();
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
root.setCenter(new GameItem(skinnable.getProfile(), skinnable.getVersion()));
StackPane imageViewContainer = new StackPane();
FXUtils.setLimitWidth(imageViewContainer, 32);
FXUtils.setLimitHeight(imageViewContainer, 32);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
imageViewContainer.getChildren().setAll(imageView);
TwoLineListItem item = new TwoLineListItem();
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
root.setCenter(center);
VBox menu = new VBox();
PopupMenu menu = new PopupMenu();
JFXPopup popup = new JFXPopup(menu);
Function<Runnable, Runnable> wrap = r -> () -> {
r.run();
popup.hide();
};
Function<Node, Node> limitWidth = node -> {
StackPane pane = new StackPane(node);
pane.setAlignment(Pos.CENTER);
FXUtils.setLimitWidth(pane, 14);
FXUtils.setLimitHeight(pane, 14);
return pane;
};
menu.getChildren().setAll(
new IconedMenuItem(limitWidth.apply(SVG.gear(Theme.blackFillBinding(), 14, 14)), i18n("version.manage.manage"), wrap.apply(skinnable::modifyGameSettings)),
new IconedMenuItem(limitWidth.apply(SVG.pencil(Theme.blackFillBinding(), 14, 14)), i18n("version.manage.rename"), wrap.apply(skinnable::rename)),
new IconedMenuItem(limitWidth.apply(SVG.delete(Theme.blackFillBinding(), 14, 14)), i18n("version.manage.remove"), wrap.apply(skinnable::remove)),
new IconedMenuItem(limitWidth.apply(SVG.export(Theme.blackFillBinding(), 14, 14)), i18n("modpack.export"), wrap.apply(skinnable::export)),
new IconedMenuItem(limitWidth.apply(SVG.folderOpen(Theme.blackFillBinding(), 14, 14)), i18n("folder.game"), wrap.apply(skinnable::browse)),
new IconedMenuItem(limitWidth.apply(SVG.launch(Theme.blackFillBinding(), 14, 14)), i18n("version.launch"), wrap.apply(skinnable::launch)),
new IconedMenuItem(limitWidth.apply(SVG.script(Theme.blackFillBinding(), 14, 14)), i18n("version.launch_script"), wrap.apply(skinnable::generateLaunchScript)));
menu.getContent().setAll(
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(skinnable::modifyGameSettings, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(skinnable::rename, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(skinnable::remove, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)));
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
@@ -123,8 +89,6 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
JFXDepthManager.setDepth(root, 1);
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
getChildren().setAll(root);
}
@@ -28,8 +28,8 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.controls.JFXTabPane;
import javafx.beans.property.ReadOnlyStringProperty;
@@ -26,11 +25,14 @@ import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
@@ -50,10 +52,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
@FXML
private WorldListPage world;
@FXML
private JFXListView<?> browseList;
@FXML
private JFXListView<?> managementList;
@FXML
private JFXButton btnBrowseMenu;
@FXML
private JFXButton btnDelete;
@@ -77,10 +75,25 @@ public final class VersionPage extends StackPane implements DecoratorPage {
{
FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml");
getChildren().removeAll(browseList, managementList);
PopupMenu browseList = new PopupMenu();
browsePopup = new JFXPopup(browseList);
browseList.getContent().setAll(
new IconedMenuItem(null, i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> onBrowse(""), browsePopup)),
new IconedMenuItem(null, i18n("folder.mod"), FXUtils.withJFXPopupClosing(() -> onBrowse("mods"), browsePopup)),
new IconedMenuItem(null, i18n("folder.config"), FXUtils.withJFXPopupClosing(() -> onBrowse("config"), browsePopup)),
new IconedMenuItem(null, i18n("folder.resourcepacks"), FXUtils.withJFXPopupClosing(() -> onBrowse("resourcepacks"), browsePopup)),
new IconedMenuItem(null, i18n("folder.screenshots"), FXUtils.withJFXPopupClosing(() -> onBrowse("screenshots"), browsePopup)),
new IconedMenuItem(null, i18n("folder.saves"), FXUtils.withJFXPopupClosing(() -> onBrowse("resourcepacks"), browsePopup))
);
PopupMenu managementList = new PopupMenu();
managementPopup = new JFXPopup(managementList);
managementList.getContent().setAll(
new IconedMenuItem(null, i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> Versions.renameVersion(profile, version), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> Versions.deleteVersion(profile, version), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(() -> new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getResolvedVersion(version)).start(), managementPopup)),
new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(() -> FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries")), managementPopup))
);
FXUtils.installTooltip(btnDelete, i18n("version.manage.remove"));
FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
@@ -104,67 +117,18 @@ public final class VersionPage extends StackPane implements DecoratorPage {
@FXML
private void onBrowseMenu() {
browseList.getSelectionModel().select(-1);
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnBrowseMenu.getHeight());
}
@FXML
private void onManagementMenu() {
managementList.getSelectionModel().select(-1);
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnManagementMenu.getHeight());
}
@FXML
private void onBrowse() {
String sub;
switch (browseList.getSelectionModel().getSelectedIndex()) {
case 0:
sub = "";
break;
case 1:
sub = "mods";
break;
case 2:
sub = "coremods";
break;
case 3:
sub = "config";
break;
case 4:
sub = "resourcepacks";
break;
case 5:
sub = "screenshots";
break;
case 6:
sub = "saves";
break;
default:
return;
}
private void onBrowse(String sub) {
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
}
@FXML
private void onManagement() {
switch (managementList.getSelectionModel().getSelectedIndex()) {
case 0: // rename a version
Versions.renameVersion(profile, version);
break;
case 1: // remove a version
Versions.deleteVersion(profile, version);
break;
case 2: // redownload asset index
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getResolvedVersion(version)).start();
break;
case 3: // delete libraries
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
break;
case 4:
throw new Error();
}
}
public String getTitle() {
return title.get();
}
@@ -46,10 +46,10 @@ import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.ImagePickerItem;
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.JavaVersion;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.OperatingSystem;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
@@ -276,7 +276,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
File iconFile = profile.getRepository().getVersionIcon(versionId);
File iconFile = profile.getRepository().getVersionIconFile(versionId);
try {
FileUtils.copyFile(selectedFile, iconFile);
loadIcon();
@@ -291,7 +291,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
if (versionId == null)
return;
File iconFile = profile.getRepository().getVersionIcon(versionId);
File iconFile = profile.getRepository().getVersionIconFile(versionId);
if (iconFile.exists())
iconFile.delete();
loadIcon();
@@ -303,7 +303,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
return;
}
File iconFile = profile.getRepository().getVersionIcon(versionId);
File iconFile = profile.getRepository().getVersionIconFile(versionId);
if (iconFile.exists())
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
else
@@ -36,8 +36,8 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.OperatingSystem;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
@@ -92,13 +92,10 @@ public class Versions {
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
executor.start();
} catch (UnsupportedModpackException e) {
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (MismatchedModpackTypeException e) {
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (IOException e) {
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
}
}
@@ -1,6 +1,9 @@
package org.jackhuang.hmcl.ui.versions;
import javafx.beans.property.*;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
@@ -8,10 +11,11 @@ import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.IntVersionNumber;
import org.jackhuang.hmcl.util.VersionNumber;
import org.jackhuang.hmcl.util.versioning.IntVersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -21,12 +25,14 @@ public class WorldListItem extends Control {
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
private final World world;
private final SimpleDateFormat simpleDateFormat;
public WorldListItem(World world) {
this.world = world;
this.simpleDateFormat = new SimpleDateFormat(i18n("world.time"));
title.set(world.getWorldName());
subtitle.set(i18n("world.description", world.getFileName(), new Date(world.getLastPlayed()).toString(), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
subtitle.set(i18n("world.description", world.getFileName(), simpleDateFormat.format(new Date(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
}
@Override
@@ -4,21 +4,18 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import java.util.function.Function;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItemSkin extends SkinBase<WorldListItem> {
@@ -46,25 +43,12 @@ public class WorldListItemSkin extends SkinBase<WorldListItem> {
center.getChildren().setAll(imageView, item);
root.setCenter(center);
VBox menu = new VBox();
PopupMenu menu = new PopupMenu();
JFXPopup popup = new JFXPopup(menu);
Function<Runnable, Runnable> wrap = r -> () -> {
r.run();
popup.hide();
};
Function<Node, Node> limitWidth = node -> {
StackPane pane = new StackPane(node);
pane.setAlignment(Pos.CENTER);
FXUtils.setLimitWidth(pane, 14);
FXUtils.setLimitHeight(pane, 14);
return pane;
};
menu.getChildren().setAll(
new IconedMenuItem(limitWidth.apply(SVG.gear(Theme.blackFillBinding(), 14, 14)), i18n("world.datapack"), wrap.apply(skinnable::manageDatapacks)),
new IconedMenuItem(limitWidth.apply(SVG.export(Theme.blackFillBinding(), 14, 14)), i18n("world.export"), wrap.apply(skinnable::export)));
menu.getContent().setAll(
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("world.datapack"), FXUtils.withJFXPopupClosing(skinnable::manageDatapacks, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("world.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)));
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
@@ -17,8 +17,6 @@
*/
package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.util.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
@@ -30,6 +28,8 @@ import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jackhuang.hmcl.util.io.IOUtils;
import static java.nio.file.StandardOpenOption.*;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.tukaani.xz.XZInputStream;
import java.io.ByteArrayInputStream;
@@ -18,8 +18,8 @@
package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.JarUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import java.io.IOException;
import java.io.InputStream;
@@ -21,8 +21,8 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.Optional;
@@ -24,7 +24,7 @@ import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.logging.Level;
@@ -35,7 +35,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
import static org.jackhuang.hmcl.util.versioning.VersionNumber.asVersion;
public final class UpdateChecker {
private UpdateChecker() {}
@@ -83,7 +83,7 @@ public final class UpdateChecker {
}
private static RemoteVersion checkUpdate(UpdateChannel channel) throws IOException {
if (!IntegrityChecker.isSelfVerified()) {
if (!IntegrityChecker.isSelfVerified() && !"true".equals(System.getProperty("hmcl.self_integrity_check.disable"))) {
throw new IOException("Self verification failed");
}
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.upgrade;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import javafx.application.Platform;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Launcher;
@@ -28,9 +29,9 @@ import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.JarUtils;
import org.jackhuang.hmcl.util.JavaVersion;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import javax.swing.*;
import java.io.IOException;
@@ -239,18 +240,20 @@ public final class UpdateHandler {
}
private static void breakForceUpdateFeature() {
try {
Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
if (Files.isRegularFile(hmclVersionJson)) {
Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
if (Files.isRegularFile(hmclVersionJson)) {
try {
Map<?, ?> content = new Gson().fromJson(new String(Files.readAllBytes(hmclVersionJson), UTF_8), Map.class);
Object ver = content.get("ver");
if (ver instanceof String && ((String) ver).startsWith("3.")) {
Files.delete(hmclVersionJson);
LOG.info("Successfully broke the force update feature");
}
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to break the force update feature", e);
} catch (JsonParseException e) {
hmclVersionJson.toFile().delete();
}
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to break the force update feature", e);
}
}
// ====
@@ -23,6 +23,8 @@ import org.jackhuang.hmcl.ui.CrashWindow;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.IOException;
import java.text.SimpleDateFormat;
+34 -3
View File
@@ -59,6 +59,10 @@
-fx-padding: 20 0 20 0;
}
.iconed-item {
-jfx-rippler-fill: -fx-base-color;
}
.iconed-item .iconed-item-container {
-fx-padding: 10 16 10 16;
-fx-spacing: 10;
@@ -66,13 +70,40 @@
-fx-alignment: CENTER_LEFT;
}
.iconed-menu-item {
-jfx-rippler-fill: -fx-base-color;
}
.iconed-menu-item .iconed-item-container {
-fx-padding: 10 16 10 16;
-fx-padding: 8 16 8 16;
-fx-spacing: 10;
-fx-font-size: 12;
-fx-alignment: CENTER_LEFT;
}
.menu-container {
-fx-padding: 8 16 8 16;
}
.menu {
-fx-padding: 4 0 4 0;
}
.bubble {
-fx-background-color: gray;
-fx-background-radius: 2px;
}
.bubble .label {
-fx-text-fill: white;
}
.two-line-list-item {
-jfx-title-font-size: 15px;
-jfx-title-fill: black;
-jfx-subtitle-fill: gray;
}
/*******************************************************************************
* *
* JFX Tab Pane *
@@ -181,7 +212,6 @@
.jfx-popup-container {
-fx-background-color: WHITE;
-fx-pref-width: 151.0px;
}
.popup-list-view {
@@ -309,6 +339,7 @@
}
.jfx-tool-bar-second {
-fx-pref-height: 42;
-fx-padding: 2 2 2 2;
-fx-background-color: -fx-base-check-color;
}
@@ -318,7 +349,7 @@
}
.jfx-tool-bar-button {
-fx-toggle-icon4-size: 35px;
-fx-toggle-icon4-size: 37px;
-fx-pref-height: -fx-toggle-icon4-size;
-fx-max-height: -fx-toggle-icon4-size;
-fx-min-height: -fx-toggle-icon4-size;
+30 -7
View File
@@ -5,21 +5,44 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import org.jackhuang.hmcl.ui.FXUtils?>
<?import javafx.geometry.Insets?>
<?import org.jackhuang.hmcl.ui.construct.TwoLineListItem?>
<?import javafx.scene.layout.HBox?>
<fx:root type="StackPane" pickOnBounds="false"
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
<StackPane fx:id="main" style="-fx-padding: 25;">
<StackPane maxWidth="200" maxHeight="50" StackPane.alignment="BOTTOM_RIGHT">
<JFXButton prefWidth="200" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
<StackPane fx:id="updatePane" styleClass="bubble" FXUtils.limitWidth="230" FXUtils.limitHeight="55"
onMouseClicked="#onUpgrade" StackPane.alignment="TOP_RIGHT">
<HBox StackPane.alignment="CENTER_LEFT" alignment="CENTER_LEFT" spacing="12">
<Label fx:id="lblIcon" />
<TwoLineListItem titleFill="white" subtitleFill="white" subtitle="%update.bubble.subtitle" fx:id="lblLatestVersion" pickOnBounds="false"
style="-jfx-title-font-weight: BOLD;" />
<StackPane.margin>
<Insets left="16" top="9" right="12" bottom="9" />
</StackPane.margin>
</HBox>
<JFXButton fx:id="closeButton" StackPane.alignment="TOP_RIGHT" styleClass="toggle-icon-tiny">
<StackPane.margin>
<Insets topRightBottomLeft="5" />
</StackPane.margin>
</JFXButton>
</StackPane>
<StackPane maxWidth="230" maxHeight="55" StackPane.alignment="BOTTOM_RIGHT">
<JFXButton prefWidth="230" prefHeight="55" buttonType="RAISED" styleClass="jfx-button-raised"
onMouseClicked="#launch" fx:id="btnLaunch">
<graphic>
<VBox alignment="CENTER" translateX="-7" maxWidth="160">
<Label style="-fx-font-size: 15;" text="%version.launch"/>
<Label style="-fx-font-size: 10px;" fx:id="lblCurrentGame"/>
<VBox alignment="CENTER" translateX="-7" maxWidth="200">
<Label style="-fx-font-size: 16;" text="%version.launch"/>
<Label style="-fx-font-size: 12px;" fx:id="lblCurrentGame"/>
</VBox>
</graphic>
</JFXButton>
<Rectangle fx:id="separator" translateX="80" height="52" fill="gray" width="1" mouseTransparent="true"/>
<JFXButton prefWidth="200" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
<Rectangle fx:id="separator" translateX="95" height="57" fill="gray" width="1" mouseTransparent="true"/>
<JFXButton prefWidth="230" prefHeight="55" buttonType="RAISED" styleClass="jfx-button-raised"
style="-fx-font-size: 15;" onMouseClicked="#onMenu" fx:id="btnMenu" />
</StackPane>
</StackPane>
@@ -29,7 +29,7 @@
</Tab>
</JFXTabPane>
<HBox alignment="TOP_RIGHT" style="-fx-padding: 2 2 2 2;" spacing="3" pickOnBounds="false">
<HBox alignment="TOP_RIGHT" style="-fx-padding: 4;" spacing="3" pickOnBounds="false">
<JFXButton fx:id="btnBrowseMenu" maxHeight="40.0" minHeight="40.0" onMouseClicked="#onBrowseMenu"
styleClass="toggle-icon3">
<graphic>
@@ -44,23 +44,4 @@
</JFXButton>
</HBox>
</StackPane>
<JFXListView fx:id="browseList" styleClass="option-list-view" onMouseClicked="#onBrowse" maxWidth="150.0"
minWidth="150.0">
<Label text="%folder.game"/>
<Label text="%folder.mod"/>
<Label text="%folder.coremod"/>
<Label text="%folder.config"/>
<Label text="%folder.resourcepacks"/>
<Label text="%folder.screenshots"/>
<Label text="%folder.saves"/>
</JFXListView>
<JFXListView fx:id="managementList" styleClass="option-list-view" onMouseClicked="#onManagement"
maxWidth="300.0" minWidth="300.0">
<Label text="%version.manage.rename"/>
<Label text="%version.manage.remove"/>
<Label text="%version.manage.redownload_assets_index"/>
<Label text="%version.manage.remove_libraries"/>
</JFXListView>
</fx:root>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

@@ -23,7 +23,7 @@ about.thanks_to=Thanks to
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors who participated in this project via issues, pull requests, etc.
about.dependency=Dependencies
# Due to space limitations, only first authors are listed
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner(jenkins-ci, CDDL, GPL 2)
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner (jenkins-ci, CDDL, GPL 2)\nOpenNBT (Steveice10, BSD 3-Clause)
about.claim=Statement
about.claim.statement=Minecraft is copyrighted by Mojang AB. We are not responsible for any copyright infringement arising from the use of this software.
about.open_source=Open Source
@@ -102,10 +102,9 @@ fatal.migration_requires_manual_reboot=The upgrade is about to be completed. Ple
fatal.apply_update_failure=We're sorry that HMCL couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading HMCL from %s.\nPlease consider reporting this issue to us.
folder.config=Configs
folder.coremod=Core Mod
folder.game=Game Dir
folder.game=Game Directory
folder.mod=Mod
folder.resourcepacks=Resourcepacks
folder.resourcepacks=Resource packs
folder.saves=Saves
folder.screenshots=Screenshots
@@ -266,6 +265,7 @@ world.import.choose=Choose the save zip to be imported
world.import.failed=Unable to import this world: %s
world.name=World Name
world.name.enter=Enter the world name
world.time=EEE, MMM d, yyyy HH:mm:ss
profile=Game Directories
profile.default=Current directory
@@ -350,6 +350,8 @@ update.checking=Checking for updates
update.failed=Failed to perform upgrade
update.found=Update Available!
update.newest_version=Latest version: %s
update.bubble.title=Update Available: %s
update.bubble.subtitle=Click here for details
update.note=Development version contains more functionality and bug fixes as well as more possible bugs.
update.latest=This is latest Version.
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
@@ -370,7 +372,7 @@ version.launch_script.failed=Unable to make launch script.
version.launch_script.save=Save the launch script
version.launch_script.success=Finished script creation, %s.
version.manage=Game List
version.manage.manage=Game Management
version.manage.manage=Manage Game
version.manage.redownload_assets_index=Redownload Assets Index
version.manage.remove=Delete this game
version.manage.remove.confirm=Sure to remove game %s? You cannot restore this game again!
@@ -23,7 +23,7 @@ about.thanks_to=鳴謝
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (預設背景圖)\n所有通過 Issues、Pull Requests 等方式參與本項目的貢獻者
about.dependency=依賴
# 由於篇幅限制,僅列出第一作者
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner(jenkins-ci, CDDL, GPL 2)
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner (jenkins-ci, CDDL, GPL 2)\nOpenNBT (Steveice10, BSD 3-Clause)
about.claim=免責聲明
about.claim.statement=Minecraft 版權歸 Mojang AB 所有,使用本軟體產生的版權問題,軟體製作方概不負責。請支援正版。
about.open_source=開源
@@ -102,7 +102,6 @@ fatal.migration_requires_manual_reboot=HMCL 即將升級完成,請重新打開
fatal.apply_update_failure=我們很抱歉 HMCL 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。\n請考慮向我們回報該問題。
folder.config=設定資料夾
folder.coremod=核心 MOD 模組資料夾
folder.game=遊戲資料夾
folder.mod=MOD 模組資料夾
folder.resourcepacks=資源包資料夾
@@ -266,10 +265,11 @@ world.import.failed=無法導入此世界: %s
world.game_version=遊戲版本
world.name=世界名稱
world.name.enter=輸入世界名稱
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
profile=遊戲目錄
profile.default=目前目錄
profile.home=主資料夾
profile.home=官方啟動器
profile.instance_directory=遊戲路徑
profile.instance_directory.choose=選擇遊戲路徑
profile.manage=遊戲目錄列表
@@ -350,6 +350,8 @@ update.checking=正在檢查更新
update.failed=更新失敗
update.found=發現到更新
update.newest_version=最新版本為:%s
update.bubble.title=發現更新:%s
update.bubble.subtitle=點擊此處查看詳情
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
update.latest=目前版本為最新版本
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址打開頁面
@@ -371,7 +373,7 @@ version.launch_script.save=儲存啟動腳本
version.launch_script.success=啟動腳本已生成完畢:%s
version.manage=遊戲列表
version.manage.manage=游戏管理
version.manage.redownload_assets_index=重新下載資源設定(assets_index.json)
version.manage.redownload_assets_index=更新資源列表
version.manage.remove=刪除該版本
version.manage.remove.confirm=真的要刪除版本 %s 嗎?你將無法找回被刪除的檔案!
version.manage.remove.confirm.trash=真的要刪除版本 %s 嗎?你可以在系統的資源回收桶 (或垃圾桶) 中還原資料夾 %s 來找回該版本。
@@ -23,7 +23,7 @@ about.thanks_to=鸣谢
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有通过 Issues、Pull Requests 等方式参与本项目的贡献者
about.dependency=依赖
# 由于篇幅限制,仅列出第一作者
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner(jenkins-ci, CDDL, GPL 2)
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner (jenkins-ci, CDDL, GPL 2)\nOpenNBT (Steveice10, BSD 3-Clause)
about.claim=免责声明
about.claim.statement=Minecraft 版权归 Mojang AB 所有,使用本软件产生的版权问题,软件制作方概不负责。请支持正版。
about.open_source=开源
@@ -102,7 +102,6 @@ fatal.migration_requires_manual_reboot=HMCL 即将完成升级,请重新打开
fatal.apply_update_failure=我们很抱歉 HMCL 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 HMCL 来完成升级。\n请考虑向我们反馈该问题。
folder.config=配置文件夹
folder.coremod=核心 MOD 文件夹
folder.game=游戏文件夹
folder.mod=MOD文件夹
folder.resourcepacks=资源包文件夹
@@ -266,10 +265,11 @@ world.import.choose=选择要导入的存档压缩包
world.import.failed=无法导入此世界:%s
world.name=世界名称
world.name.enter=输入世界名称
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
profile=游戏目录
profile.default=当前目录
profile.home=主文件夹
profile.home=官方启动器
profile.instance_directory=游戏路径
profile.instance_directory.choose=选择游戏路径
profile.manage=游戏目录列表
@@ -350,6 +350,8 @@ update.checking=正在检查更新
update.failed=更新失败
update.found=发现更新
update.newest_version=最新版本为:%s
update.bubble.title=发现更新:%s
update.bubble.subtitle=点击此处查看详情
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
update.latest=当前版本为最新版本
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
@@ -371,7 +373,7 @@ version.launch_script.save=保存启动脚本
version.launch_script.success=启动脚本已生成完毕:%s
version.manage=游戏列表
version.manage.manage=游戏管理
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
version.manage.redownload_assets_index=更新资源列表
version.manage.remove=删除该版本
version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!
version.manage.remove.confirm.trash=真的要删除版本 %s 吗?你可以在系统的回收站中恢复文件夹 %s 来找回该版本。
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="black" content="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="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" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="black" content="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="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.143 11.429 0 20.571 6.286z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath content="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" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="black" content="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="black" content="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" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="M2.81,14.12L5.64,11.29L8.17,10.79C11.39,6.41 17.55,4.22 19.78,4.22C19.78,6.45 17.59,12.61 13.21,15.83L12.71,18.36L9.88,21.19L9.17,17.66C7.76,17.66 7.76,17.66 7.05,16.95C6.34,16.24 6.34,16.24 6.34,14.83L2.81,14.12M5.64,16.95L7.05,18.36L4.39,21.03H2.97V19.61L5.64,16.95M4.22,15.54L5.46,15.71L3,18.16V16.74L4.22,15.54M8.29,18.54L8.46,19.78L7.26,21H5.84L8.29,18.54M13,9.5A1.5,1.5 0 0,0 11.5,11A1.5,1.5 0 0,0 13,12.5A1.5,1.5 0 0,0 14.5,11A1.5,1.5 0 0,0 13,9.5Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="black" content="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" />
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="black" content="M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z" />
@@ -20,8 +20,9 @@ package org.jackhuang.hmcl.auth;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import org.jackhuang.hmcl.util.ObservableHelper;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import java.util.Map;
import java.util.Optional;
@@ -25,9 +25,9 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.util.ExceptionalSupplier;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.*;
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
import org.jackhuang.hmcl.util.ExceptionalSupplier;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.io.IOException;
import java.util.Map;
@@ -22,8 +22,8 @@ import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.net.URL;
@@ -18,8 +18,8 @@
package org.jackhuang.hmcl.auth.authlibinjector;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.net.URL;
import java.util.UUID;
@@ -20,8 +20,9 @@ package org.jackhuang.hmcl.auth.authlibinjector;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.Optional;
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import java.util.Map;
import java.util.Objects;
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.auth.offline;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import java.util.Map;
import java.util.UUID;
@@ -17,7 +17,10 @@
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.Validation;
import java.util.UUID;
@@ -26,7 +29,7 @@ import java.util.UUID;
* @author huangyuhui
*/
@Immutable
public final class GameProfile {
public final class GameProfile implements Validation {
private final UUID id;
private final String name;
@@ -61,4 +64,11 @@ public final class GameProfile {
return properties;
}
@Override
public void validate() throws JsonParseException {
if (id == null)
throw new JsonParseException("Game profile id cannot be null or malformed");
if (StringUtils.isBlank(name))
throw new JsonParseException("Game profile name cannot be null or blank");
}
}
@@ -1,7 +1,7 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.net.URL;
import java.util.UUID;
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.Validation;
import org.jackhuang.hmcl.util.gson.Validation;
/**
*
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import java.util.*;
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import java.util.Map;
import java.util.Objects;
@@ -6,9 +6,10 @@ import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.gson.ValidationTypeAdapterFactory;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.net.URL;
@@ -187,6 +188,7 @@ public class YggdrasilService {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE)
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
.registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)
.create();
}
Loaded 100 of 215 files, more files were not shown because too many files have changed in this diff. Show more