Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e57729349 | ||
|
|
66ed87eb1e | ||
|
|
27ffc8eec5 | ||
|
|
e2f50c92e9 | ||
|
|
e8ff42c4b2 | ||
|
|
44ca2cdb55 | ||
|
|
77bbee02f9 | ||
|
|
e4c7b137f1 | ||
|
|
efd088e014 | ||
|
|
2ae266491c | ||
|
|
0912613cfc | ||
|
|
d9e3816b8e | ||
|
|
bdaf72fe2f | ||
|
|
7efbbefdcf | ||
|
|
ad71216bfc | ||
|
|
57903da09b | ||
|
|
cf56c8035b | ||
|
|
dbf519b435 | ||
|
|
696b9bd7ef | ||
|
|
ec5236368a | ||
|
|
8d3794d828 | ||
|
|
d1b59ef553 | ||
|
|
79f29e143e | ||
|
|
e7ffb0b271 | ||
|
|
cda2f6fb82 | ||
|
|
114aa4ce94 | ||
|
|
0ff194b391 | ||
|
|
025bde66d6 | ||
|
|
872c1943b7 | ||
|
|
d268d923d7 | ||
|
|
d523ccd312 | ||
|
|
78a5ade35a | ||
|
|
f011a14798 | ||
|
|
f4701c678e | ||
|
|
e61fb27c61 | ||
|
|
d2f58a89a2 | ||
|
|
5174e0a67a | ||
|
|
4225e98d6b | ||
|
|
0540f3c9cd | ||
|
|
4292d47fe8 | ||
|
|
4e98d80855 | ||
|
|
578aaafb0a | ||
|
|
60deea64c8 | ||
|
|
7e1cd4319b | ||
|
|
318da840f9 | ||
|
|
0c7046d764 | ||
|
|
bdf4886445 | ||
|
|
4a758d045d | ||
|
|
1059a4b3d4 | ||
|
|
a25eb14da4 | ||
|
|
349b663c83 | ||
|
|
04781da3a7 | ||
|
|
96e1ddff79 | ||
|
|
e8502890a4 | ||
|
|
4d0619a9df | ||
|
|
fba14737f9 | ||
|
|
5de98399c5 | ||
|
|
7adfa0e3d1 | ||
|
|
a45b636b1c | ||
|
|
50c22e1f43 | ||
|
|
3b38c22398 | ||
|
|
618243350e | ||
|
|
ff4940e79f | ||
|
|
1c163934ee | ||
|
|
c513d00c50 | ||
|
|
2c9549b36b | ||
|
|
3389e0a934 | ||
|
|
78149449a9 | ||
|
|
b792b23ad4 | ||
|
|
881b0e9127 | ||
|
|
77e06c8820 | ||
|
|
bc2405cf12 | ||
|
|
72b5428f8b | ||
|
|
e7e8d45497 | ||
|
|
82dbe18d78 | ||
|
|
ecb7ef0867 | ||
|
|
d40742a624 | ||
|
|
6f8002a0cf | ||
|
|
3d3896c5c4 | ||
|
|
a24f8e2ec0 | ||
|
|
fce08a6923 | ||
|
|
5fa01c8838 | ||
|
|
15a3fb5f59 | ||
|
|
d5ab14f476 | ||
|
|
554ecfd6d5 | ||
|
|
c2abe755f4 | ||
|
|
c0410c46ff | ||
|
|
da93b4c12d | ||
|
|
c16d6e8d5f | ||
|
|
6df73aeb68 | ||
|
|
23d11eca81 | ||
|
|
0e307f3b71 | ||
|
|
33b791f2b0 | ||
|
|
de01946ab2 | ||
|
|
5526bac70e | ||
|
|
741f65d37c | ||
|
|
ecafc9d6f0 | ||
|
|
8795d522b0 | ||
|
|
d8e7e0e97e | ||
|
|
8236dfc7e5 | ||
|
|
8a15b7d274 | ||
|
|
fce4c13515 | ||
|
|
2f36506099 | ||
|
|
5e60eaf595 | ||
|
|
f70fb4ae08 | ||
|
|
38adb48051 |
No files matched your search
@@ -20,6 +20,7 @@ minecraft-exported-crash-info*
|
||||
/HMCLCore/build/
|
||||
/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
|
||||
/buildSrc/build/
|
||||
|
||||
# idea
|
||||
.idea
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
/data.csv
|
||||
/data.json
|
||||
/data.json
|
||||
/mod.json
|
||||
/modpack.json
|
||||
@@ -1,3 +1,4 @@
|
||||
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
@@ -143,8 +144,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"jdk.attach/sun.tools.attach"
|
||||
).joinToString(" ")
|
||||
"jdk.attach/sun.tools.attach",
|
||||
).joinToString(" "),
|
||||
"Enable-Native-Access" to "ALL-UNNAMED"
|
||||
)
|
||||
|
||||
System.getenv("GITHUB_SHA")?.also {
|
||||
@@ -253,3 +255,15 @@ tasks.create<JavaExec>("run") {
|
||||
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
|
||||
}
|
||||
}
|
||||
|
||||
// mcmod data
|
||||
|
||||
tasks.register<ParseModDataTask>("parseModData") {
|
||||
inputFile.set(layout.projectDirectory.file("mod.json"))
|
||||
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/mod_data.txt"))
|
||||
}
|
||||
|
||||
tasks.register<ParseModDataTask>("parseModPackData") {
|
||||
inputFile.set(layout.projectDirectory.file("modpack.json"))
|
||||
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/modpack_data.txt"))
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,123 +0,0 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import json
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
S = ';'
|
||||
|
||||
MOD_SEPARATOR = ','
|
||||
|
||||
CURSEFORGE_PATTERN1 = re.compile(
|
||||
r'^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
CURSEFORGE_PATTERN2 = re.compile(
|
||||
r'^/projects/(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
CURSEFORGE_PATTERN3 = re.compile(
|
||||
r'^/mc-mods/minecraft/(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
CURSEFORGE_PATTERN4 = re.compile(
|
||||
r'^/legacy/mc-mods/minecraft/(\d+)-(?P<modid>[\w-]+)'
|
||||
)
|
||||
|
||||
|
||||
def parseCurseforge(url):
|
||||
res = urlparse(url)
|
||||
if res.scheme not in ['http', 'https']:
|
||||
return ''
|
||||
for pattern in [CURSEFORGE_PATTERN1, CURSEFORGE_PATTERN2, CURSEFORGE_PATTERN3, CURSEFORGE_PATTERN4]:
|
||||
match = pattern.match(res.path)
|
||||
if match:
|
||||
return match.group('modid')
|
||||
return ''
|
||||
|
||||
|
||||
MCMOD_PATTERN = re.compile(
|
||||
r'^http(s)?://www\.mcmod\.cn/(class|modpack)/(?P<modid>\d+)\.html$')
|
||||
|
||||
|
||||
def parseMcmod(url):
|
||||
match = MCMOD_PATTERN.match(url)
|
||||
if match:
|
||||
return match.group('modid')
|
||||
return ''
|
||||
|
||||
skip = [
|
||||
'Minecraft',
|
||||
'The Building Game'
|
||||
]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
json_name = sys.argv[1] or 'data.json'
|
||||
|
||||
with codecs.open(json_name, mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile:
|
||||
data = json.load(jsonfile)
|
||||
|
||||
for mod in data:
|
||||
chinese_name = mod['name']['main']
|
||||
sub_name = mod['name']['sub']
|
||||
abbr = mod['name']['abbr']
|
||||
|
||||
if sub_name in skip:
|
||||
continue
|
||||
|
||||
if S in chinese_name:
|
||||
print('Error! ' + chinese_name)
|
||||
exit(1)
|
||||
|
||||
if S in sub_name:
|
||||
print('Error! ' + chinese_name)
|
||||
exit(1)
|
||||
|
||||
black_lists = [
|
||||
'Wood Converter'
|
||||
]
|
||||
|
||||
curseforge_id = ''
|
||||
mcmod_id = ''
|
||||
links = mod['links']['list']
|
||||
if 'curseforge' in links and links['curseforge'] and sub_name not in black_lists and chinese_name not in black_lists:
|
||||
for link in links['curseforge']:
|
||||
curseforge_id = parseCurseforge(link['url'])
|
||||
if curseforge_id != '':
|
||||
break
|
||||
if curseforge_id == '':
|
||||
print('Error curseforge ' + chinese_name)
|
||||
exit(1)
|
||||
if 'mcmod' in links and links['mcmod']:
|
||||
mcmod_id = parseMcmod(links['mcmod'][0]['url'])
|
||||
if mcmod_id == '':
|
||||
print('Error mcmod ' + chinese_name)
|
||||
exit(1)
|
||||
|
||||
mod_id = []
|
||||
if 'modid' in mod and 'list' in mod['modid']:
|
||||
for id in mod['modid']['list']:
|
||||
if MOD_SEPARATOR in id:
|
||||
print('Error mod id!' + id)
|
||||
exit(1)
|
||||
|
||||
mod_id.append(id)
|
||||
mod_ids = MOD_SEPARATOR.join([str(id) for id in mod_id])
|
||||
|
||||
outfile.write(
|
||||
f'{curseforge_id}{S}{mcmod_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
|
||||
|
||||
print('Success!')
|
||||
@@ -225,8 +225,16 @@ public final class Launcher extends Application {
|
||||
LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null
|
||||
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION
|
||||
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')'));
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH_NAME);
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH_NAME);
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
|
||||
}
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
|
||||
LOG.info("Native Encoding: " + OperatingSystem.NATIVE_CHARSET);
|
||||
LOG.info("JNU Encoding: " + System.getProperty("sun.jnu.encoding"));
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
LOG.info("Code Page: " + OperatingSystem.CODE_PAGE);
|
||||
}
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
@@ -245,7 +253,6 @@ public final class Launcher extends Application {
|
||||
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
|
||||
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
|
||||
}
|
||||
|
||||
launch(Launcher.class, args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import org.jackhuang.hmcl.ui.AwtUtils;
|
||||
import org.jackhuang.hmcl.util.ModuleHelper;
|
||||
import org.jackhuang.hmcl.util.SelfDependencyPatcher;
|
||||
import org.jackhuang.hmcl.ui.SwingUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
@@ -31,6 +32,7 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -69,6 +71,8 @@ public final class Main {
|
||||
|
||||
checkJavaFX();
|
||||
verifyJavaFX();
|
||||
addEnableNativeAccess();
|
||||
enableUnsafeMemoryAccess();
|
||||
|
||||
Launcher.main(args);
|
||||
}
|
||||
@@ -79,7 +83,7 @@ public final class Main {
|
||||
}
|
||||
|
||||
private static void initIcon() {
|
||||
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon@8x.png"));
|
||||
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon-mac.png"));
|
||||
AwtUtils.setAppleIcon(image);
|
||||
}
|
||||
|
||||
@@ -116,10 +120,36 @@ public final class Main {
|
||||
Class.forName("javafx.stage.Stage"); // javafx.graphics
|
||||
Class.forName("javafx.scene.control.Skin"); // javafx.controls
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
showErrorAndExit(i18n("fatal.javafx.incomplete"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addEnableNativeAccess() {
|
||||
if (JavaRuntime.CURRENT_VERSION > 21) {
|
||||
try {
|
||||
ModuleHelper.addEnableNativeAccess(Class.forName("javafx.stage.Stage")); // javafx.graphics
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace(System.err);
|
||||
showErrorAndExit(i18n("fatal.javafx.incomplete"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void enableUnsafeMemoryAccess() {
|
||||
// https://openjdk.org/jeps/498
|
||||
if (JavaRuntime.CURRENT_VERSION == 24 || JavaRuntime.CURRENT_VERSION == 25) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("sun.misc.Unsafe");
|
||||
Method trySetMemoryAccessWarned = clazz.getDeclaredMethod("trySetMemoryAccessWarned");
|
||||
trySetMemoryAccessWarned.setAccessible(true);
|
||||
trySetMemoryAccessWarned.invoke(null);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
|
||||
@@ -43,8 +43,8 @@ public class CrashReport {
|
||||
stackTrace + "\n\n" +
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName() + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName() + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
|
||||
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
|
||||
|
||||
@@ -38,7 +38,6 @@ 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.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
@@ -359,12 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs = createLocalVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
VersionIconType versionIcon = vs.getVersionIcon();
|
||||
if (vs.isUsesGlobal()) {
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), vs);
|
||||
vs.setUsesGlobal(false);
|
||||
}
|
||||
vs.setVersionIcon(versionIcon); // versionIcon is preserved
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -374,7 +370,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -417,7 +413,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
|
||||
.setJavaAgents(javaAgents);
|
||||
.setJavaAgents(javaAgents)
|
||||
.setJavaArguments(javaArguments);
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -131,13 +131,14 @@ public final class LauncherHelper {
|
||||
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
|
||||
CountDownLatch launchingLatch = new CountDownLatch(1);
|
||||
List<String> javaAgents = new ArrayList<>(0);
|
||||
List<String> javaArguments = new ArrayList<>(0);
|
||||
|
||||
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version.get())
|
||||
.thenComposeAsync(java -> {
|
||||
javaVersionRef.set(Objects.requireNonNull(java));
|
||||
version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), java, setting));
|
||||
version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
return Task.allOf(
|
||||
@@ -155,7 +156,7 @@ public final class LauncherHelper {
|
||||
Task.composeAsync(() -> {
|
||||
Renderer renderer = setting.getRenderer();
|
||||
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
Library lib = NativePatcher.getMesaLoader(java, renderer);
|
||||
Library lib = NativePatcher.getMesaLoader(java);
|
||||
if (lib == null)
|
||||
return null;
|
||||
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
@@ -182,7 +183,8 @@ public final class LauncherHelper {
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(
|
||||
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
|
||||
|
||||
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));
|
||||
|
||||
|
||||
@@ -376,8 +376,8 @@ public final class JavaManager {
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
|
||||
break;
|
||||
case OSX:
|
||||
tryAddJavaHome(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines/Contents/Home"));
|
||||
tryAddJavaHome(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines/Contents/Home"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
|
||||
// Homebrew
|
||||
@@ -631,6 +631,20 @@ public final class JavaManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(javaRuntimes, subDir.resolve("Contents/Home"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Windows Registry Support ====
|
||||
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, String location) {
|
||||
for (String java : querySubFolders(location)) {
|
||||
|
||||
@@ -24,7 +24,10 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -56,29 +59,41 @@ public class Theme {
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static Optional<Font> font;
|
||||
private static final Lazy<Font> FONT = new Lazy<>(() -> {
|
||||
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
|
||||
|
||||
private static Optional<Font> tryLoadFont() {
|
||||
//noinspection OptionalAssignedToNull
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Paths.get(fileName).toAbsolutePath();
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
Path path = Paths.get("font.ttf");
|
||||
if (!Files.isRegularFile(path)) {
|
||||
path = Paths.get("font.otf");
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
return font = Optional.ofNullable(Font.loadFont(path.toAbsolutePath().toUri().toURL().toExternalForm(), 0));
|
||||
} catch (MalformedURLException ignored) {
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return font = Optional.empty();
|
||||
}
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
public static Theme getTheme() {
|
||||
Theme theme = config().getTheme();
|
||||
@@ -123,6 +138,45 @@ public class Theme {
|
||||
opacity);
|
||||
}
|
||||
|
||||
private static Pair<String, String> parseFontStyle(String style) {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
style = style.toLowerCase(Locale.ROOT);
|
||||
|
||||
String weight;
|
||||
String posture;
|
||||
|
||||
if (style.contains("thin")) {
|
||||
weight = "100";
|
||||
} else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) {
|
||||
weight = "200";
|
||||
} else if (style.contains("medium")) {
|
||||
weight = "500";
|
||||
} else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) {
|
||||
weight = "600";
|
||||
} else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) {
|
||||
weight = "800";
|
||||
} else if (style.contains("black") || style.contains("heavy")) {
|
||||
weight = "900";
|
||||
} else if (style.contains("light")) {
|
||||
weight = "lighter";
|
||||
} else if (style.contains("bold")) {
|
||||
weight = "bold";
|
||||
} else {
|
||||
weight = null;
|
||||
}
|
||||
|
||||
if (style.contains("italic") || style.contains("oblique")) {
|
||||
posture = "italic";
|
||||
} else {
|
||||
posture = null;
|
||||
}
|
||||
|
||||
return Pair.pair(weight, posture);
|
||||
}
|
||||
|
||||
public String[] getStylesheets(String overrideFontFamily) {
|
||||
String css = "/assets/css/blue.css";
|
||||
|
||||
@@ -130,12 +184,12 @@ public class Theme {
|
||||
? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT"))
|
||||
: overrideFontFamily;
|
||||
|
||||
String fontStyle = null;
|
||||
Pair<String, String> fontStyle = null;
|
||||
if (fontFamily == null) {
|
||||
Optional<Font> font = tryLoadFont();
|
||||
if (font.isPresent()) {
|
||||
fontFamily = font.get().getFamily();
|
||||
fontStyle = font.get().getStyle();
|
||||
Font font = FONT.get();
|
||||
if (font != null) {
|
||||
fontFamily = font.getFamily();
|
||||
fontStyle = parseFontStyle(font.getStyle());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +213,13 @@ public class Theme {
|
||||
else
|
||||
themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
|
||||
if (fontStyle != null && !fontStyle.isEmpty())
|
||||
themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";");
|
||||
if (fontStyle != null) {
|
||||
if (fontStyle.getKey() != null)
|
||||
themeBuilder.append("-fx-font-weight:").append(fontStyle.getKey()).append(";");
|
||||
|
||||
if (fontStyle.getValue() != null)
|
||||
themeBuilder.append("-fx-font-style:").append(fontStyle.getValue()).append(";");
|
||||
}
|
||||
|
||||
themeBuilder.append('}');
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
@@ -34,6 +37,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
@@ -42,6 +46,7 @@ import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
@@ -267,6 +272,24 @@ public final class Controllers {
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.setScene(scene);
|
||||
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(600),
|
||||
new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {
|
||||
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
|
||||
|
||||
@@ -368,10 +391,7 @@ public final class Controllers {
|
||||
|
||||
public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, TaskCancellationAction onCancel) {
|
||||
TaskExecutor executor = task.executor();
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setExecutor(executor);
|
||||
dialog(pane);
|
||||
TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel);
|
||||
executor.start();
|
||||
return pane;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@ import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.*;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
@@ -79,6 +79,7 @@ import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -253,20 +254,6 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
|
||||
column.setCellValueFactory(param -> {
|
||||
if (column.validateValue(param))
|
||||
return mapper.apply(param.getValue().getValue());
|
||||
else
|
||||
return column.getComputedValue(param);
|
||||
});
|
||||
}
|
||||
|
||||
public static Node wrapMargin(Node node, Insets insets) {
|
||||
StackPane.setMargin(node, insets);
|
||||
return new StackPane(node);
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(Node field, boolean validate) {
|
||||
if (field instanceof JFXTextField) {
|
||||
if (validate) {
|
||||
@@ -582,7 +569,7 @@ public final class FXUtils {
|
||||
focusedListener = (observable, oldFocused, newFocused) -> {
|
||||
if (oldFocused && !newFocused) {
|
||||
if (textField.validate()) {
|
||||
uppdateProperty();
|
||||
updateProperty();
|
||||
} else {
|
||||
// Rollback to old value
|
||||
updateTextField();
|
||||
@@ -594,7 +581,7 @@ public final class FXUtils {
|
||||
if (oldScene != null && newScene == null) {
|
||||
// Component is being removed from scene
|
||||
if (textField.validate()) {
|
||||
uppdateProperty();
|
||||
updateProperty();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -604,7 +591,7 @@ public final class FXUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public void uppdateProperty() {
|
||||
public void updateProperty() {
|
||||
String newText = textField.getText();
|
||||
@SuppressWarnings("unchecked")
|
||||
T newValue = converter == null ? (T) newText : converter.fromString(newText);
|
||||
@@ -620,20 +607,80 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {
|
||||
private final WeakReference<JFXComboBox<E>> comboBoxRef;
|
||||
private final WeakReference<Property<E>> propertyRef;
|
||||
private final int hashCode;
|
||||
|
||||
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
private boolean updating = false;
|
||||
|
||||
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {
|
||||
this.comboBoxRef = new WeakReference<>(comboBox);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
this.hashCode = System.identityHashCode(comboBox) ^ System.identityHashCode(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().unbindBidirectional(property);
|
||||
@Override
|
||||
public void invalidated(Observable sourceProperty) {
|
||||
if (!updating) {
|
||||
final JFXComboBox<E> comboBox = comboBoxRef.get();
|
||||
final Property<E> property = propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null) {
|
||||
if (comboBox != null) {
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(this);
|
||||
}
|
||||
|
||||
if (property != null) {
|
||||
property.removeListener(this);
|
||||
}
|
||||
} else {
|
||||
updating = true;
|
||||
try {
|
||||
if (property == sourceProperty) {
|
||||
E newValue = property.getValue();
|
||||
comboBox.getSelectionModel().select(newValue);
|
||||
} else {
|
||||
E newValue = comboBox.getSelectionModel().getSelectedItem();
|
||||
property.setValue(newValue);
|
||||
}
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasGarbageCollected() {
|
||||
return comboBoxRef.get() == null || propertyRef.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof EnumBidirectionalBinding))
|
||||
return false;
|
||||
|
||||
EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;
|
||||
|
||||
final JFXComboBox<E> comboBox = this.comboBoxRef.get();
|
||||
final Property<E> property = this.propertyRef.get();
|
||||
|
||||
final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();
|
||||
final Property<?> thatProperty = that.propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)
|
||||
return false;
|
||||
|
||||
return comboBox == thatComboBox && property == thatProperty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -642,20 +689,18 @@ public final class FXUtils {
|
||||
*
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @see #unbindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
unbindEnum(comboBox);
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
|
||||
T currentValue = property.getValue();
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
|
||||
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
|
||||
comboBox.getSelectionModel().select(currentValue.ordinal());
|
||||
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
||||
comboBox.getSelectionModel().select(property.getValue());
|
||||
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
|
||||
property.addListener(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -666,11 +711,10 @@ public final class FXUtils {
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
|
||||
if (listener != null)
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
}
|
||||
|
||||
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
|
||||
@@ -732,6 +776,8 @@ public final class FXUtils {
|
||||
String icon;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
icon = "/assets/img/icon.png";
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
icon = "/assets/img/icon-mac.png";
|
||||
} else {
|
||||
icon = "/assets/img/icon@4x.png";
|
||||
}
|
||||
@@ -1010,6 +1056,8 @@ public final class FXUtils {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
|
||||
|
||||
public static void onEscPressed(Node node, Runnable action) {
|
||||
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
@@ -1028,6 +1076,18 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyOnDoubleClick(Labeled label) {
|
||||
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
String text = label.getText();
|
||||
if (text != null && !text.isEmpty()) {
|
||||
copyText(label.getText());
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyText(String text) {
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(text);
|
||||
|
||||
@@ -400,7 +400,7 @@ public class GameCrashWindow extends Stage {
|
||||
ScrollPane reasonPane = new ScrollPane(reasonTextFlow);
|
||||
reasonPane.setFitToWidth(true);
|
||||
reasonPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||
|
||||
gameDirPane.setPadding(new Insets(8));
|
||||
VBox.setVgrow(gameDirPane, Priority.ALWAYS);
|
||||
|
||||
@@ -151,6 +151,12 @@ public final class HTMLRenderer {
|
||||
children.add(textNode);
|
||||
}
|
||||
|
||||
private void appendAutoLineBreak(String text) {
|
||||
AutoLineBreak textNode = new AutoLineBreak(text);
|
||||
applyStyle(textNode);
|
||||
children.add(textNode);
|
||||
}
|
||||
|
||||
private void appendImage(Node node) {
|
||||
String src = node.absUrl("src");
|
||||
URI imageUri = null;
|
||||
@@ -230,7 +236,7 @@ public final class HTMLRenderer {
|
||||
case "h6":
|
||||
case "tr":
|
||||
if (!children.isEmpty())
|
||||
appendText("\n\n");
|
||||
appendAutoLineBreak("\n\n");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -252,15 +258,62 @@ public final class HTMLRenderer {
|
||||
case "h4":
|
||||
case "h5":
|
||||
case "h6":
|
||||
appendText("\n");
|
||||
appendAutoLineBreak("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSpacing(String text) {
|
||||
if (text == null)
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch != ' ' && ch != '\t')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void mergeLineBreaks() {
|
||||
for (int i = 0; i < this.children.size(); i++) {
|
||||
javafx.scene.Node child = this.children.get(i);
|
||||
if (child instanceof AutoLineBreak) {
|
||||
int lastAutoLineBreak = -1;
|
||||
|
||||
for (int j = i + 1; j < this.children.size(); j++) {
|
||||
javafx.scene.Node otherChild = this.children.get(j);
|
||||
|
||||
if (otherChild instanceof AutoLineBreak) {
|
||||
lastAutoLineBreak = j;
|
||||
} else if (otherChild instanceof Text && isSpacing(((Text) otherChild).getText())) {
|
||||
// do nothing
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastAutoLineBreak > 0) {
|
||||
this.children.subList(i + 1, lastAutoLineBreak + 1).clear();
|
||||
|
||||
if (((Text) child).getText().length() == 1) {
|
||||
((Text) child).setText("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TextFlow render() {
|
||||
TextFlow textFlow = new TextFlow();
|
||||
textFlow.getStyleClass().add("html");
|
||||
textFlow.getChildren().setAll(children);
|
||||
return textFlow;
|
||||
}
|
||||
|
||||
private static final class AutoLineBreak extends Text {
|
||||
public AutoLineBreak(String text) {
|
||||
super(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,7 +378,7 @@ public class InstallerItem extends Control {
|
||||
pane.getChildren().add(buttonsContainer);
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1));
|
||||
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1));
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
if (control.id.equals(MINECRAFT.getPatchId())) {
|
||||
removeButton.setVisible(false);
|
||||
@@ -396,8 +396,8 @@ public class InstallerItem extends Control {
|
||||
JFXButton installButton = new JFXButton();
|
||||
installButton.graphicProperty().bind(Bindings.createObjectBinding(() ->
|
||||
control.resolvedStateProperty.get() instanceof InstallableState ?
|
||||
SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), -1, -1) :
|
||||
SVG.UPDATE.createIcon(Theme.blackFill(), -1, -1),
|
||||
SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), -1) :
|
||||
SVG.UPDATE.createIcon(Theme.blackFill(), -1),
|
||||
control.resolvedStateProperty
|
||||
));
|
||||
installButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.getStyleClass().add("jfx-button-raised-round");
|
||||
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnAdd.setGraphic(SVG.PLUS.createIcon(Theme.whiteFill(), -1, -1));
|
||||
btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1));
|
||||
btnAdd.setOnAction(e -> skinnable.add());
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
@@ -77,7 +77,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
FXUtils.setLimitHeight(btnRefresh, 40);
|
||||
btnRefresh.getStyleClass().add("jfx-button-raised-round");
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1, -1));
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1));
|
||||
btnRefresh.setOnAction(e -> skinnable.refresh());
|
||||
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
|
||||
@@ -25,94 +25,88 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
|
||||
/**
|
||||
* All vector icons used in the launcher.
|
||||
* <p>
|
||||
* Unless otherwise stated,
|
||||
* these icons are from <a href="https://fonts.google.com/icons?icon.size=24&icon.color=%235f6368&icon.query=list&icon.set=Material+Symbols">Material Symbols</a>,
|
||||
* with a style of outlined, a weight of 400, a grade of 0, and an optical size of 24 px.
|
||||
* The view boxes of all icons are normalized to {@code 0 0 24 24}.
|
||||
*/
|
||||
public enum SVG {
|
||||
GEAR("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"),
|
||||
GEAR_OUTLINE("M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"),
|
||||
BACK("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"),
|
||||
CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"),
|
||||
CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"),
|
||||
CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"),
|
||||
COPY("M15 16H7V6h8m.4-2H6.6A1.6 1.6 90 005 5.6V16.4a1.6 1.6 90 001.6 1.6h8.8A1.6 1.6 90 0017 16.4V5.6A1.6 1.6 90 0015.4 4M13 1H3.6A1.6 1.6 90 002 2.6V13h2V3H13V1Z"),
|
||||
DOTS_VERTICAL("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"),
|
||||
DOTS_HORIZONTAL("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"),
|
||||
DELETE("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"),
|
||||
DELETE_OUTLINE("M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8,9H16V19H8V9M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z"),
|
||||
ACCOUNT_EDIT("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z"),
|
||||
EXPAND("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"),
|
||||
COLLAPSE("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"),
|
||||
NAVIGATE("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
|
||||
ROCKET_LAUNCH_OUTLINE("M13.13 22.19L11.5 18.36C13.07 17.78 14.54 17 15.9 16.09L13.13 22.19M5.64 12.5L1.81 10.87L7.91 8.1C7 9.46 6.22 10.93 5.64 12.5M19.22 4C19.5 4 19.75 4 19.96 4.05C20.13 5.44 19.94 8.3 16.66 11.58C14.96 13.29 12.93 14.6 10.65 15.47L8.5 13.37C9.42 11.06 10.73 9.03 12.42 7.34C15.18 4.58 17.64 4 19.22 4M19.22 2C17.24 2 14.24 2.69 11 5.93C8.81 8.12 7.5 10.53 6.65 12.64C6.37 13.39 6.56 14.21 7.11 14.77L9.24 16.89C9.62 17.27 10.13 17.5 10.66 17.5C10.89 17.5 11.13 17.44 11.36 17.35C13.5 16.53 15.88 15.19 18.07 13C23.73 7.34 21.61 2.39 21.61 2.39S20.7 2 19.22 2M14.54 9.46C13.76 8.68 13.76 7.41 14.54 6.63S16.59 5.85 17.37 6.63C18.14 7.41 18.15 8.68 17.37 9.46C16.59 10.24 15.32 10.24 14.54 9.46M8.88 16.53L7.47 15.12L8.88 16.53M6.24 22L9.88 18.36C9.54 18.27 9.21 18.12 8.91 17.91L4.83 22H6.24M2 22H3.41L8.18 17.24L6.76 15.83L2 20.59V22M2 19.17L6.09 15.09C5.88 14.79 5.73 14.47 5.64 14.12L2 17.76V19.17Z"),
|
||||
LAUNCH_OUTLINE("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
|
||||
SCRIPT("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"),
|
||||
PENCIL("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"),
|
||||
PENCIL_OUTLINE("M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z"),
|
||||
REFRESH("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"),
|
||||
FOLDER_OPEN("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z"),
|
||||
FOLDER_OUTLINE("M20,18H4V8H20M20,6H12L10,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,6Z"),
|
||||
UPDATE("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"),
|
||||
CLOSE_CIRCLE("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z"),
|
||||
CHECK_CIRCLE("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"),
|
||||
INFO_CIRCLE("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
|
||||
HELP_CIRCLE("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"),
|
||||
HELP_CIRCLE_OUTLINE("M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z"),
|
||||
ALERT("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z"),
|
||||
ALERT_OUTLINE("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"),
|
||||
PLUS("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"),
|
||||
PLUS_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"),
|
||||
MINUS("M19 13H5V11H19V13Z"),
|
||||
IMPORT_ICON("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z"),
|
||||
EXPORT("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"),
|
||||
OPEN_IN_NEW("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
|
||||
TRIANGLE("M1,21H23L12,2"),
|
||||
HOME("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"),
|
||||
VIEW_LIST("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z"),
|
||||
CHECK("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"),
|
||||
ARROW_RIGHT("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"),
|
||||
WRENCH("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"),
|
||||
WRENCH_OUTLINE("M22.61,19L13.53,9.91C14.46,7.57 14,4.81 12.09,2.91C9.79,0.61 6.21,0.4 3.66,2.26L7.5,6.11L6.08,7.5L2.25,3.69C0.39,6.23 0.6,9.82 2.9,12.11C4.76,13.97 7.47,14.46 9.79,13.59L18.9,22.7C19.29,23.09 19.92,23.09 20.31,22.7L22.61,20.4C23,20 23,19.39 22.61,19M19.61,20.59L10.15,11.13C9.54,11.58 8.86,11.85 8.15,11.95C6.79,12.15 5.36,11.74 4.32,10.7C3.37,9.76 2.93,8.5 3,7.26L6.09,10.35L10.33,6.11L7.24,3C8.5,2.95 9.73,3.39 10.68,4.33C11.76,5.41 12.17,6.9 11.92,8.29C11.8,9 11.5,9.66 11.04,10.25L20.5,19.7L19.61,20.59Z"),
|
||||
UPLOAD("M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z"),
|
||||
HANGER("M12 4A3.5 3.5 0 0 0 8.5 7.5H10.5A1.5 1.5 0 0 1 12 6A1.5 1.5 0 0 1 13.5 7.5A1.5 1.5 0 0 1 12 9C11.45 9 11 9.45 11 10V11.75L2.4 18.2A1 1 0 0 0 3 20H21A1 1 0 0 0 21.6 18.2L13 11.75V10.85A3.5 3.5 0 0 0 15.5 7.5A3.5 3.5 0 0 0 12 4M12 13.5L18 18H6Z"),
|
||||
PUZZLE("M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z"),
|
||||
CUBE("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"),
|
||||
PACK("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"),
|
||||
TEXTURE_BOX("M20 2H4C2.9 2 2 2.9 2 4V20C2 21.11 2.9 22 4 22H20C21.11 22 22 21.11 22 20V4C22 2.9 21.11 2 20 2M4 6L6 4H10.9L4 10.9V6M4 13.7L13.7 4H18.6L4 18.6V13.7M20 18L18 20H13.1L20 13.1V18M20 10.3L10.3 20H5.4L20 5.4V10.3Z"),
|
||||
GAMEPAD("M6,9H8V11H10V13H8V15H6V13H4V11H6V9M18.5,9A1.5,1.5 0 0,1 20,10.5A1.5,1.5 0 0,1 18.5,12A1.5,1.5 0 0,1 17,10.5A1.5,1.5 0 0,1 18.5,9M15.5,12A1.5,1.5 0 0,1 17,13.5A1.5,1.5 0 0,1 15.5,15A1.5,1.5 0 0,1 14,13.5A1.5,1.5 0 0,1 15.5,12M17,5A7,7 0 0,1 24,12A7,7 0 0,1 17,19C15.04,19 13.27,18.2 12,16.9C10.73,18.2 8.96,19 7,19A7,7 0 0,1 0,12A7,7 0 0,1 7,5H17M7,7A5,5 0 0,0 2,12A5,5 0 0,0 7,17C8.64,17 10.09,16.21 11,15H13C13.91,16.21 15.36,17 17,17A5,5 0 0,0 22,12A5,5 0 0,0 17,7H7Z"),
|
||||
FIRE("M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08 13.88 8.04 13.83 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2M14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5H14.5Z"),
|
||||
MONITOR_SCREENSHOT("M9,6H5V10H7V8H9M19,10H17V12H15V14H19M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2"),
|
||||
TEXTURE("M9.29,21H12.12L21,12.12V9.29M19,21C19.55,21 20.05,20.78 20.41,20.41C20.78,20.05 21,19.55 21,19V17L17,21M5,3A2,2 0 0,0 3,5V7L7,3M11.88,3L3,11.88V14.71L14.71,3M19.5,3.08L3.08,19.5C3.17,19.85 3.35,20.16 3.59,20.41C3.84,20.65 4.15,20.83 4.5,20.92L20.93,4.5C20.74,3.8 20.2,3.26 19.5,3.08Z"),
|
||||
ALPHA_CIRCLE_OUTLINE("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z"),
|
||||
BETA_CIRCLE_OUTLINE("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"),
|
||||
RELEASE_CIRCLE_OUTLINE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"),
|
||||
INFORMATION_OUTLINE("M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"),
|
||||
HAND_HEAR_OUTLINE("M16 3.23C16.71 2.41 17.61 2 18.7 2C19.61 2 20.37 2.33 21 3C21.63 3.67 21.96 4.43 22 5.3C22 6 21.67 6.81 21 7.76S19.68 9.5 19.03 10.15C18.38 10.79 17.37 11.74 16 13C14.61 11.74 13.59 10.79 12.94 10.15S11.63 8.71 10.97 7.76C10.31 6.81 10 6 10 5.3C10 4.39 10.32 3.63 10.97 3C11.62 2.37 12.4 2.04 13.31 2C14.38 2 15.27 2.41 16 3.23M22 19V20L14 22.5L7 20.56V22H1V11H8.97L15.13 13.3C16.25 13.72 17 14.8 17 16H19C20.66 16 22 17.34 22 19M5 20V13H3V20H5M19.9 18.57C19.74 18.24 19.39 18 19 18H13.65C13.11 18 12.58 17.92 12.07 17.75L9.69 16.96L10.32 15.06L12.7 15.85C13 15.95 15 16 15 16C15 15.63 14.77 15.3 14.43 15.17L8.61 13H7V18.5L13.97 20.41L19.9 18.57Z"),
|
||||
MOJANG("m 13.965847,0 c -1.010612,0.82802228 -1.197232,2.1950303 -1.265172,3.4179557 0.02123,1.0021189 1.341654,1.2994159 1.953117,0.590289 C 15.091158,2.6579315 14.369242,1.2738804 13.965847,0 Z M 10.913012,2.9296764 C 10.755901,3.6982508 10.628413,4.4668999 10.492533,5.2354744 8.9893533,3.9913178 7.1504705,3.0824837 5.1419856,3.2947971 3.4944341,3.4646478 0.94293227,2.6961479 0.14038761,4.645185 -0.12288102,8.3139606 0.07223999,12.01236 0.03402357,15.689629 c -0.12314178,1.222925 0.86170213,2.420422 2.14407513,2.280295 4.2207899,0.03397 8.4502143,0.04723 12.6710043,-0.0038 1.265389,0.135918 1.957646,-1.010748 2.13599,-2.0893 C 13.269608,16.437357 9.1760813,16.929609 5.7111265,15.129192 2.5986124,13.58355 2.246023,8.3138817 5.5581114,6.7979639 9.3203049,5.1758896 13.859607,8.0382886 14.942405,11.787743 15.613316,12.11046 16.284433,12.424684 16.95959,12.743154 16.624135,10.348258 16.653651,7.800456 15.579346,5.5881508 15.057054,4.7473897 14.097531,6.2714182 13.379911,5.6217388 12.416008,4.865903 11.749527,3.8128948 10.913012,2.9296713 Z"),
|
||||
MICROSOFT("M2,3H11V12H2V3M11,22H2V13H11V22M21,3V12H12V3H21M21,22H12V13H21V22Z"),
|
||||
ACCOUNT_OUTLINE("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z"),
|
||||
ACCOUNT_GROUP_OUTLINE("M12,5A3.5,3.5 0 0,0 8.5,8.5A3.5,3.5 0 0,0 12,12A3.5,3.5 0 0,0 15.5,8.5A3.5,3.5 0 0,0 12,5M12,7A1.5,1.5 0 0,1 13.5,8.5A1.5,1.5 0 0,1 12,10A1.5,1.5 0 0,1 10.5,8.5A1.5,1.5 0 0,1 12,7M5.5,8A2.5,2.5 0 0,0 3,10.5C3,11.44 3.53,12.25 4.29,12.68C4.65,12.88 5.06,13 5.5,13C5.94,13 6.35,12.88 6.71,12.68C7.08,12.47 7.39,12.17 7.62,11.81C6.89,10.86 6.5,9.7 6.5,8.5C6.5,8.41 6.5,8.31 6.5,8.22C6.2,8.08 5.86,8 5.5,8M18.5,8C18.14,8 17.8,8.08 17.5,8.22C17.5,8.31 17.5,8.41 17.5,8.5C17.5,9.7 17.11,10.86 16.38,11.81C16.5,12 16.63,12.15 16.78,12.3C16.94,12.45 17.1,12.58 17.29,12.68C17.65,12.88 18.06,13 18.5,13C18.94,13 19.35,12.88 19.71,12.68C20.47,12.25 21,11.44 21,10.5A2.5,2.5 0 0,0 18.5,8M12,14C9.66,14 5,15.17 5,17.5V19H19V17.5C19,15.17 14.34,14 12,14M4.71,14.55C2.78,14.78 0,15.76 0,17.5V19H3V17.07C3,16.06 3.69,15.22 4.71,14.55M19.29,14.55C20.31,15.22 21,16.06 21,17.07V19H24V17.5C24,15.76 21.22,14.78 19.29,14.55M12,16C13.53,16 15.24,16.5 16.23,17H7.77C8.76,16.5 10.47,16 12,16Z"),
|
||||
ACCOUNT_ARROW_RIGHT_OUTLINE("M19,21V19H15V17H19V15L22,18L19,21M13,18C13,18.71 13.15,19.39 13.42,20H2V17C2,14.79 5.58,13 10,13C11,13 11.96,13.09 12.85,13.26C13.68,13.42 14.44,13.64 15.11,13.92C13.83,14.83 13,16.32 13,18M4,17V18H11C11,16.96 11.23,15.97 11.64,15.08L10,15C6.69,15 4,15.9 4,17M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,6A2,2 0 0,0 8,8A2,2 0 0,0 10,10A2,2 0 0,0 12,8A2,2 0 0,0 10,6Z"),
|
||||
STYLE_OUTLINE("M2.5 19.6L3.8 20.2V11.2L1.4 17C1 18.1 1.5 19.2 2.5 19.6M15.2 4.8L20.2 16.8L12.9 19.8L7.9 7.9V7.8L15.2 4.8M15.3 2.8C15 2.8 14.8 2.8 14.5 2.9L7.1 6C6.4 6.3 5.9 7 5.9 7.8C5.9 8 5.9 8.3 6 8.6L11 20.5C11.3 21.3 12 21.7 12.8 21.7C13.1 21.7 13.3 21.7 13.6 21.6L21 18.5C22 18.1 22.5 16.9 22.1 15.9L17.1 4C16.8 3.2 16 2.8 15.3 2.8M10.5 9.9C9.9 9.9 9.5 9.5 9.5 8.9S9.9 7.9 10.5 7.9C11.1 7.9 11.5 8.4 11.5 8.9S11.1 9.9 10.5 9.9M5.9 19.8C5.9 20.9 6.8 21.8 7.9 21.8H9.3L5.9 13.5V19.8Z"),
|
||||
DOWNLOAD_OUTLINE("M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z"),
|
||||
APPLICATION_OUTLINE("M21 2H3C1.9 2 1 2.9 1 4V20C1 21.1 1.9 22 3 22H21C22.1 22 23 21.1 23 20V4C23 2.9 22.1 2 21 2M21 20H3V6H21V20Z"),
|
||||
EARTH("M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
|
||||
BELL("M21,19V20H3V19L5,17V11C5,7.9 7.03,5.17 10,4.29C10,4.19 10,4.1 10,4A2,2 0 0,1 12,2A2,2 0 0,1 14,4C14,4.1 14,4.19 14,4.29C16.97,5.17 19,7.9 19,11V17L21,19M14,21A2,2 0 0,1 12,23A2,2 0 0,1 10,21"),
|
||||
CONTENT_SAVE_MOVE_OUTLINE("M13 17H17V14L22 18.5L17 23V20H13V17M14 12.8C13.5 12.31 12.78 12 12 12C10.34 12 9 13.34 9 15C9 16.31 9.84 17.41 11 17.82C11.07 15.67 12.27 13.8 14 12.8M11.09 19H5V5H16.17L19 7.83V12.35C19.75 12.61 20.42 13 21 13.54V7L17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H11.81C11.46 20.39 11.21 19.72 11.09 19M6 10H15V6H6V10Z"),
|
||||
ACCOUNT("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"),
|
||||
SERVER("M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z"),
|
||||
MESSAGE_ALERT_OUTLINE("M13,10H11V6H13V10M13,12H11V14H13V12M22,4V16A2,2 0 0,1 20,18H6L2,22V4A2,2 0 0,1 4,2H20A2,2 0 0,1 22,4M20,4H4V17.2L5.2,16H20V4Z"),
|
||||
CHECK_CIRCLE_OUTLINE("M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z"),
|
||||
CLOSE_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z"),
|
||||
CLOCK_OUTLINE("M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z"),
|
||||
MAGNIFY("M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"),
|
||||
MENU_UP("M7,15L12,10L17,15H7Z"),
|
||||
MENU_DOWN("M7,10L12,15L17,10H7Z"),
|
||||
RESTORE("M13,3A9,9 0 0,0 4,12H1L4.89,15.89L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3Z"),
|
||||
BUG("M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z"),
|
||||
DISCORD("M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z"),
|
||||
LAN("M10,2C8.89,2 8,2.89 8,4V7C8,8.11 8.89,9 10,9H11V11H2V13H6V15H5C3.89,15 3,15.89 3,17V20C3,21.11 3.89,22 5,22H9C10.11,22 11,21.11 11,20V17C11,15.89 10.11,15 9,15H8V13H16V15H15C13.89,15 13,15.89 13,17V20C13,21.11 13.89,22 15,22H19C20.11,22 21,21.11 21,20V17C21,15.89 20.11,15 19,15H18V13H22V11H13V9H14C15.11,9 16,8.11 16,7V4C16,2.89 15.11,2 14,2H10M10,4H14V7H10V4M5,17H9V20H5V17M15,17H19V20H15V17Z"),
|
||||
THUMB_UP_OUTLINE("M5,9V21H1V9H5M9,21A2,2 0 0,1 7,19V9C7,8.45 7.22,7.95 7.59,7.59L14.17,1L15.23,2.06C15.5,2.33 15.67,2.7 15.67,3.11L15.64,3.43L14.69,8H21C22.11,8 23,8.9 23,10V12C23,12.26 22.95,12.5 22.86,12.73L19.84,19.78C19.54,20.5 18.83,21 18,21H9M9,19H18.03L21,12V10H12.21L13.34,4.68L9,9.03V19Z"),
|
||||
THUMB_DOWN_OUTLINE("M19,15V3H23V15H19M15,3A2,2 0 0,1 17,5V15C17,15.55 16.78,16.05 16.41,16.41L9.83,23L8.77,21.94C8.5,21.67 8.33,21.3 8.33,20.88L8.36,20.57L9.31,16H3C1.89,16 1,15.1 1,14V12C1,11.74 1.05,11.5 1.14,11.27L4.16,4.22C4.46,3.5 5.17,3 6,3H15M15,5H5.97L3,12V14H11.78L10.65,19.32L15,14.97V5Z"),
|
||||
SELECT_ALL("M9,9H15V15H9M7,17H17V7H7M15,5H17V3H15M15,21H17V19H15M19,17H21V15H19M19,9H21V7H19M19,21A2,2 0 0,0 21,19H19M19,13H21V11H19M11,21H13V19H11M9,3H7V5H9M3,17H5V15H3M5,21V19H3A2,2 0 0,0 5,21M19,3V5H21A2,2 0 0,0 19,3M13,3H11V5H13M3,9H5V7H3M7,21H9V19H7M3,13H5V11H3M3,5H5V3A2,2 0 0,0 3,5Z"),
|
||||
JAVA("m 12.400746,23.498132 c 0,0 -1.047682,0.609185 0.745753,0.816159 2.173248,0.247515 3.284943,0.212301 5.680103,-0.241112 0,0 0.629462,0.394742 1.509642,0.737207 -5.369636,2.301245 -12.1518319,-0.133351 -7.934431,-1.312254 z m -0.656135,-3.003246 c 0,0 -1.175708,0.870569 0.619861,1.056206 2.321545,0.238977 4.155521,0.25925 7.32844,-0.35207 0,0 0.43849,0.444887 1.128765,0.688135 -6.492002,1.897965 -13.7233551,0.149351 -9.077066,-1.392271 z m 5.531806,-5.094318 c 1.322937,1.523496 -0.347807,2.894427 -0.347807,2.894427 0,0 3.359626,-1.733668 1.816908,-3.905821 -1.441364,-2.024926 -2.545591,-3.030987 3.43644,-6.5004569 0,0 -9.389665,2.3449859 -4.905541,7.5129179 z m 7.101193,10.318794 c 0,0 0.775626,0.639057 -0.854578,1.133019 C 20.42373,27.79123 10.623314,28.075018 7.9006204,26.88973 6.9222859,26.464048 8.75733,25.873001 9.3345153,25.749244 9.9362392,25.619093 10.280844,25.642568 10.280844,25.642568 9.1926195,24.875486 3.2457961,27.147927 7.2604887,27.798718 18.208875,29.573994 27.21766,26.999632 24.37761,25.718316 Z M 12.904316,17.383886 c 0,0 -4.9855587,1.184229 -1.765696,1.614178 1.359213,0.182426 4.069103,0.140825 6.594422,-0.07042 2.063359,-0.173899 4.135252,-0.544104 4.135252,-0.544104 0,0 -0.727616,0.311527 -1.253592,0.671064 -5.062375,1.331456 -14.8414526,0.711604 -12.02594,-0.649727 2.38129,-1.151156 4.316621,-1.019929 4.316621,-1.019929 z m 8.943706,4.998298 c 5.146658,-2.673583 2.766435,-5.24368 1.106361,-4.898013 -0.407551,0.08428 -0.58892,0.1579 -0.58892,0.1579 0,0 0.151501,-0.236845 0.439557,-0.339265 3.284942,-1.155424 5.812393,3.406524 -1.060485,5.213807 0,0 0.08003,-0.07147 0.103478,-0.134425 z M 18.744451,2.2855 c 0,0 2.849653,2.8506848 -2.703489,7.2344656 -4.453183,3.5164124 -1.015676,5.5221324 -0.0021,7.8127104 C 13.439893,14.987688 11.532301,12.92329 12.811497,11.001852 14.690284,8.1810394 19.893487,6.8143757 18.743384,2.2855 Z M 13.41002,29.628384 c 4.939684,0.315794 12.525242,-0.174974 12.70448,-2.512485 0,0 -0.345672,0.886571 -4.081906,1.589641 -4.216334,0.793754 -9.416337,0.700936 -12.4996379,0.192025 0,0 0.6315969,0.522768 3.8781309,0.730807 z");
|
||||
ADD("M11 13H5V11H11V5H13V11H19V13H13V19H11V13Z"),
|
||||
ADD_CIRCLE("M11 17H13V13H17V11H13V7H11V11H7V13H11V17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
ALPHA_CIRCLE("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z"), // Not Material
|
||||
ARCHIVE("M12 18 16 14 14.6 12.6 13 14.2V10H11V14.2L9.4 12.6 8 14 12 18ZM5 8V19H19V8H5ZM5 21Q4.175 21 3.5875 20.4125T3 19V6.525Q3 6.175 3.1125 5.85T3.45 5.25L4.7 3.725Q4.975 3.375 5.3875 3.1875T6.25 3H17.75Q18.2 3 18.6125 3.1875T19.3 3.725L20.55 5.25Q20.775 5.525 20.8875 5.85T21 6.525V19Q21 19.825 20.4125 20.4125T19 21H5ZM5.4 6H18.6L17.75 5H6.25L5.4 6ZM12 13.5Z"),
|
||||
ARROW_BACK("M7.825 13 13.425 18.6 12 20 4 12 12 4 13.425 5.4 7.825 11H20V13H7.825Z"),
|
||||
ARROW_DROP_DOWN("M12 15 7 10H17L12 15Z"),
|
||||
ARROW_DROP_UP("M7 14 12 9 17 14H7Z"),
|
||||
ARROW_FORWARD("M16.175 13H4V11H16.175L10.575 5.4 12 4 20 12 12 20 10.575 18.6 16.175 13Z"),
|
||||
BETA_CIRCLE("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
|
||||
CANCEL("M8.4 17 12 13.4 15.6 17 17 15.6 13.4 12 17 8.4 15.6 7 12 10.6 8.4 7 7 8.4 10.6 12 7 15.6 8.4 17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
CHAT("M6 14H14V12H6V14ZM6 11H18V9H6V11ZM6 8H18V6H6V8ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
|
||||
CHECK("M9.55 18 3.85 12.3 5.275 10.875 9.55 15.15 18.725 5.975 20.15 7.4 9.55 18Z"),
|
||||
CHECKROOM("M3 20Q2.575 20 2.2875 19.7125T2 19Q2 18.75 2.1 18.5375T2.4 18.2L11 11.75V10Q11 9.575 11.3 9.2875T12.025 9Q12.65 9 13.075 8.55T13.5 7.475Q13.5 6.85 13.0625 6.425T12 6Q11.375 6 10.9375 6.4375T10.5 7.5H8.5Q8.5 6.05 9.525 5.025T12 4Q13.45 4 14.475 5.0125T15.5 7.475Q15.5 8.65 14.8125 9.575T13 10.85V11.75L21.6 18.2Q21.8 18.325 21.9 18.5375T22 19Q22 19.425 21.7125 19.7125T21 20H3ZM6 18H18L12 13.5 6 18Z"),
|
||||
CHECK_CIRCLE("M10.6 16.6 17.65 9.55 16.25 8.15 10.6 13.8 7.75 10.95 6.35 12.35 10.6 16.6ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
CLOSE("M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z"),
|
||||
CONTENT_COPY("M9 18Q8.175 18 7.5875 17.4125T7 16V4Q7 3.175 7.5875 2.5875T9 2H18Q18.825 2 19.4125 2.5875T20 4V16Q20 16.825 19.4125 17.4125T18 18H9ZM9 16H18V4H9V16ZM5 22Q4.175 22 3.5875 21.4125T3 20V6H5V20H16V22H5ZM9 16V4 16Z"),
|
||||
DELETE("M7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17ZM7 6V19 6Z"),
|
||||
DELETE_FOREVER("M9.4 16.5 12 13.9 14.6 16.5 16 15.1 13.4 12.5 16 9.9 14.6 8.5 12 11.1 9.4 8.5 8 9.9 10.6 12.5 8 15.1 9.4 16.5ZM7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM7 6V19 6Z"),
|
||||
DEPLOYED_CODE("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM12 10.85 17.925 7.425 12 4 6.075 7.425 12 10.85ZM4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725L4 17.7ZM12 12Z"),
|
||||
DOWNLOAD("M12 16 7 11 8.4 9.55 11 12.15V4H13V12.15L15.6 9.55 17 11 12 16ZM6 20Q5.175 20 4.5875 19.4125T4 18V15H6V18H18V15H20V18Q20 18.825 19.4125 19.4125T18 20H6Z"),
|
||||
DRESSER("M4 21V5Q4 4.175 4.5875 3.5875T6 3H18Q18.825 3 19.4125 3.5875T20 5V21H18V19H6V21H4ZM6 11H11V5H6V11ZM13 7H18V5H13V7ZM13 11H18V9H13V11ZM10 16H14V14H10V16ZM6 13V17H18V13H6ZM6 13V17 13Z"),
|
||||
EDIT("M5 19H6.425L16.2 9.225 14.775 7.8 5 17.575V19ZM3 21V16.75L16.2 3.575Q16.5 3.3 16.8625 3.15T17.625 3Q18.025 3 18.4 3.15T19.05 3.6L20.425 5Q20.725 5.275 20.8625 5.65T21 6.4Q21 6.8 20.8625 7.1625T20.425 7.825L7.25 21H3ZM19 6.4 17.6 5 19 6.4ZM15.475 8.525 14.775 7.8 16.2 9.225 15.475 8.525Z"),
|
||||
ERROR("M12 17Q12.425 17 12.7125 16.7125T13 16Q13 15.575 12.7125 15.2875T12 15Q11.575 15 11.2875 15.2875T11 16Q11 16.425 11.2875 16.7125T12 17ZM11 13H13V7H11V13ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
EXTENSION("M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z"),
|
||||
FEEDBACK("M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
|
||||
FOLDER("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8V18Q22 18.825 21.4125 19.4125T20 20H4ZM4 18H20V8H11.175L9.175 6H4V18ZM4 18V6 18Z"),
|
||||
FOLDER_COPY("M3 21Q2.175 21 1.5875 20.4125T1 19V6H3V19H20V21H3ZM7 17Q6.175 17 5.5875 16.4125T5 15V4Q5 3.175 5.5875 2.5875T7 2H12L14 4H21Q21.825 4 22.4125 4.5875T23 6V15Q23 15.825 22.4125 16.4125T21 17H7ZM7 15H21V6H13.175L11.175 4H7V15ZM7 15V4 15Z"),
|
||||
FOLDER_OPEN("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8H11.175L9.175 6H4V18L6.4 10H23.5L20.925 18.575Q20.725 19.225 20.1875 19.6125T19 20H4ZM6.1 18H19L20.8 12H7.9L6.1 18ZM6.1 18 7.9 12 6.1 18ZM4 8V6 8Z"),
|
||||
FORMAT_LIST_BULLETED("M9 19V17H21V19H9ZM9 13V11H21V13H9ZM9 7V5H21V7H9ZM5 20Q4.175 20 3.5875 19.4125T3 18Q3 17.175 3.5875 16.5875T5 16Q5.825 16 6.4125 16.5875T7 18Q7 18.825 6.4125 19.4125T5 20ZM5 14Q4.175 14 3.5875 13.4125T3 12Q3 11.175 3.5875 10.5875T5 10Q5.825 10 6.4125 10.5875T7 12Q7 12.825 6.4125 13.4125T5 14ZM5 8Q4.175 8 3.5875 7.4125T3 6Q3 5.175 3.5875 4.5875T5 4Q5.825 4 6.4125 4.5875T7 6Q7 6.825 6.4125 7.4125T5 8Z"),
|
||||
FOR_YOU("M12 12Q14.025 12 16.225 11.5875T20 10.5V20.5Q18.5 21.175 16.35 21.5875T12 22Q9.8 22 7.65 21.5875T4 20.5V10.5Q5.575 11.175 7.775 11.5875T12 12ZM18 19V13.25Q16.75 13.6 15.1125 13.8T12 14Q10.525 14 8.8875 13.8T6 13.25V19Q7.25 19.45 8.875 19.725T12 20Q13.5 20 15.125 19.725T18 19ZM12 2Q13.65 2 14.825 3.175T16 6Q16 7.65 14.825 8.825T12 10Q10.35 10 9.175 8.825T8 6Q8 4.35 9.175 3.175T12 2ZM12 8Q12.825 8 13.4125 7.4125T14 6Q14 5.175 13.4125 4.5875T12 4Q11.175 4 10.5875 4.5875T10 6Q10 6.825 10.5875 7.4125T12 8ZM12 6ZM12 16.625Z"),
|
||||
GAMEPAD("M12 7.65ZM16.35 12ZM7.65 12ZM12 16.35ZM12 10.5 9 7.5V2H15V7.5L12 10.5ZM16.5 15 13.5 12 16.5 9H22V15H16.5ZM2 15V9H7.5L10.5 12 7.5 15H2ZM9 22V16.5L12 13.5 15 16.5V22H9ZM12 7.65 13 6.65V4H11V6.65L12 7.65ZM4 13H6.65L7.65 12 6.65 11H4V13ZM11 20H13V17.35L12 16.35 11 17.35V20ZM17.35 13H20V11H17.35L16.35 12 17.35 13Z"),
|
||||
GLOBE_BOOK("M3.075 13Q3.05 12.75 3.0375 12.5T3.025 12Q3.025 10.125 3.725 8.4875T5.65 5.6375Q6.875 4.425 8.5 3.7125T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 12.25 20.9875 12.5T20.95 13H18.925Q18.975 12.75 18.9875 12.5T19 12Q19 11.75 18.9875 11.5T18.925 11H15.975Q16 11.25 16 11.5V12.5Q16 12.75 15.975 13H14V12.175Q14 11.875 13.9875 11.575T13.95 11H10.075Q10.05 11.275 10.0375 11.575T10.025 12.175V13H8.05Q8.025 12.75 8.025 12.5V11.5Q8.025 11.25 8.05 11H5.1Q5.05 11.25 5.0375 11.5T5.025 12Q5.025 12.25 5.0375 12.5T5.1 13H3.075ZM5.7 9H8.275Q8.475 7.925 8.775 7.0625T9.425 5.5Q8.225 5.95 7.25 6.8625T5.7 9ZM10.35 9H13.65Q13.4 7.925 13.025 6.9T12 5Q11.35 5.875 10.9625 6.9T10.35 9ZM15.75 9H18.325Q17.75 7.775 16.7625 6.8625T14.575 5.5Q14.925 6.25 15.2375 7.0875T15.75 9ZM11 21V20Q11 18.75 10.125 17.875T8 17H2V15H8Q9.2 15 10.2375 15.525T12 17Q12.725 16.05 13.7625 15.525T16 15H22V17H16Q14.75 17 13.875 17.875T13 20V21H11Z"),
|
||||
HELP("M11.95 18Q12.475 18 12.8375 17.6375T13.2 16.75Q13.2 16.225 12.8375 15.8625T11.95 15.5Q11.425 15.5 11.0625 15.8625T10.7 16.75Q10.7 17.275 11.0625 17.6375T11.95 18ZM11.05 14.15H12.9Q12.9 13.325 13.0875 12.85T14.15 11.55Q14.8 10.9 15.175 10.3125T15.55 8.9Q15.55 7.5 14.525 6.75T12.1 6Q10.675 6 9.7875 6.75T8.55 8.55L10.2 9.2Q10.325 8.75 10.7625 8.225T12.1 7.7Q12.9 7.7 13.3 8.1375T13.7 9.1Q13.7 9.6 13.4 10.0375T12.65 10.85Q11.55 11.825 11.3 12.325T11.05 14.15ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
HOME("M6 19H9V13H15V19H18V10L12 5.5 6 10V19ZM4 21V9L12 3 20 9V21H13V15H11V21H4ZM12 12.25Z"),
|
||||
HOST("M4 21Q3.175 21 2.5875 20.4125T2 19V5Q2 4.175 2.5875 3.5875T4 3H9Q9.825 3 10.4125 3.5875T11 5V19Q11 19.825 10.4125 20.4125T9 21H4ZM15 21Q14.175 21 13.5875 20.4125T13 19V5Q13 4.175 13.5875 3.5875T15 3H20Q20.825 3 21.4125 3.5875T22 5V19Q22 19.825 21.4125 20.4125T20 21H15ZM4 19H9V5H4V19ZM15 19H20V5H15V19ZM5 15H8V13H5V15ZM16 15H19V13H16V15ZM5 12H8V10H5V12ZM16 12H19V10H16V12ZM5 9H8V7H5V9ZM16 9H19V7H16V9ZM4 19H9 4ZM15 19H20 15Z"),
|
||||
INFO("M11 17H13V11H11V17ZM12 9Q12.425 9 12.7125 8.7125T13 8Q13 7.575 12.7125 7.2875T12 7Q11.575 7 11.2875 7.2875T11 8Q11 8.425 11.2875 8.7125T12 9ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
KEYBOARD_ARROW_DOWN("M12 15.4 6 9.4 7.4 8 12 12.6 16.6 8 18 9.4 12 15.4Z"),
|
||||
KEYBOARD_ARROW_UP("M12 10.8 7.4 15.4 6 14 12 8 18 14 16.6 15.4 12 10.8Z"),
|
||||
LIST("M7 9V7H21V9H7ZM7 13V11H21V13H7ZM7 17V15H21V17H7ZM4 9Q3.575 9 3.2875 8.7125T3 8Q3 7.575 3.2875 7.2875T4 7Q4.425 7 4.7125 7.2875T5 8Q5 8.425 4.7125 8.7125T4 9ZM4 13Q3.575 13 3.2875 12.7125T3 12Q3 11.575 3.2875 11.2875T4 11Q4.425 11 4.7125 11.2875T5 12Q5 12.425 4.7125 12.7125T4 13ZM4 17Q3.575 17 3.2875 16.7125T3 16Q3 15.575 3.2875 15.2875T4 15Q4.425 15 4.7125 15.2875T5 16Q5 16.425 4.7125 16.7125T4 17Z"),
|
||||
LISTS("M2 20V16H6V20H2ZM8 20V16H22V20H8ZM2 14V10H6V14H2ZM8 14V10H22V14H8ZM2 8V4H6V8H2ZM8 8V4H22V8H8Z"),
|
||||
LOCAL_CAFE("M4 21V19H20V21H4ZM8 17Q6.35 17 5.175 15.825T4 13V3H20Q20.825 3 21.4125 3.5875T22 5V8Q22 8.825 21.4125 9.4125T20 10H18V13Q18 14.65 16.825 15.825T14 17H8ZM8 15H14Q14.825 15 15.4125 14.4125T16 13V5H6V13Q6 13.825 6.5875 14.4125T8 15ZM18 8H20V5H18V8ZM8 15H6 16 8Z"),
|
||||
MENU("M3 18V16H21V18H3ZM3 13V11H21V13H3ZM3 8V6H21V8H3Z"),
|
||||
MICROSOFT("M4 20H22v2H4V13H20v7h2V4H20v7H4V4h7V20h2V4h9V2H2V22H4"), // Not Material
|
||||
MINIMIZE("M6 21V19H18V21H6Z"),
|
||||
MOJANG("M13.9658 0C12.9552.828 12.7686 2.195 12.7007 3.418 12.7219 4.4201 14.0423 4.7174 14.6538 4.0082 15.0912 2.6579 14.3692 1.2739 13.9658 0ZM10.913 2.9297C10.7559 3.6983 10.6284 4.4669 10.4925 5.2355 8.9894 3.9913 7.1505 3.0825 5.142 3.2948 3.4944 3.4646.9429 2.6961.1404 4.6452-.1229 8.314.0722 12.0124.034 15.6896-.0891 16.9126.8957 18.1101 2.1781 17.9699 6.3989 18.0039 10.6283 18.0172 14.8491 17.9661 16.1145 18.102 16.8067 16.9554 16.9851 15.8768 13.2696 16.4374 9.1761 16.9296 5.7111 15.1292 2.5986 13.5836 2.246 8.3139 5.5581 6.798 9.3203 5.1759 13.8596 8.0383 14.9424 11.7877 15.6133 12.1105 16.2844 12.4247 16.9596 12.7432 16.6241 10.3483 16.6537 7.8005 15.5793 5.5882 15.0571 4.7474 14.0975 6.2714 13.3799 5.6217 12.416 4.8659 11.7495 3.8129 10.913 2.9297Z"), // Not Material
|
||||
MORE_HORIZ("M6 14Q5.175 14 4.5875 13.4125T4 12Q4 11.175 4.5875 10.5875T6 10Q6.825 10 7.4125 10.5875T8 12Q8 12.825 7.4125 13.4125T6 14ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM18 14Q17.175 14 16.5875 13.4125T16 12Q16 11.175 16.5875 10.5875T18 10Q18.825 10 19.4125 10.5875T20 12Q20 12.825 19.4125 13.4125T18 14Z"),
|
||||
MORE_VERT("M12 20Q11.175 20 10.5875 19.4125T10 18Q10 17.175 10.5875 16.5875T12 16Q12.825 16 13.4125 16.5875T14 18Q14 18.825 13.4125 19.4125T12 20ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM12 8Q11.175 8 10.5875 7.4125T10 6Q10 5.175 10.5875 4.5875T12 4Q12.825 4 13.4125 4.5875T14 6Q14 6.825 13.4125 7.4125T12 8Z"),
|
||||
OPEN_IN_NEW("M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H12V5H5V19H19V12H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM9.7 15.7 8.3 14.3 17.6 5H14V3H21V10H19V6.4L9.7 15.7Z"),
|
||||
OUTPUT("M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V7H19V5H5V19H19V17H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM17 17 15.6 15.6 18.175 13H9V11H18.175L15.6 8.4 17 7 22 12 17 17Z"),
|
||||
PACKAGE("M10 9.75 12 8.75 14 9.75V5H10V9.75ZM7 17V15H12V17H7ZM5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V19Q21 19.825 20.4125 20.4125T19 21H5ZM5 5V19 5ZM5 19H19V5H16V13L12 11 8 13V5H5V19Z"),
|
||||
PACKAGE2("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM11 21.725 4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725ZM16 8.525 17.925 7.425 12 4 10.05 5.125 16 8.525ZM12 10.85 13.95 9.725 8.025 6.3 6.075 7.425 12 10.85Z"),
|
||||
PERSON("M12 12Q10.35 12 9.175 10.825T8 8Q8 6.35 9.175 5.175T12 4Q13.65 4 14.825 5.175T16 8Q16 9.65 14.825 10.825T12 12ZM4 20V17.2Q4 16.35 4.4375 15.6375T5.6 14.55Q7.15 13.775 8.75 13.3875T12 13Q13.65 13 15.25 13.3875T18.4 14.55Q19.125 14.925 19.5625 15.6375T20 17.2V20H4ZM6 18H18V17.2Q18 16.925 17.8625 16.7T17.5 16.35Q16.15 15.675 14.775 15.3375T12 15Q10.6 15 9.225 15.3375T6.5 16.35Q6.275 16.475 6.1375 16.7T6 17.2V18ZM12 10Q12.825 10 13.4125 9.4125T14 8Q14 7.175 13.4125 6.5875T12 6Q11.175 6 10.5875 6.5875T10 8Q10 8.825 10.5875 9.4125T12 10ZM12 8ZM12 18Z"),
|
||||
PUBLIC("M12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM11 19.95V18Q10.175 18 9.5875 17.4125T9 16V15L4.2 10.2Q4.125 10.65 4.0625 11.1T4 12Q4 15.025 5.9875 17.3T11 19.95ZM17.9 17.4Q18.925 16.275 19.4625 14.8875T20 12Q20 9.55 18.6375 7.525T15 4.6V5Q15 5.825 14.4125 6.4125T13 7H11V9Q11 9.425 10.7125 9.7125T10 10H8V12H14Q14.425 12 14.7125 12.2875T15 13V16H16Q16.65 16 17.175 16.3875T17.9 17.4Z"),
|
||||
REFRESH("M12 20Q8.65 20 6.325 17.675T4 12Q4 8.65 6.325 6.325T12 4Q13.725 4 15.3 4.7125T18 6.75V4H20V11H13V9H17.2Q16.4 7.6 15.0125 6.8T12 6Q9.5 6 7.75 7.75T6 12Q6 14.5 7.75 16.25T12 18Q13.925 18 15.475 16.9T17.65 14H19.75Q19.05 16.65 16.9 18.325T12 20Z"),
|
||||
RELEASE_CIRCLE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
|
||||
RESTORE("M12 21Q8.55 21 5.9875 18.7125T3.05 13H5.1Q5.45 15.6 7.4125 17.3T12 19Q14.925 19 16.9625 16.9625T19 12Q19 9.075 16.9625 7.0375T12 5Q10.275 5 8.775 5.8T6.25 8H9V10H3V4H5V6.35Q6.275 4.75 8.1125 3.875T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 13.875 20.2875 15.5125T18.3625 18.3625Q17.15 19.575 15.5125 20.2875T12 21Z"), // Not Material
|
||||
ROCKET_LAUNCH("M5.65 10.025 7.6 10.85Q7.95 10.15 8.325 9.5T9.15 8.2L7.75 7.925 5.65 10.025ZM9.2 12.1 12.05 14.925Q13.1 14.525 14.3 13.7T16.55 11.825Q18.3 10.075 19.2875 7.9375T20.15 4Q18.35 3.875 16.2 4.8625T12.3 7.6Q11.25 8.65 10.425 9.85T9.2 12.1ZM13.65 10.475Q13.075 9.9 13.075 9.0625T13.65 7.65Q14.225 7.075 15.075 7.075T16.5 7.65Q17.075 8.225 17.075 9.0625T16.5 10.475Q15.925 11.05 15.075 11.05T13.65 10.475ZM14.125 18.5 16.225 16.4 15.95 15Q15.3 15.45 14.65 15.8125T13.3 16.525L14.125 18.5ZM21.95 2.175Q22.425 5.2 21.3625 8.0625T17.7 13.525L18.2 16Q18.3 16.5 18.15 16.975T17.65 17.8L13.45 22 11.35 17.075 7.075 12.8 2.15 10.7 6.325 6.5Q6.675 6.15 7.1625 6T8.15 5.95L10.625 6.45Q13.225 3.85 16.075 2.775T21.95 2.175ZM3.925 15.975Q4.8 15.1 6.0625 15.0875T8.2 15.95Q9.075 16.825 9.0625 18.0875T8.175 20.225Q7.55 20.85 6.0875 21.3T2.05 22.1Q2.4 19.525 2.85 18.0625T3.925 15.975ZM5.35 17.375Q5.1 17.625 4.85 18.2875T4.5 19.625Q5.175 19.525 5.8375 19.2875T6.75 18.8Q7.05 18.5 7.075 18.075T6.8 17.35Q6.5 17.05 6.075 17.0625T5.35 17.375Z"),
|
||||
SCREENSHOT_MONITOR("M15 16H19V12H17.5V14.5H15V16ZM5 10H6.5V7.5H9V6H5V10ZM8 21V19H4Q3.175 19 2.5875 18.4125T2 17V5Q2 4.175 2.5875 3.5875T4 3H20Q20.825 3 21.4125 3.5875T22 5V17Q22 17.825 21.4125 18.4125T20 19H16V21H8ZM4 17H20V5H4V17ZM4 17V5 17Z"),
|
||||
SCRIPT("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"), // Not Material
|
||||
SEARCH("M19.6 21 13.3 14.7Q12.55 15.3 11.575 15.65T9.5 16Q6.775 16 4.8875 14.1125T3 9.5Q3 6.775 4.8875 4.8875T9.5 3Q12.225 3 14.1125 4.8875T16 9.5Q16 10.6 15.65 11.575T14.7 13.3L21 19.6 19.6 21ZM9.5 14Q11.375 14 12.6875 12.6875T14 9.5Q14 7.625 12.6875 6.3125T9.5 5Q7.625 5 6.3125 6.3125T5 9.5Q5 11.375 6.3125 12.6875T9.5 14Z"),
|
||||
SELECT_ALL("M7 17V7H17V17H7ZM9 15H15V9H9V15ZM5 19V21Q4.175 21 3.5875 20.4125T3 19H5ZM3 17V15H5V17H3ZM3 13V11H5V13H3ZM3 9V7H5V9H3ZM5 5H3Q3 4.175 3.5875 3.5875T5 3V5ZM7 21V19H9V21H7ZM7 5V3H9V5H7ZM11 21V19H13V21H11ZM11 5V3H13V5H11ZM15 21V19H17V21H15ZM15 5V3H17V5H15ZM19 21V19H21Q21 19.825 20.4125 20.4125T19 21ZM19 17V15H21V17H19ZM19 13V11H21V13H19ZM19 9V7H21V9H19ZM19 5V3Q19.825 3 20.4125 3.5875T21 5H19Z"),
|
||||
SETTINGS("M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12 19.5 11.66 19.47 11.34 19.43 11.02L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.57 5.11 19.4 5.02 19.22 5.02 19.16 5.02 19.1 5.03 19.05 5.05L16.56 6.05C16.04 5.65 15.48 5.32 14.87 5.07L14.49 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.89 5.03 4.83 5.02 4.77 5.02 4.6 5.02 4.43 5.11 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11.02C4.53 11.34 4.5 11.67 4.5 12 4.5 12.33 4.53 12.66 4.57 12.98L2.46 14.63C2.27 14.78 2.22 15.05 2.34 15.27L4.34 18.73C4.43 18.89 4.6 18.98 4.78 18.98 4.84 18.98 4.9 18.97 4.95 18.95L7.44 17.95C7.96 18.35 8.52 18.68 9.13 18.93L9.51 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.49 21.58L14.87 18.93C15.48 18.68 16.04 18.34 16.56 17.95L19.05 18.95C19.11 18.97 19.17 18.98 19.23 18.98 19.4 18.98 19.57 18.89 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.98ZM17.45 11.27C17.49 11.58 17.5 11.79 17.5 12 17.5 12.21 17.48 12.43 17.45 12.73L17.31 13.86 18.2 14.56 19.28 15.4 18.58 16.61 17.31 16.1 16.27 15.68 15.37 16.36C14.94 16.68 14.53 16.92 14.12 17.09L13.06 17.52 12.9 18.65 12.7 20H11.3L11.11 18.65 10.95 17.52 9.89 17.09C9.46 16.91 9.06 16.68 8.66 16.38L7.75 15.68 6.69 16.11 5.42 16.62 4.72 15.41 5.8 14.57 6.69 13.87 6.55 12.74C6.52 12.43 6.5 12.2 6.5 12S6.52 11.57 6.55 11.27L6.69 10.14 5.8 9.44 4.72 8.6 5.42 7.39 6.69 7.9 7.73 8.32 8.63 7.64C9.06 7.32 9.47 7.08 9.88 6.91L10.94 6.48 11.1 5.35 11.3 4H12.69L12.88 5.35 13.04 6.48 14.1 6.91C14.53 7.09 14.93 7.32 15.33 7.62L16.24 8.32 17.3 7.89 18.57 7.38 19.27 8.59 18.2 9.44 17.31 10.14 17.45 11.27ZM12 8C9.79 8 8 9.79 8 12S9.79 16 12 16 16 14.21 16 12 14.21 8 12 8ZM12 14C10.9 14 10 13.1 10 12S10.9 10 12 10 14 10.9 14 12 13.1 14 12 14Z"), // Material Icons
|
||||
STADIA_CONTROLLER("M4.725 20Q3.225 20 2.1625 18.925T1.05 16.325Q1.05 16.1 1.075 15.875T1.15 15.425L3.25 7.025Q3.6 5.675 4.675 4.8375T7.125 4H16.875Q18.25 4 19.325 4.8375T20.75 7.025L22.85 15.425Q22.9 15.65 22.9375 15.8875T22.975 16.35Q22.975 17.875 21.8875 18.9375T19.275 20Q18.225 20 17.325 19.45T15.975 17.95L15.275 16.5Q15.15 16.25 14.9 16.125T14.375 16H9.625Q9.35 16 9.1 16.125T8.725 16.5L8.025 17.95Q7.575 18.9 6.675 19.45T4.725 20ZM4.8 18Q5.275 18 5.6625 17.75T6.25 17.075L6.95 15.65Q7.325 14.875 8.05 14.4375T9.625 14H14.375Q15.225 14 15.95 14.45T17.075 15.65L17.775 17.075Q17.975 17.5 18.3625 17.75T19.225 18Q19.925 18 20.425 17.5375T20.95 16.375Q20.95 16.4 20.9 15.9L18.8 7.525Q18.625 6.85 18.1 6.425T16.875 6H7.125Q6.425 6 5.8875 6.425T5.2 7.525L3.1 15.9Q3.05 16.05 3.05 16.35 3.05 17.05 3.5625 17.525T4.8 18ZM13.5 11Q13.925 11 14.2125 10.7125T14.5 10Q14.5 9.575 14.2125 9.2875T13.5 9Q13.075 9 12.7875 9.2875T12.5 10Q12.5 10.425 12.7875 10.7125T13.5 11ZM15.5 9Q15.925 9 16.2125 8.7125T16.5 8Q16.5 7.575 16.2125 7.2875T15.5 7Q15.075 7 14.7875 7.2875T14.5 8Q14.5 8.425 14.7875 8.7125T15.5 9ZM15.5 13Q15.925 13 16.2125 12.7125T16.5 12Q16.5 11.575 16.2125 11.2875T15.5 11Q15.075 11 14.7875 11.2875T14.5 12Q14.5 12.425 14.7875 12.7125T15.5 13ZM17.5 11Q17.925 11 18.2125 10.7125T18.5 10Q18.5 9.575 18.2125 9.2875T17.5 9Q17.075 9 16.7875 9.2875T16.5 10Q16.5 10.425 16.7875 10.7125T17.5 11ZM8.5 12.5Q8.825 12.5 9.0375 12.2875T9.25 11.75V10.75H10.25Q10.575 10.75 10.7875 10.5375T11 10Q11 9.675 10.7875 9.4625T10.25 9.25H9.25V8.25Q9.25 7.925 9.0375 7.7125T8.5 7.5Q8.175 7.5 7.9625 7.7125T7.75 8.25V9.25H6.75Q6.425 9.25 6.2125 9.4625T6 10Q6 10.325 6.2125 10.5375T6.75 10.75H7.75V11.75Q7.75 12.075 7.9625 12.2875T8.5 12.5ZM12 12Z"),
|
||||
STYLE("M3.975 19.8 3.125 19.45Q2.35 19.125 2.0875 18.325T2.175 16.75L3.975 12.85V19.8ZM7.975 22Q7.15 22 6.5625 21.4125T5.975 20V14L8.625 21.35Q8.7 21.525 8.775 21.6875T8.975 22H7.975ZM13.125 21.9Q12.325 22.2 11.575 21.825T10.525 20.65L6.075 8.45Q5.775 7.65 6.125 6.8875T7.275 5.85L14.825 3.1Q15.625 2.8 16.375 3.175T17.425 4.35L21.875 16.55Q22.175 17.35 21.825 18.1125T20.675 19.15L13.125 21.9ZM10.975 10Q11.4 10 11.6875 9.7125T11.975 9Q11.975 8.575 11.6875 8.2875T10.975 8Q10.55 8 10.2625 8.2875T9.975 9Q9.975 9.425 10.2625 9.7125T10.975 10ZM12.425 20 19.975 17.25 15.525 5 7.975 7.75 12.425 20ZM7.975 7.75 15.525 5 7.975 7.75Z"),
|
||||
TEXTURE("M4.4-3Q3.925-3.1 3.5125-3.5125T3-4.4L19.6-21Q20.125-20.875 20.5-20.4875T21.025-19.6L4.4-3ZM3-9.3V-12.1L11.9-21H14.7L3-9.3ZM3-17V-19Q3-19.825 3.5875-20.4125T5-21H7L3-17ZM17-3 21-7V-5Q21-4.175 20.4125-3.5875T19-3H17ZM9.3-3 21-14.7V-11.9L12.1-3H9.3Z"),
|
||||
TRIP("M4 21Q3.175 21 2.5875 20.4125T2 19V8Q2 7.175 2.5875 6.5875T4 6H8V4Q8 3.175 8.5875 2.5875T10 2H14Q14.825 2 15.4125 2.5875T16 4V6H20Q20.825 6 21.4125 6.5875T22 8V19Q22 19.825 21.4125 20.4125T20 21H4ZM10 6H14V4H10V6ZM6 8H4V19H6V8ZM16 19V8H8V19H16ZM18 8V19H20V8H18ZM12 13.5Z"),
|
||||
TUNE("M11 21V15H13V17H21V19H13V21H11ZM3 19V17H9V19H3ZM7 15V13H3V11H7V9H9V15H7ZM11 13V11H21V13H11ZM15 9V3H17V5H21V7H17V9H15ZM3 7V5H13V7H3Z"),
|
||||
UPDATE("M12 21Q10.125 21 8.4875 20.2875T5.6375 18.3625Q4.425 17.15 3.7125 15.5125T3 12Q3 10.125 3.7125 8.4875T5.6375 5.6375Q6.85 4.425 8.4875 3.7125T12 3Q14.05 3 15.8875 3.875T19 6.35V4H21V10H15V8H17.75Q16.725 6.6 15.225 5.8T12 5Q9.075 5 7.0375 7.0375T5 12Q5 14.925 7.0375 16.9625T12 19Q14.625 19 16.5875 17.3T18.9 13H20.95Q20.575 16.425 18.0125 18.7125T12 21ZM14.8 16.2 11 12.4V7H13V11.6L16.2 14.8 14.8 16.2Z"),
|
||||
WARNING("M1 21 12 2 23 21H1ZM4.45 19H19.55L12 6 4.45 19ZM12 18Q12.425 18 12.7125 17.7125T13 17Q13 16.575 12.7125 16.2875T12 16Q11.575 16 11.2875 16.2875T11 17Q11 17.425 11.2875 17.7125T12 18ZM11 15H13V10H11V15ZM12 12.5Z"),
|
||||
WB_SUNNY("M11 4V1H13V4H11ZM11 23V20H13V23H11ZM20 13V11H23V13H20ZM1 13V11H4V13H1ZM18.7 6.7 17.3 5.3 19.05 3.5 20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3 6.7 18.7 4.95 20.5ZM19.05 20.5 17.3 18.7 18.7 17.3 20.5 19.05 19.05 20.5ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18Q9.5 18 7.75 16.25T6 12Q6 9.5 7.75 7.75T12 6Q14.5 6 16.25 7.75T18 12Q18 14.5 16.25 16.25T12 18ZM12 16Q13.675 16 14.8375 14.8375T16 12Q16 10.325 14.8375 9.1625T12 8Q10.325 8 9.1625 9.1625T8 12Q8 13.675 9.1625 14.8375T12 16ZM12 12Z"),
|
||||
;
|
||||
|
||||
public static final double DEFAULT_SIZE = 24;
|
||||
|
||||
private final String path;
|
||||
|
||||
@@ -124,39 +118,38 @@ public enum SVG {
|
||||
return path;
|
||||
}
|
||||
|
||||
private static Node createIcon(SVGPath path, double width, double height) {
|
||||
if (width < 0 || height < 0) {
|
||||
private static Node createIcon(SVGPath path, double size) {
|
||||
if (size < 0) {
|
||||
StackPane pane = new StackPane(path);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
return pane;
|
||||
}
|
||||
|
||||
Group svg = new Group(path);
|
||||
double scale = Math.min(width / 24, height / 24);
|
||||
double scale = size / 24;
|
||||
svg.setScaleX(scale);
|
||||
svg.setScaleY(scale);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
public Node createIcon(ObservableValue<? extends Paint> fill, double width, double height) {
|
||||
public Node createIcon(ObservableValue<? extends Paint> fill, double size) {
|
||||
SVGPath p = new SVGPath();
|
||||
p.getStyleClass().add("svg");
|
||||
p.setContent(path);
|
||||
if (fill != null)
|
||||
p.fillProperty().bind(fill);
|
||||
|
||||
return createIcon(p, width, height);
|
||||
return createIcon(p, size);
|
||||
}
|
||||
|
||||
public Node createIcon(Paint fill, double width, double height) {
|
||||
public Node createIcon(Paint fill, double size) {
|
||||
SVGPath p = new SVGPath();
|
||||
p.getStyleClass().add("svg");
|
||||
p.setContent(path);
|
||||
if (fill != null)
|
||||
p.fillProperty().set(fill);
|
||||
|
||||
return createIcon(p, width, height);
|
||||
return createIcon(p, size);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,14 +25,11 @@ import javafx.animation.Animation.Status;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
|
||||
/**
|
||||
* Utility class for ScrollPanes.
|
||||
@@ -142,23 +139,23 @@ final class ScrollUtils {
|
||||
smoothScroll(scrollPane, speed, trackPadAdjustment);
|
||||
}
|
||||
|
||||
private static final double[] FRICTIONS = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
|
||||
private static final Duration DURATION = Duration.millis(3);
|
||||
|
||||
private static void smoothScroll(ScrollPane scrollPane, double speed, double trackPadAdjustment) {
|
||||
final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
|
||||
final double[] derivatives = new double[frictions.length];
|
||||
AtomicReference<Double> atomicSpeed = new AtomicReference<>(speed);
|
||||
final double[] derivatives = new double[FRICTIONS.length];
|
||||
|
||||
Timeline timeline = new Timeline();
|
||||
AtomicReference<ScrollDirection> scrollDirection = new AtomicReference<>();
|
||||
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
|
||||
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
|
||||
final EventHandler<ScrollEvent> scrollHandler = event -> {
|
||||
if (event.getEventType() == ScrollEvent.SCROLL) {
|
||||
scrollDirection.set(determineScrollDirection(event));
|
||||
if (isTrackPad(event, scrollDirection.get())) {
|
||||
atomicSpeed.set(speed / trackPadAdjustment);
|
||||
} else {
|
||||
atomicSpeed.set(speed);
|
||||
}
|
||||
derivatives[0] += scrollDirection.get().intDirection * atomicSpeed.get();
|
||||
ScrollDirection scrollDirection = determineScrollDirection(event);
|
||||
scrollDirectionHolder.value = scrollDirection;
|
||||
|
||||
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
|
||||
|
||||
derivatives[0] += scrollDirection.intDirection * currentSpeed;
|
||||
if (timeline.getStatus() == Status.STOPPED) {
|
||||
timeline.play();
|
||||
}
|
||||
@@ -180,28 +177,26 @@ final class ScrollUtils {
|
||||
}
|
||||
});
|
||||
|
||||
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
|
||||
timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {
|
||||
for (int i = 0; i < derivatives.length; i++) {
|
||||
derivatives[i] *= frictions[i];
|
||||
derivatives[i] *= FRICTIONS[i];
|
||||
}
|
||||
for (int i = 1; i < derivatives.length; i++) {
|
||||
derivatives[i] += derivatives[i - 1];
|
||||
}
|
||||
|
||||
double dy = derivatives[derivatives.length - 1];
|
||||
Function<Bounds, Double> sizeFunction = (scrollDirection.get() == ScrollDirection.UP || scrollDirection.get() == ScrollDirection.DOWN) ? Bounds::getHeight : Bounds::getWidth;
|
||||
double size = sizeFunction.apply(scrollPane.getContent().getLayoutBounds());
|
||||
double value;
|
||||
switch (scrollDirection.get()) {
|
||||
double size;
|
||||
switch (scrollDirectionHolder.value) {
|
||||
case LEFT:
|
||||
case RIGHT:
|
||||
value = Math.min(Math.max(scrollPane.hvalueProperty().get() + dy / size, 0), 1);
|
||||
scrollPane.hvalueProperty().set(value);
|
||||
size = scrollPane.getContent().getLayoutBounds().getWidth();
|
||||
scrollPane.setHvalue(Math.min(Math.max(scrollPane.getHvalue() + dy / size, 0), 1));
|
||||
break;
|
||||
case UP:
|
||||
case DOWN:
|
||||
value = Math.min(Math.max(scrollPane.vvalueProperty().get() + dy / size, 0), 1);
|
||||
scrollPane.vvalueProperty().set(value);
|
||||
size = scrollPane.getContent().getLayoutBounds().getHeight();
|
||||
scrollPane.setVvalue(Math.min(Math.max(scrollPane.getVvalue() + dy / size, 0), 1));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-tool-bar-button");
|
||||
ret.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
|
||||
ret.setText(text);
|
||||
ret.setOnAction(e -> onClick.run());
|
||||
return ret;
|
||||
@@ -98,7 +98,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-tool-bar-button");
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1, -1)));
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1)));
|
||||
ret.setText(text);
|
||||
ret.setOnAction(e -> onClick.run());
|
||||
return ret;
|
||||
@@ -108,7 +108,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-decorator-button");
|
||||
ret.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
|
||||
FXUtils.installFastTooltip(ret, tooltip);
|
||||
ret.setOnAction(e -> onClick.run());
|
||||
return ret;
|
||||
|
||||
@@ -64,11 +64,13 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
node = node.nextSibling();
|
||||
} while (node != null);
|
||||
|
||||
renderer.mergeLineBreaks();
|
||||
return renderer.render();
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
ScrollPane scrollPane = new ScrollPane(result);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
setBody(scrollPane);
|
||||
} else {
|
||||
LOG.warning("Failed to load update log, trying to open it in browser");
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class WebPage extends SpinnerPane implements DecoratorPage {
|
||||
}, null);
|
||||
});
|
||||
renderer.appendNode(document);
|
||||
renderer.mergeLineBreaks();
|
||||
return renderer.render();
|
||||
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
|
||||
@@ -20,7 +20,9 @@ package org.jackhuang.hmcl.ui.account;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
@@ -50,13 +52,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(root, skinnable::fire);
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
chkSelected.setMouseTransparent(true);
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
@@ -116,10 +116,10 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
});
|
||||
btnMove.getStyleClass().add("toggle-icon4");
|
||||
if (skinnable.getAccount().isPortable()) {
|
||||
btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
|
||||
btnMove.setGraphic(SVG.PUBLIC.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
|
||||
} else {
|
||||
btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1));
|
||||
btnMove.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
|
||||
}
|
||||
spinnerMove.setContent(btnMove);
|
||||
@@ -141,7 +141,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
.start();
|
||||
});
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1));
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
|
||||
spinnerRefresh.setContent(btnRefresh);
|
||||
right.getChildren().add(spinnerRefresh);
|
||||
@@ -158,10 +158,9 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
}
|
||||
});
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1));
|
||||
btnUpload.setGraphic(SVG.CHECKROOM.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
|
||||
spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty());
|
||||
spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin());
|
||||
btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));
|
||||
spinnerUpload.setContent(btnUpload);
|
||||
spinnerUpload.getStyleClass().add("small-spinner-pane");
|
||||
right.getChildren().add(spinnerUpload);
|
||||
@@ -171,7 +170,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
|
||||
btnCopyUUID.setGraphic(SVG.COPY.createIcon(Theme.blackFill(), -1, -1));
|
||||
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
|
||||
spinnerCopyUUID.setContent(btnCopyUUID);
|
||||
right.getChildren().add(spinnerCopyUUID);
|
||||
@@ -180,7 +179,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1));
|
||||
btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
@@ -103,7 +103,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
offlineItem.getStyleClass().add("navigation-drawer-item");
|
||||
offlineItem.setActionButtonVisible(false);
|
||||
offlineItem.setTitle(i18n("account.methods.offline"));
|
||||
offlineItem.setLeftGraphic(wrap(SVG.ACCOUNT));
|
||||
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
|
||||
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
||||
boxMethods.getChildren().add(offlineItem);
|
||||
|
||||
@@ -119,7 +119,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
item.getStyleClass().add("navigation-drawer-item");
|
||||
item.setLeftGraphic(wrap(SVG.SERVER));
|
||||
item.setLeftGraphic(wrap(SVG.DRESSER));
|
||||
item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
@@ -130,7 +130,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
e.consume();
|
||||
});
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
|
||||
item.setRightGraphic(btnRemove);
|
||||
|
||||
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
|
||||
@@ -158,7 +158,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
addAuthServerItem.setTitle(i18n("account.injector.add"));
|
||||
addAuthServerItem.setSubtitle(i18n("account.methods.authlib_injector"));
|
||||
addAuthServerItem.setActionButtonVisible(false);
|
||||
addAuthServerItem.setLeftGraphic(wrap(SVG.PLUS_CIRCLE_OUTLINE));
|
||||
addAuthServerItem.setLeftGraphic(wrap(SVG.ADD_CIRCLE));
|
||||
addAuthServerItem.setOnAction(e -> Controllers.dialog(new AddAuthlibInjectorServerPane()));
|
||||
VBox.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0));
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
doCreate,
|
||||
() -> {
|
||||
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid"));
|
||||
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid.tip"));
|
||||
body.setDisable(false);
|
||||
spinner.hideSpinner();
|
||||
}
|
||||
@@ -460,7 +460,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
JFXButton btnAddServer = new JFXButton();
|
||||
btnAddServer.setGraphic(SVG.PLUS.createIcon(Theme.blackFill(), 20, 20));
|
||||
btnAddServer.setGraphic(SVG.ADD.createIcon(Theme.blackFill(), 20));
|
||||
btnAddServer.getStyleClass().add("toggle-icon4");
|
||||
btnAddServer.setOnAction(e -> {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
|
||||
@@ -101,6 +101,9 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
VBox optionPane = new VBox(skinItem, skinOptionPane);
|
||||
pane.setRight(optionPane);
|
||||
|
||||
skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
|
||||
capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
|
||||
|
||||
layout.setBody(pane);
|
||||
|
||||
cslApiField.setPromptText(i18n("account.skin.type.csl_api.location.hint"));
|
||||
@@ -108,10 +111,10 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
|
||||
skinItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("message.default"), Skin.Type.DEFAULT),
|
||||
new MultiFileItem.Option<>("Steve", Skin.Type.STEVE),
|
||||
new MultiFileItem.Option<>("Alex", Skin.Type.ALEX),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.steve"), Skin.Type.STEVE),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.alex"), Skin.Type.ALEX),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.local_file"), Skin.Type.LOCAL_FILE),
|
||||
new MultiFileItem.Option<>("LittleSkin", Skin.Type.LITTLE_SKIN),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.little_skin"), Skin.Type.LITTLE_SKIN),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.csl_api"), Skin.Type.CUSTOM_SKIN_LOADER_API)
|
||||
));
|
||||
|
||||
@@ -201,6 +204,12 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
}
|
||||
|
||||
private Skin getSkin() {
|
||||
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
|
||||
Skin.Type type = skinItem.getSelectedData();
|
||||
if (type == Skin.Type.LOCAL_FILE) {
|
||||
return new Skin(type, cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
|
||||
} else {
|
||||
String cslApi = type == Skin.Type.CUSTOM_SKIN_LOADER_API ? cslApiField.getText() : null;
|
||||
return new Skin(type, cslApi, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,28 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class AnimationUtils {
|
||||
|
||||
private AnimationUtils() {
|
||||
@@ -15,9 +36,14 @@ public final class AnimationUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static final boolean enabled = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();
|
||||
|
||||
public static boolean isAnimationEnabled() {
|
||||
return enabled;
|
||||
return ENABLED;
|
||||
}
|
||||
|
||||
public static boolean playWindowAnimation() {
|
||||
return PLAY_WINDOW_ANIMATION;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
@@ -26,6 +25,8 @@ import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
private static final Duration DEFAULT_DURATION = Duration.millis(200);
|
||||
|
||||
private Duration duration;
|
||||
private Node previousNode, currentNode;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition) {
|
||||
setContent(newView, transition, Duration.millis(160));
|
||||
setContent(newView, transition, DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
|
||||
@@ -64,26 +65,25 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
updateContent(newView);
|
||||
|
||||
if (previousNode == EMPTY_PANE) {
|
||||
setMouseTransparent(false);
|
||||
getChildren().setAll(newView);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
|
||||
setMouseTransparent(true);
|
||||
transition.init(this);
|
||||
|
||||
// runLater or "init" will not work
|
||||
Platform.runLater(() -> {
|
||||
Timeline newAnimation = new Timeline();
|
||||
newAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
newAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
newAnimation.getKeyFrames().setAll(transition.animate(this));
|
||||
newAnimation.setOnFinished(e -> {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}));
|
||||
});
|
||||
FXUtils.playAnimation(this, "transition_pane", newAnimation);
|
||||
});
|
||||
} else {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,6 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
if (previousNode == newView)
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
getChildren().setAll(previousNode, currentNode);
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -54,11 +56,36 @@ public class AdvancedListBox extends ScrollPane {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(Consumer<AdvancedListItem> fn) {
|
||||
private AdvancedListItem createNavigationDrawerItem(String title, SVG leftGraphic) {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
item.getStyleClass().add("navigation-drawer-item");
|
||||
item.setActionButtonVisible(false);
|
||||
fn.accept(item);
|
||||
item.setTitle(title);
|
||||
if (leftGraphic != null) {
|
||||
item.setLeftGraphic(VersionPage.wrap(leftGraphic));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction) {
|
||||
return addNavigationDrawerItem(title, leftGraphic, onAction, null);
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction, Consumer<AdvancedListItem> initializer) {
|
||||
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
|
||||
if (onAction != null) {
|
||||
item.setOnAction(e -> onAction.run());
|
||||
}
|
||||
if (initializer != null) {
|
||||
initializer.accept(item);
|
||||
}
|
||||
return add(item);
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title, SVG leftGraphic) {
|
||||
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
|
||||
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
|
||||
item.setOnAction(e -> tabHeader.select(tab));
|
||||
return add(item);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
RipplerContainer container = new RipplerContainer(root);
|
||||
|
||||
HBox left = new HBox();
|
||||
left.setAlignment(Pos.CENTER_LEFT);
|
||||
left.setMouseTransparent(true);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
public final class AnnouncementCard extends VBox {
|
||||
|
||||
public AnnouncementCard(String title, String content, Runnable onClose) {
|
||||
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
|
||||
body.setLineSpacing(4);
|
||||
|
||||
BorderPane titleBar = new BorderPane();
|
||||
titleBar.getStyleClass().add("title");
|
||||
titleBar.setLeft(new Label(title));
|
||||
|
||||
if (onClose != null) {
|
||||
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
|
||||
hideNode.setOnMouseClicked(e -> onClose.run());
|
||||
hideNode.setCursor(Cursor.HAND);
|
||||
titleBar.setRight(hideNode);
|
||||
}
|
||||
|
||||
getChildren().setAll(titleBar, body);
|
||||
setSpacing(16);
|
||||
getStyleClass().addAll("card", "announcement");
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ final class ComponentListCell extends StackPane {
|
||||
|
||||
VBox groupNode = new VBox();
|
||||
|
||||
Node expandIcon = SVG.EXPAND.createIcon(Theme.blackFill(), 20, 20);
|
||||
Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(Theme.blackFill(), 20);
|
||||
JFXButton expandButton = new JFXButton();
|
||||
expandButton.setGraphic(expandIcon);
|
||||
expandButton.getStyleClass().add("options-list-item-expand-button");
|
||||
@@ -149,8 +149,8 @@ final class ComponentListCell extends StackPane {
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
double newAnimatedHeight = (list.prefHeight(-1) + 8 + 10) * (expanded ? 1 : -1);
|
||||
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(-1);
|
||||
double newAnimatedHeight = (list.prefHeight(list.getWidth()) + 8 + 10) * (expanded ? 1 : -1);
|
||||
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(list.getWidth());
|
||||
double contentHeight = expanded ? newAnimatedHeight : 0;
|
||||
|
||||
if (expanded) {
|
||||
|
||||
@@ -58,7 +58,7 @@ public class FileItem extends BorderPane {
|
||||
setLeft(left);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
right.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 15, 15));
|
||||
right.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 16));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
right.setOnAction(e -> onExplore());
|
||||
FXUtils.installFastTooltip(right, i18n("button.edit"));
|
||||
|
||||
@@ -81,7 +81,7 @@ public class FileSelector extends HBox {
|
||||
FXUtils.bindString(customField, valueProperty());
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15, 15));
|
||||
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15));
|
||||
selectButton.setOnAction(e -> {
|
||||
if (directory) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
|
||||
@@ -49,25 +49,25 @@ public class HintPane extends VBox {
|
||||
SVG svg;
|
||||
switch (type) {
|
||||
case INFO:
|
||||
svg = SVG.INFORMATION_OUTLINE;
|
||||
svg = SVG.INFO;
|
||||
break;
|
||||
case ERROR:
|
||||
svg = SVG.CLOSE_CIRCLE_OUTLINE;
|
||||
svg = SVG.ERROR;
|
||||
break;
|
||||
case SUCCESS:
|
||||
svg = SVG.CHECK_CIRCLE_OUTLINE;
|
||||
svg = SVG.CHECK_CIRCLE;
|
||||
break;
|
||||
case WARNING:
|
||||
svg = SVG.ALERT_OUTLINE;
|
||||
svg = SVG.WARNING;
|
||||
break;
|
||||
case QUESTION:
|
||||
svg = SVG.HELP_CIRCLE_OUTLINE;
|
||||
svg = SVG.HELP;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box message type " + type);
|
||||
}
|
||||
|
||||
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16, 16), new Text(type.getDisplayName()));
|
||||
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16), new Text(type.getDisplayName()));
|
||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||
flow.getChildren().setAll(label);
|
||||
getChildren().setAll(hbox, flow);
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
public class IconedMenuItem extends IconedItem {
|
||||
|
||||
public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) {
|
||||
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14, 14), 14, 14) : null, text);
|
||||
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14), 14, 14) : null, text);
|
||||
|
||||
getStyleClass().setAll("iconed-menu-item");
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ public class IconedTwoLineListItem extends HBox {
|
||||
if (externalLinkButton == null) {
|
||||
externalLinkButton = new JFXButton();
|
||||
externalLinkButton.getStyleClass().add("toggle-icon4");
|
||||
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
|
||||
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1));
|
||||
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
|
||||
}
|
||||
return externalLinkButton;
|
||||
|
||||
@@ -54,12 +54,12 @@ public final class ImagePickerItem extends BorderPane {
|
||||
imageView.setPreserveRatio(true);
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 20, 20));
|
||||
selectButton.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 20));
|
||||
selectButton.onActionProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20));
|
||||
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
|
||||
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
|
||||
@@ -20,24 +20,28 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class StackContainerPane extends StackPane {
|
||||
private final Stack<Node> stack = new Stack<>();
|
||||
public class JFXDialogPane extends StackPane {
|
||||
private final ArrayList<Node> stack = new ArrayList<>();
|
||||
|
||||
public int size() {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
public Optional<Node> peek() {
|
||||
if (stack.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(stack.peek());
|
||||
return Optional.of(stack.get(stack.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void push(Node node) {
|
||||
stack.push(node);
|
||||
stack.add(node);
|
||||
getChildren().setAll(node);
|
||||
|
||||
LOG.info(this + " " + stack);
|
||||
@@ -48,7 +52,7 @@ public class StackContainerPane extends StackPane {
|
||||
if (stack.isEmpty())
|
||||
getChildren().setAll();
|
||||
else
|
||||
getChildren().setAll(stack.peek());
|
||||
getChildren().setAll(stack.get(stack.size() - 1));
|
||||
|
||||
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
|
||||
}
|
||||
@@ -22,18 +22,12 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
public class JFXHyperlink extends Hyperlink {
|
||||
|
||||
public JFXHyperlink() {
|
||||
super();
|
||||
|
||||
setGraphic(SVG.LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 16, 16));
|
||||
}
|
||||
public final class JFXHyperlink extends Hyperlink {
|
||||
|
||||
public JFXHyperlink(String text) {
|
||||
super(text);
|
||||
|
||||
setGraphic(SVG.LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 16, 16));
|
||||
setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16));
|
||||
}
|
||||
|
||||
public void setExternalLink(String externalLink) {
|
||||
|
||||
@@ -81,8 +81,8 @@ public class MenuUpDownButton extends Control {
|
||||
label.setStyle("-fx-text-fill: black;");
|
||||
label.textProperty().bind(control.text);
|
||||
|
||||
Node up = SVG.MENU_UP.createIcon(Theme.blackFill(), 16, 16);
|
||||
Node down = SVG.MENU_DOWN.createIcon(Theme.blackFill(), 16, 16);
|
||||
Node up = SVG.ARROW_DROP_UP.createIcon(Theme.blackFill(), 16);
|
||||
Node down = SVG.ARROW_DROP_DOWN.createIcon(Theme.blackFill(), 16);
|
||||
|
||||
JFXButton button = new JFXButton();
|
||||
button.setGraphic(content);
|
||||
|
||||
@@ -74,24 +74,24 @@ public final class MessageDialogPane extends HBox {
|
||||
SVG svg;
|
||||
switch (type) {
|
||||
case INFO:
|
||||
svg = SVG.INFO_CIRCLE;
|
||||
svg = SVG.INFO;
|
||||
break;
|
||||
case ERROR:
|
||||
svg = SVG.CLOSE_CIRCLE;
|
||||
svg = SVG.ERROR;
|
||||
break;
|
||||
case SUCCESS:
|
||||
svg = SVG.CHECK_CIRCLE;
|
||||
break;
|
||||
case WARNING:
|
||||
svg = SVG.ALERT;
|
||||
svg = SVG.WARNING;
|
||||
break;
|
||||
case QUESTION:
|
||||
svg = SVG.HELP_CIRCLE;
|
||||
svg = SVG.HELP;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box message type " + type);
|
||||
}
|
||||
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40, 40));
|
||||
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40));
|
||||
|
||||
VBox vbox = new VBox();
|
||||
HBox.setHgrow(vbox, Priority.ALWAYS);
|
||||
|
||||
@@ -53,10 +53,9 @@ public final class MultiFileItem<T> extends VBox {
|
||||
setSpacing(8);
|
||||
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
selectedData.set(newValue != null ? (T) newValue.getUserData() : null);
|
||||
if (toggleSelectedListener != null)
|
||||
toggleSelectedListener.accept(newValue);
|
||||
|
||||
selectedData.set(newValue != null ? (T) newValue.getUserData() : null);
|
||||
});
|
||||
selectedData.addListener((a, b, newValue) -> {
|
||||
Optional<Toggle> selecting = group.getToggles().stream()
|
||||
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
@@ -46,6 +47,7 @@ import java.util.List;
|
||||
@DefaultProperty("container")
|
||||
public class RipplerContainer extends StackPane {
|
||||
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
|
||||
private static final Duration DURATION = Duration.millis(200);
|
||||
|
||||
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
|
||||
@@ -103,11 +105,10 @@ public class RipplerContainer extends StackPane {
|
||||
rectangle.heightProperty().bind(heightProperty());
|
||||
}));
|
||||
|
||||
setOnMouseEntered(e -> {
|
||||
new Transition() {
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
setOnMouseEntered(e -> new Transition() {
|
||||
{
|
||||
setCycleCount(1);
|
||||
setCycleDuration(Duration.millis(200));
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Interpolator.EASE_IN);
|
||||
}
|
||||
|
||||
@@ -115,14 +116,11 @@ public class RipplerContainer extends StackPane {
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(frac);
|
||||
}
|
||||
}.play();
|
||||
});
|
||||
}.play());
|
||||
|
||||
setOnMouseExited(e -> {
|
||||
new Transition() {
|
||||
setOnMouseExited(e -> new Transition() {
|
||||
{
|
||||
setCycleCount(1);
|
||||
setCycleDuration(Duration.millis(200));
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Interpolator.EASE_OUT);
|
||||
}
|
||||
|
||||
@@ -130,8 +128,11 @@ public class RipplerContainer extends StackPane {
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(1 - frac);
|
||||
}
|
||||
}.play();
|
||||
});
|
||||
}.play());
|
||||
} else {
|
||||
setOnMouseEntered(e -> interpolateBackground(1));
|
||||
setOnMouseExited(e -> interpolateBackground(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void interpolateBackground(double frac) {
|
||||
|
||||
@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
|
||||
}
|
||||
}
|
||||
|
||||
public interface State {}
|
||||
|
||||
public static class LoadedState implements State {}
|
||||
|
||||
public static class LoadingState implements State {}
|
||||
|
||||
public static class FailedState implements State {
|
||||
private final String reason;
|
||||
|
||||
public FailedState(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
|
||||
}
|
||||
@@ -270,21 +270,21 @@ public final class TaskListPane extends StackPane {
|
||||
BorderPane.setMargin(title, new Insets(0, 0, 0, 8));
|
||||
setPadding(new Insets(0, 0, 8, 4));
|
||||
setCenter(title);
|
||||
setLeft(FXUtils.limitingSize(SVG.DOTS_HORIZONTAL.createIcon(Theme.blackFill(), 14, 14), 14, 14));
|
||||
setLeft(FXUtils.limitingSize(SVG.MORE_HORIZ.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
}
|
||||
|
||||
public void begin() {
|
||||
if (started) return;
|
||||
started = true;
|
||||
setLeft(FXUtils.limitingSize(SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), 14, 14), 14, 14));
|
||||
setLeft(FXUtils.limitingSize(SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
}
|
||||
|
||||
public void fail() {
|
||||
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14), 14, 14));
|
||||
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
}
|
||||
|
||||
public void succeed() {
|
||||
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14, 14), 14, 14));
|
||||
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
}
|
||||
|
||||
public void count() {
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
|
||||
public class Decorator extends Control {
|
||||
@@ -61,12 +67,40 @@ public class Decorator extends Control {
|
||||
private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();
|
||||
private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();
|
||||
|
||||
private boolean playRestoreMinimizeAnimation = false;
|
||||
|
||||
public Decorator(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {
|
||||
if (playRestoreMinimizeAnimation && !iconified) {
|
||||
playRestoreMinimizeAnimation = false;
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Stage getPrimaryStage() {
|
||||
@@ -239,7 +273,29 @@ public class Decorator extends Control {
|
||||
}
|
||||
|
||||
public void minimize() {
|
||||
primaryStage.setIconified(true);
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
playRestoreMinimizeAnimation = true;
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> primaryStage.setIconified(true));
|
||||
timeline.play();
|
||||
} else {
|
||||
primaryStage.setIconified(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DecoratorAnimationProducer implements AnimationProducer {
|
||||
public final class DecoratorAnimationProducer implements AnimationProducer {
|
||||
@Override
|
||||
public void init(AnimationHandler handler) {
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
@@ -35,6 +38,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
@@ -42,11 +46,12 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -77,11 +82,32 @@ public class DecoratorController {
|
||||
private final Navigator navigator;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
private JFXDialogPane dialogPane;
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
});
|
||||
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
|
||||
|
||||
navigator = new Navigator();
|
||||
@@ -354,7 +380,7 @@ public class DecoratorController {
|
||||
return;
|
||||
}
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
dialogPane = new JFXDialogPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
decorator.capableDraggingWindow(dialog);
|
||||
@@ -371,21 +397,22 @@ public class DecoratorController {
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
DialogAware dialogAware = (DialogAware) node;
|
||||
if (dialog.isVisible()) {
|
||||
dialogAware.onDialogShown();
|
||||
} else {
|
||||
dialog.visibleProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
dialogAware.onDialogShown();
|
||||
observable.removeListener(this);
|
||||
}
|
||||
if (dialog.isVisible()) {
|
||||
dialog.requestFocus();
|
||||
if (node instanceof DialogAware)
|
||||
((DialogAware) node).onDialogShown();
|
||||
} else {
|
||||
dialog.visibleProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
dialog.requestFocus();
|
||||
if (node instanceof DialogAware)
|
||||
((DialogAware) node).onDialogShown();
|
||||
observable.removeListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,18 +424,21 @@ public class DecoratorController {
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
if (dialog != null) {
|
||||
dialogPane.pop(node);
|
||||
JFXDialogPane pane = dialogPane;
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
|
||||
dialog.setOnDialogClosed(e -> pane.pop(node));
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
|
||||
navigator.setDisable(false);
|
||||
} else {
|
||||
pane.pop(node);
|
||||
}
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,17 +187,20 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
buttonsContainer.setMaxHeight(40);
|
||||
{
|
||||
JFXButton btnHelp = new JFXButton();
|
||||
btnHelp.setGraphic(SVG.HELP_CIRCLE_OUTLINE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnHelp.setFocusTraversable(false);
|
||||
btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnHelp.getStyleClass().add("jfx-decorator-button");
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnMin.setFocusTraversable(false);
|
||||
btnMin.setGraphic(SVG.MINIMIZE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnMin.getStyleClass().add("jfx-decorator-button");
|
||||
btnMin.setOnAction(e -> skinnable.minimize());
|
||||
|
||||
JFXButton btnClose = new JFXButton();
|
||||
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnClose.setFocusTraversable(false);
|
||||
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnClose.getStyleClass().add("jfx-decorator-button");
|
||||
btnClose.setOnAction(e -> skinnable.close());
|
||||
|
||||
@@ -224,7 +227,8 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canBack) {
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setGraphic(SVG.BACK.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.setFocusTraversable(false);
|
||||
backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||
@@ -235,14 +239,15 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canClose) {
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.setFocusTraversable(false);
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
if (showCloseAsHome)
|
||||
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
|
||||
navLeft.getChildren().add(closeNavButton);
|
||||
}
|
||||
@@ -258,7 +263,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
if (titleNode == null) {
|
||||
titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> skinnable.getWidth() - 100 - navLeft.getWidth(),
|
||||
() -> skinnable.getWidth() - 150 - navLeft.getWidth(),
|
||||
skinnable.widthProperty(), navLeft.widthProperty()));
|
||||
} else {
|
||||
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
@@ -281,7 +286,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
HBox navRight = new HBox();
|
||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton refreshNavButton = new JFXButton();
|
||||
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
refreshNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
|
||||
@@ -60,7 +60,6 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
@@ -100,43 +99,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("game"));
|
||||
item.setLeftGraphic(wrap(SVG.GAMEPAD));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(newGameTab));
|
||||
item.setOnAction(e -> tab.select(newGameTab));
|
||||
})
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("modpack"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.PACK));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
|
||||
settingsItem.setOnAction(e -> tab.select(modpackTab));
|
||||
})
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("mods"));
|
||||
item.setLeftGraphic(wrap(SVG.PUZZLE));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab));
|
||||
item.setOnAction(e -> tab.select(modTab));
|
||||
})
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("resourcepack"));
|
||||
item.setLeftGraphic(wrap(SVG.TEXTURE_BOX));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab));
|
||||
item.setOnAction(e -> tab.select(resourcePackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("world"));
|
||||
item.setLeftGraphic(wrap(SVG.EARTH));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab));
|
||||
item.setOnAction(e -> selectTabIfCurseForgeAvailable(worldTab));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
}
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.STADIA_CONTROLLER)
|
||||
.addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACKAGE2)
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.EXTENSION)
|
||||
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE)
|
||||
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
graphic.setLeft(new TwoLineListItem(i18n("modpack.choose." + type), i18n("modpack.choose." + type + ".detail")));
|
||||
|
||||
SVGPath arrow = new SVGPath();
|
||||
arrow.setContent(SVG.ARROW_RIGHT.getPath());
|
||||
arrow.setContent(SVG.ARROW_FORWARD.getPath());
|
||||
BorderPane.setAlignment(arrow, Pos.CENTER);
|
||||
graphic.setRight(arrow);
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -59,6 +61,8 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -72,9 +76,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.isBlank;
|
||||
|
||||
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
|
||||
private final String gameVersion;
|
||||
@@ -90,18 +92,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
private final JFXCheckBox chkRelease;
|
||||
private final JFXCheckBox chkSnapshot;
|
||||
private final JFXCheckBox chkOld;
|
||||
private final JFXButton btnRefresh;
|
||||
private final HBox checkPane;
|
||||
private final ComponentList centrePane;
|
||||
private final StackPane center;
|
||||
|
||||
private final VersionList<?> versionList;
|
||||
private CompletableFuture<?> executor;
|
||||
|
||||
private final TransitionPane toolbarPane;
|
||||
private final HBox searchBar;
|
||||
private final JFXTextField searchField;
|
||||
private boolean isSearching = false;
|
||||
private final StringProperty queryString = new SimpleStringProperty();
|
||||
|
||||
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
||||
this.title = title;
|
||||
@@ -117,6 +115,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
this.setTop(hintPane);
|
||||
|
||||
root = new TransitionPane();
|
||||
BorderPane toolbarPane = new BorderPane();
|
||||
JFXButton btnRefresh;
|
||||
{
|
||||
spinner = new JFXSpinner();
|
||||
|
||||
@@ -126,7 +126,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
centrePane = new ComponentList();
|
||||
centrePane.getStyleClass().add("no-padding");
|
||||
{
|
||||
checkPane = new HBox();
|
||||
HBox checkPane = new HBox();
|
||||
checkPane.setSpacing(10);
|
||||
{
|
||||
chkRelease = new JFXCheckBox(i18n("version.game.releases"));
|
||||
@@ -139,24 +139,69 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkOld = new JFXCheckBox(i18n("version.game.old"));
|
||||
HBox.setMargin(chkOld, new Insets(10, 0, 10, 0));
|
||||
|
||||
HBox pane = new HBox();
|
||||
HBox.setHgrow(pane, Priority.ALWAYS);
|
||||
|
||||
btnRefresh = new JFXButton(i18n("button.refresh"));
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.setOnAction(e -> onRefresh());
|
||||
|
||||
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld, pane, btnRefresh);
|
||||
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld);
|
||||
}
|
||||
|
||||
list = new JFXListView<>();
|
||||
list.getStyleClass().add("jfx-list-view-float");
|
||||
VBox.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
TransitionPane rightToolbarPane = new TransitionPane();
|
||||
{
|
||||
HBox refreshPane = new HBox();
|
||||
refreshPane.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
btnRefresh = new JFXButton(i18n("button.refresh"));
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.setOnAction(e -> onRefresh());
|
||||
|
||||
JFXButton btnSearch = new JFXButton(i18n("search"));
|
||||
btnSearch.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnSearch.setGraphic(wrap(SVG.SEARCH.createIcon(Theme.blackFill(), -1)));
|
||||
|
||||
searchBar = new HBox();
|
||||
{
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 0));
|
||||
|
||||
JFXTextField searchField = new JFXTextField();
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
|
||||
JFXButton closeSearchBar = new JFXButton();
|
||||
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
|
||||
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1)));
|
||||
closeSearchBar.setOnAction(e -> {
|
||||
searchField.clear();
|
||||
rightToolbarPane.setContent(refreshPane, ContainerAnimations.FADE);
|
||||
});
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> queryString.set(searchField.getText()));
|
||||
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
});
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
btnSearch.setOnAction(e -> {
|
||||
rightToolbarPane.setContent(searchBar, ContainerAnimations.FADE);
|
||||
searchField.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
refreshPane.getChildren().setAll(new HBox(btnSearch, btnRefresh));
|
||||
rightToolbarPane.setContent(refreshPane, ContainerAnimations.NONE);
|
||||
}
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
toolbarPane.setLeft(checkPane);
|
||||
toolbarPane.setRight(rightToolbarPane);
|
||||
|
||||
centrePane.getContent().setAll(toolbarPane, list);
|
||||
}
|
||||
|
||||
center.getChildren().setAll(centrePane);
|
||||
@@ -192,18 +237,41 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkOld.setVisible(hasType);
|
||||
|
||||
if (hasType) {
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
centrePane.getContent().setAll(toolbarPane, list);
|
||||
} else {
|
||||
centrePane.getContent().setAll(list);
|
||||
}
|
||||
ComponentList.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
|
||||
InvalidationListener listener = o -> {
|
||||
List<RemoteVersion> versions = loadVersions();
|
||||
String query = queryString.get();
|
||||
if (!StringUtils.isBlank(query)) {
|
||||
Predicate<RemoteVersion> predicate;
|
||||
if (query.startsWith("regex:")) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(query.substring("regex:".length()));
|
||||
predicate = it -> pattern.matcher(it.getSelfVersion()).find();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = query.toLowerCase(Locale.ROOT);
|
||||
predicate = it -> it.getSelfVersion().toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
|
||||
versions = versions.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
list.getItems().setAll(versions);
|
||||
};
|
||||
chkRelease.selectedProperty().addListener(listener);
|
||||
chkSnapshot.selectedProperty().addListener(listener);
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
queryString.addListener(listener);
|
||||
|
||||
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)));
|
||||
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1)));
|
||||
|
||||
Holder<RemoteVersionListCell> lastCell = new Holder<>();
|
||||
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, libraryId));
|
||||
@@ -215,50 +283,6 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
callback.run();
|
||||
});
|
||||
|
||||
{
|
||||
toolbarPane = new TransitionPane();
|
||||
searchBar = new HBox();
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 5));
|
||||
searchField = new JFXTextField();
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
|
||||
JFXButton closeSearchBar = new JFXButton();
|
||||
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
|
||||
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)));
|
||||
closeSearchBar.setOnAction(e -> {
|
||||
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
|
||||
isSearching = false;
|
||||
searchField.clear();
|
||||
list.getItems().setAll(loadVersions());
|
||||
});
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
JFXButton searchButton = new JFXButton(i18n("search"));
|
||||
searchButton.getStyleClass().add("jfx-tool-bar-button");
|
||||
searchButton.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
|
||||
searchButton.setOnAction(e -> {
|
||||
toolbarPane.setContent(searchBar, ContainerAnimations.FADE);
|
||||
searchField.requestFocus();
|
||||
});
|
||||
|
||||
checkPane.getChildren().add(checkPane.getChildren().size() - 1, searchButton);
|
||||
|
||||
centrePane.getContent().remove(checkPane);
|
||||
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
|
||||
centrePane.getContent().add(0, toolbarPane);
|
||||
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> search());
|
||||
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
});
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -331,43 +355,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void onBack() { navigation.onPrev(true); }
|
||||
private void onBack() {
|
||||
navigation.onPrev(true);
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
|
||||
}
|
||||
|
||||
private void search() {
|
||||
isSearching = true;
|
||||
String queryString = searchField.getText();
|
||||
|
||||
if (isBlank(queryString)) {
|
||||
list.getItems().setAll(loadVersions());
|
||||
} else {
|
||||
list.getItems().clear();
|
||||
|
||||
Predicate<String> predicate;
|
||||
if (queryString.startsWith("regex:")) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
|
||||
predicate = s -> pattern.matcher(s).find();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
|
||||
predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
|
||||
for (RemoteVersion version : loadVersions()) {
|
||||
if (predicate.test(version.getSelfVersion())) {
|
||||
list.getItems().add(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteVersionListCell extends ListCell<RemoteVersion> {
|
||||
final IconedTwoLineListItem content = new IconedTwoLineListItem();
|
||||
final RipplerContainer ripplerContainer = new RipplerContainer(content);
|
||||
@@ -378,7 +373,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell, String libraryId) {
|
||||
this.lastCell = lastCell;
|
||||
if ("game".equals(libraryId)) {
|
||||
content.getExternalLinkButton().setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
|
||||
content.getExternalLinkButton().setGraphic(SVG.GLOBE_BOOK.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(content.getExternalLinkButton(), i18n("wiki.tooltip"));
|
||||
}
|
||||
|
||||
@@ -404,7 +399,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
|
||||
content.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
content.setSubtitle(null);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
|
||||
graphic.setLeft(new TwoLineListItem(i18n("modpack.type." + type), i18n("modpack.type." + type + ".export")));
|
||||
|
||||
SVGPath arrow = new SVGPath();
|
||||
arrow.setContent(SVG.ARROW_RIGHT.getPath());
|
||||
arrow.setContent(SVG.ARROW_FORWARD.getPath());
|
||||
BorderPane.setAlignment(arrow, Pos.CENTER);
|
||||
graphic.setRight(arrow);
|
||||
|
||||
|
||||
@@ -216,6 +216,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));
|
||||
|
||||
FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {
|
||||
|
||||
ObservableList<DiscoJavaRemoteVersion> versions;
|
||||
if (packageType == null
|
||||
|| currentJavaVersionList.get() == null
|
||||
@@ -224,8 +225,26 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
return;
|
||||
}
|
||||
|
||||
DiscoJavaRemoteVersion oldVersion = remoteVersionBox.getSelectionModel().getSelectedItem();
|
||||
remoteVersionBox.setItems(versions);
|
||||
|
||||
if (oldVersion != null) {
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (Objects.equals(version.getDistributionVersion(), oldVersion.getDistributionVersion())) {
|
||||
remoteVersionBox.getSelectionModel().select(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (version.getJdkVersion() == oldVersion.getJdkVersion()) {
|
||||
remoteVersionBox.getSelectionModel().select(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
|
||||
|
||||
@@ -181,6 +181,7 @@ public final class JavaInstallPage extends WizardSinglePage {
|
||||
pane.setLeft(new Label(name));
|
||||
|
||||
Label valueLabel = FXUtils.truncatedLabel(value, 60);
|
||||
FXUtils.copyOnDoubleClick(valueLabel);
|
||||
BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT);
|
||||
pane.setCenter(valueLabel);
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
revealButton.setOnAction(e -> control.onReveal());
|
||||
FXUtils.installFastTooltip(revealButton, i18n("java.reveal"));
|
||||
|
||||
@@ -275,12 +275,12 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
null
|
||||
));
|
||||
if (java.isManaged()) {
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.uninstall"));
|
||||
if (JavaRuntime.CURRENT_JAVA != null && java.getBinary().equals(JavaRuntime.CURRENT_JAVA.getBinary()))
|
||||
removeButton.setDisable(true);
|
||||
} else {
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.disable"));
|
||||
}
|
||||
|
||||
@@ -307,11 +307,11 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
|
||||
res.add(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, JavaManager::refresh));
|
||||
if (skinnable.onInstallJava != null) {
|
||||
res.add(createToolbarButton2(i18n("java.download"), SVG.DOWNLOAD_OUTLINE, skinnable.onInstallJava));
|
||||
res.add(createToolbarButton2(i18n("java.download"), SVG.DOWNLOAD, skinnable.onInstallJava));
|
||||
}
|
||||
res.add(createToolbarButton2(i18n("java.add"), SVG.PLUS, skinnable::onAddJava));
|
||||
res.add(createToolbarButton2(i18n("java.add"), SVG.ADD, skinnable::onAddJava));
|
||||
|
||||
JFXButton disableJava = createToolbarButton2(i18n("java.disabled.management"), SVG.VIEW_LIST, skinnable::onShowRestoreJavaPage);
|
||||
JFXButton disableJava = createToolbarButton2(i18n("java.disabled.management"), SVG.FORMAT_LIST_BULLETED, skinnable::onShowRestoreJavaPage);
|
||||
disableJava.disableProperty().bind(Bindings.isEmpty(ConfigHolder.globalConfig().getDisabledJava()));
|
||||
res.add(disableJava);
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
revealButton.setOnAction(e -> skinnable.onReveal());
|
||||
FXUtils.installFastTooltip(revealButton, i18n("java.reveal"));
|
||||
|
||||
@@ -169,7 +169,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
removeButton.setOnAction(e -> skinnable.onRemove());
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
|
||||
|
||||
@@ -177,7 +177,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
} else {
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
restoreButton.setOnAction(e -> skinnable.onRestore());
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
||||
@@ -63,67 +62,25 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
gameTab.initializeIfNeeded();
|
||||
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.type.global.manage"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
|
||||
settingsItem.setOnAction(e -> tab.select(gameTab));
|
||||
})
|
||||
.addNavigationDrawerItem(javaItem -> {
|
||||
javaItem.setTitle(i18n("java.management"));
|
||||
javaItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
|
||||
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
|
||||
javaItem.setOnAction(e -> tab.select(javaManagementTab));
|
||||
})
|
||||
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.launcher.general"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab));
|
||||
settingsItem.setOnAction(e -> tab.select(settingsTab));
|
||||
})
|
||||
.addNavigationDrawerItem(personalizationItem -> {
|
||||
personalizationItem.setTitle(i18n("settings.launcher.appearance"));
|
||||
personalizationItem.setLeftGraphic(wrap(SVG.STYLE_OUTLINE));
|
||||
personalizationItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab));
|
||||
personalizationItem.setOnAction(e -> tab.select(personalizationTab));
|
||||
})
|
||||
.addNavigationDrawerItem(downloadItem -> {
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
|
||||
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
|
||||
downloadItem.setOnAction(e -> tab.select(downloadTab));
|
||||
})
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(helpItem -> {
|
||||
helpItem.setTitle(i18n("help"));
|
||||
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
|
||||
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
|
||||
helpItem.setOnAction(e -> tab.select(helpTab));
|
||||
})
|
||||
.addNavigationDrawerItem(feedbackItem -> {
|
||||
feedbackItem.setTitle(i18n("feedback"));
|
||||
feedbackItem.setLeftGraphic(wrap(SVG.MESSAGE_ALERT_OUTLINE));
|
||||
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
|
||||
feedbackItem.setOnAction(e -> tab.select(feedbackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(aboutItem -> {
|
||||
aboutItem.setTitle(i18n("about"));
|
||||
aboutItem.setLeftGraphic(wrap(SVG.INFORMATION_OUTLINE));
|
||||
aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab));
|
||||
aboutItem.setOnAction(e -> tab.select(aboutTab));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
}
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER)
|
||||
.addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.LOCAL_CAFE)
|
||||
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, settingsTab, i18n("settings.launcher.general"), SVG.TUNE)
|
||||
.addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE)
|
||||
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD)
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP)
|
||||
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.FEEDBACK)
|
||||
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFO);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -28,13 +28,16 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -47,7 +50,6 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AnnouncementCard;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
@@ -102,23 +104,49 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
setPadding(new Insets(20));
|
||||
|
||||
if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) {
|
||||
AnnouncementCard announcementCard = null;
|
||||
|
||||
String title;
|
||||
String content;
|
||||
if (Metadata.isNightly()) {
|
||||
announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null);
|
||||
} else if (Metadata.isDev()) {
|
||||
announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane);
|
||||
title = i18n("update.channel.nightly.title");
|
||||
content = i18n("update.channel.nightly.hint");
|
||||
} else {
|
||||
title = i18n("update.channel.dev.title");
|
||||
content = i18n("update.channel.dev.hint");
|
||||
}
|
||||
|
||||
if (announcementCard != null) {
|
||||
VBox announcementBox = new VBox(16);
|
||||
announcementBox.getChildren().add(announcementCard);
|
||||
VBox announcementCard = new VBox();
|
||||
|
||||
announcementPane = new TransitionPane();
|
||||
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
|
||||
BorderPane titleBar = new BorderPane();
|
||||
titleBar.getStyleClass().add("title");
|
||||
titleBar.setLeft(new Label(title));
|
||||
|
||||
getChildren().add(announcementPane);
|
||||
}
|
||||
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20);
|
||||
hideNode.setCursor(Cursor.HAND);
|
||||
titleBar.setRight(hideNode);
|
||||
FXUtils.onClicked(hideNode, () -> {
|
||||
if (announcementPane != null) {
|
||||
if (Metadata.isDev()) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
}
|
||||
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
}
|
||||
});
|
||||
|
||||
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
|
||||
body.setLineSpacing(4);
|
||||
|
||||
announcementCard.getChildren().setAll(titleBar, body);
|
||||
announcementCard.setSpacing(16);
|
||||
announcementCard.getStyleClass().addAll("card", "announcement");
|
||||
|
||||
VBox announcementBox = new VBox(16);
|
||||
announcementBox.getChildren().add(announcementCard);
|
||||
|
||||
announcementPane = new TransitionPane();
|
||||
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
|
||||
|
||||
getChildren().add(announcementPane);
|
||||
}
|
||||
|
||||
updatePane = new StackPane();
|
||||
@@ -138,7 +166,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));
|
||||
{
|
||||
Label lblIcon = new Label();
|
||||
lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20, 20));
|
||||
lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20));
|
||||
|
||||
TwoLineListItem prompt = new TwoLineListItem();
|
||||
prompt.setSubtitle(i18n("update.bubble.subtitle"));
|
||||
@@ -150,7 +178,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
JFXButton closeUpdateButton = new JFXButton();
|
||||
closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10, 10));
|
||||
closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10));
|
||||
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
|
||||
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
|
||||
StackPane.setMargin(closeUpdateButton, new Insets(5));
|
||||
@@ -219,10 +247,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
menuButton.setOnAction(e -> onMenu());
|
||||
menuButton.setClip(new Rectangle(211, -100, 100, 200));
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.TRIANGLE.createIcon(Theme.foregroundFillBinding(), 10, 10);
|
||||
Node svg = SVG.ARROW_DROP_UP.createIcon(Theme.foregroundFillBinding(), 30);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(12);
|
||||
graphic.setTranslateX(6);
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(graphic);
|
||||
|
||||
@@ -297,13 +325,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
showUpdate.set(false);
|
||||
}
|
||||
|
||||
public void hideAnnouncementPane() {
|
||||
if (announcementPane != null) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||
return state;
|
||||
|
||||
@@ -204,7 +204,7 @@ public class PersonalizationPage extends StackPane {
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1, -1));
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
|
||||
clearButton.setOnAction(e -> config().setLauncherFontFamily(null));
|
||||
|
||||
hBox.getChildren().setAll(cboFont, clearButton);
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage,
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file),
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
|
||||
modpacks -> {
|
||||
File file = modpacks.get(0);
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
|
||||
i18n("install.modpack"));
|
||||
} else if (NBTHelper.isNBTFileByExtension(file)) {
|
||||
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
|
||||
try {
|
||||
Controllers.navigate(new NBTEditorPage(file));
|
||||
Controllers.navigate(new NBTEditorPage(file.toPath()));
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
|
||||
@@ -154,14 +154,14 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
|
||||
// third item in left sidebar
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
gameItem.setLeftGraphic(wrap(SVG.VIEW_LIST));
|
||||
gameItem.setLeftGraphic(wrap(SVG.FORMAT_LIST_BULLETED));
|
||||
gameItem.setActionButtonVisible(false);
|
||||
gameItem.setTitle(i18n("version.manage"));
|
||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
|
||||
// forth item in left sidebar
|
||||
AdvancedListItem downloadItem = new AdvancedListItem();
|
||||
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
|
||||
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD));
|
||||
downloadItem.setActionButtonVisible(false);
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
@@ -169,7 +169,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
|
||||
// fifth item in left sidebar
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
|
||||
launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
|
||||
launcherSettingsItem.setActionButtonVisible(false);
|
||||
launcherSettingsItem.setTitle(i18n("settings"));
|
||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
|
||||
@@ -123,7 +123,7 @@ public abstract class SettingsView extends StackPane {
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20, 20));
|
||||
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20));
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state;
|
||||
private final File file;
|
||||
private final Path file;
|
||||
private final NBTFileType type;
|
||||
|
||||
public NBTEditorPage(File file) throws IOException {
|
||||
private final BorderPane root = new BorderPane();
|
||||
|
||||
public NBTEditorPage(Path file) throws IOException {
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath())));
|
||||
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
|
||||
this.file = file;
|
||||
this.type = NBTFileType.ofFile(file);
|
||||
|
||||
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
throw new IOException("Unknown type of file " + file);
|
||||
}
|
||||
|
||||
setCenter(new ProgressIndicator());
|
||||
setContent(root);
|
||||
setLoading(true);
|
||||
|
||||
HBox actions = new HBox(8);
|
||||
actions.setPadding(new Insets(8));
|
||||
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
|
||||
actions.getChildren().setAll(saveButton, cancelButton);
|
||||
|
||||
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file)))
|
||||
.thenAcceptAsync(tree -> {
|
||||
setCenter(new NBTTreeView(tree));
|
||||
// setBottom(actions);
|
||||
}, Schedulers.javafx())
|
||||
.handleAsync((result, e) -> {
|
||||
if (e != null) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
Task.supplyAsync(() -> type.readAsTree(file))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
setLoading(false);
|
||||
root.setCenter(new NBTTreeView(result));
|
||||
} else {
|
||||
LOG.warning("Fail to open nbt file", exception);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
}
|
||||
return null;
|
||||
}, Schedulers.javafx());
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTFileType {
|
||||
COMPRESSED("dat", "dat_old") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
|
||||
fileInputStream.mark(3);
|
||||
byte[] header = new byte[3];
|
||||
if (fileInputStream.read(header) < 3) {
|
||||
@@ -42,25 +64,28 @@ public enum NBTFileType {
|
||||
},
|
||||
ANVIL("mca") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
public Tag read(Path file) throws IOException {
|
||||
return REGION.read(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
return REGION.readAsTree(file);
|
||||
}
|
||||
},
|
||||
REGION("mcr") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file, "r")) {
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
|
||||
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
|
||||
if (r.length() == 0) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
byte[] header = new byte[4096];
|
||||
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
|
||||
Inflater inflater = new Inflater();
|
||||
|
||||
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
|
||||
|
||||
r.readFully(header);
|
||||
for (int i = 0; i < 4096; i += 4) {
|
||||
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
|
||||
@@ -109,7 +134,7 @@ public enum NBTFileType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
|
||||
|
||||
for (Tag tag : ((ListTag) item.getValue())) {
|
||||
@@ -135,7 +160,11 @@ public enum NBTFileType {
|
||||
|
||||
static final NBTFileType[] types = values();
|
||||
|
||||
public static NBTFileType ofFile(File file) {
|
||||
public static boolean isNBTFileByExtension(Path file) {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
|
||||
public static NBTFileType ofFile(Path file) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
for (NBTFileType type : types) {
|
||||
for (String extension : type.extensions) {
|
||||
@@ -153,11 +182,11 @@ public enum NBTFileType {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
public abstract Tag read(File file) throws IOException;
|
||||
public abstract Tag read(Path file) throws IOException;
|
||||
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
|
||||
root.setName(file.getName());
|
||||
root.setName(file.getFileName().toString());
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class NBTHelper {
|
||||
private NBTHelper() {
|
||||
}
|
||||
|
||||
public static boolean isNBTFileByExtension(File file) {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
@@ -6,6 +23,9 @@ import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTTagType {
|
||||
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
|
||||
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
@@ -14,7 +31,10 @@ import java.util.EnumMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class NBTTreeView extends TreeView<Tag> {
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTTreeView extends TreeView<Tag> {
|
||||
|
||||
public NBTTreeView(NBTTreeView.Item tree) {
|
||||
this.setRoot(tree);
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true));
|
||||
|
||||
Node left = VersionPage.wrap(SVG.FOLDER_OUTLINE);
|
||||
Node left = VersionPage.wrap(SVG.FOLDER);
|
||||
root.setLeft(left);
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
btnRemove.setOnAction(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
versionId == null ? i18n("settings.advanced") : i18n("settings.advanced.title", versionId)
|
||||
));
|
||||
|
||||
this.getStyleClass().add("gray-background");
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
@@ -240,7 +242,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());
|
||||
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||
FXUtils.unbindEnum(cboRenderer);
|
||||
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
|
||||
@@ -60,7 +60,7 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
toolbar.setPickOnBounds(false);
|
||||
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.PLUS, skinnable::add));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.ADD, skinnable::add));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("button.remove"), SVG.DELETE, () -> {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
|
||||
|
||||
@@ -95,11 +95,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private Runnable retrySearch;
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository) {
|
||||
this(repository, null);
|
||||
}
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback) {
|
||||
this(repository, callback, false);
|
||||
this(repository, null, false);
|
||||
}
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
|
||||
@@ -55,9 +55,6 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -308,7 +305,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
ComponentList sublist = new ComponentList(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>(versions.size());
|
||||
for (RemoteMod.Version v: versions) {
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(v, control));
|
||||
}
|
||||
return items;
|
||||
@@ -388,20 +385,20 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||
content.setSubtitle(I18n.formatDateTime(dataItem.getDatePublished()));
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Alpha:
|
||||
content.getTags().add(i18n("mods.channel.alpha"));
|
||||
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
|
||||
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(Theme.blackFill(), 24));
|
||||
break;
|
||||
case Beta:
|
||||
content.getTags().add(i18n("mods.channel.beta"));
|
||||
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
|
||||
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(Theme.blackFill(), 24));
|
||||
break;
|
||||
case Release:
|
||||
content.getTags().add(i18n("mods.channel.release"));
|
||||
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
|
||||
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(Theme.blackFill(), 24));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -442,7 +439,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
private static final class ModVersion extends JFXDialogLayout {
|
||||
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
|
||||
this.setHeading(new HBox(new Label(i18n("mods.download.title", version.getName()))));
|
||||
boolean isModpack = selfPage.repository.getType() == RemoteModRepository.Type.MODPACK;
|
||||
|
||||
this.setHeading(new HBox(new Label(i18n(isModpack ? "modpack.download.title" : "mods.download.title", version.getName()))));
|
||||
|
||||
VBox box = new VBox(8);
|
||||
box.setPadding(new Insets(8));
|
||||
@@ -464,10 +463,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
this.setBody(box);
|
||||
|
||||
JFXButton downloadButton = new JFXButton(i18n("mods.install"));
|
||||
JFXButton downloadButton = new JFXButton(isModpack ? i18n("install.modpack") : i18n("mods.install"));
|
||||
downloadButton.getStyleClass().add("dialog-accept");
|
||||
downloadButton.setOnAction(e -> {
|
||||
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
if (isModpack || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.download(version);
|
||||
@@ -528,8 +527,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
|
||||
|
||||
public interface DownloadCallback {
|
||||
void download(Profile profile, @Nullable String version, RemoteMod.Version file);
|
||||
}
|
||||
|
||||
@@ -44,17 +44,17 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH_OUTLINE, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup),
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), () -> currentSkinnable.generateLaunchScript(), popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup),
|
||||
new IconedMenuItem(SVG.SETTINGS, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.PENCIL_OUTLINE, i18n("version.manage.rename"), () -> currentSkinnable.rename(), popup),
|
||||
new IconedMenuItem(SVG.COPY, i18n("version.manage.duplicate"), () -> currentSkinnable.duplicate(), popup),
|
||||
new IconedMenuItem(SVG.DELETE_OUTLINE, i18n("version.manage.remove"), () -> currentSkinnable.remove(), popup),
|
||||
new IconedMenuItem(SVG.EXPORT, i18n("modpack.export"), () -> currentSkinnable.export(), popup),
|
||||
new IconedMenuItem(SVG.EDIT, i18n("version.manage.rename"), () -> currentSkinnable.rename(), popup),
|
||||
new IconedMenuItem(SVG.FOLDER_COPY, i18n("version.manage.duplicate"), () -> currentSkinnable.duplicate(), popup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("version.manage.remove"), () -> currentSkinnable.remove(), popup),
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), () -> currentSkinnable.export(), popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.FOLDER_OUTLINE, i18n("folder.game"), () -> currentSkinnable.browse(), popup));
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.game"), () -> currentSkinnable.browse(), popup));
|
||||
return popup;
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
JFXButton btnUpgrade = new JFXButton();
|
||||
btnUpgrade.setOnAction(e -> skinnable.update());
|
||||
btnUpgrade.getStyleClass().add("toggle-icon4");
|
||||
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
btnLaunch.setOnAction(e -> skinnable.launch());
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
|
||||
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
|
||||
right.getChildren().add(btnLaunch);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(FXUtils.limitingSize(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
|
||||
right.getChildren().add(btnManage);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
addProfileItem.getStyleClass().add("navigation-drawer-item");
|
||||
addProfileItem.setTitle(i18n("profile.new"));
|
||||
addProfileItem.setActionButtonVisible(false);
|
||||
addProfileItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
|
||||
addProfileItem.setLeftGraphic(VersionPage.wrap(SVG.ADD_CIRCLE));
|
||||
addProfileItem.setOnAction(e -> Controllers.navigate(new ProfilePage(null)));
|
||||
|
||||
pane.setFitToWidth(true);
|
||||
@@ -87,26 +87,10 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(installNewGameItem -> {
|
||||
installNewGameItem.setTitle(i18n("install.new_game"));
|
||||
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
|
||||
installNewGameItem.setOnAction(e -> Versions.addNewGame());
|
||||
})
|
||||
.addNavigationDrawerItem(installModpackItem -> {
|
||||
installModpackItem.setTitle(i18n("install.modpack"));
|
||||
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK));
|
||||
installModpackItem.setOnAction(e -> Versions.importModpack());
|
||||
})
|
||||
.addNavigationDrawerItem(refreshItem -> {
|
||||
refreshItem.setTitle(i18n("button.refresh"));
|
||||
refreshItem.setLeftGraphic(VersionPage.wrap(SVG.REFRESH));
|
||||
refreshItem.setOnAction(e -> gameList.refreshList());
|
||||
})
|
||||
.addNavigationDrawerItem(globalManageItem -> {
|
||||
globalManageItem.setTitle(i18n("settings.type.global.manage"));
|
||||
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE));
|
||||
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
|
||||
});
|
||||
.addNavigationDrawerItem(i18n("install.new_game"), SVG.ADD_CIRCLE, Versions::addNewGame)
|
||||
.addNavigationDrawerItem(i18n("install.modpack"), SVG.PACKAGE2, Versions::importModpack)
|
||||
.addNavigationDrawerItem(i18n("button.refresh"), SVG.REFRESH, gameList::refreshList)
|
||||
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.SETTINGS, this::modifyGlobalGameSettings);
|
||||
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
|
||||
setLeft(pane, bottomLeftCornerList);
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
|
||||
return Collections.singletonList(
|
||||
createToolbarButton2(i18n("install.installer.install_offline"), SVG.PLUS, skinnable::installOffline)
|
||||
createToolbarButton2(i18n("install.installer.install_offline"), SVG.ADD, skinnable::installOffline)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,11 +141,11 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
// Toolbar Normal
|
||||
toolbarNormal.getChildren().setAll(
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createToolbarButton2(i18n("mods.add"), SVG.PLUS, skinnable::add),
|
||||
createToolbarButton2(i18n("mods.add"), SVG.ADD, skinnable::add),
|
||||
createToolbarButton2(i18n("folder.mod"), SVG.FOLDER_OPEN, skinnable::openModFolder),
|
||||
createToolbarButton2(i18n("mods.check_updates"), SVG.UPDATE, skinnable::checkUpdates),
|
||||
createToolbarButton2(i18n("download"), SVG.DOWNLOAD_OUTLINE, skinnable::download),
|
||||
createToolbarButton2(i18n("search"), SVG.MAGNIFY, () -> changeToolbar(searchBar))
|
||||
createToolbarButton2(i18n("download"), SVG.DOWNLOAD, skinnable::download),
|
||||
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar))
|
||||
);
|
||||
|
||||
// Toolbar Selecting
|
||||
@@ -290,7 +290,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
|
||||
StringBuilder message = new StringBuilder(localModFile.getFileName());
|
||||
if (isNotBlank(localModFile.getGameVersion()))
|
||||
message.append(", ").append(i18n("game.version")).append(": ").append(localModFile.getGameVersion());
|
||||
message.append(", ").append(i18n("mods.game.version")).append(": ").append(localModFile.getGameVersion());
|
||||
if (isNotBlank(localModFile.getAuthors()))
|
||||
message.append(", ").append(i18n("archive.author")).append(": ").append(localModFile.getAuthors());
|
||||
this.message = message.toString();
|
||||
@@ -398,6 +398,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
setHeading(titleContainer);
|
||||
|
||||
Label description = new Label(modInfo.getModInfo().getDescription().toString());
|
||||
FXUtils.copyOnDoubleClick(description);
|
||||
setBody(description);
|
||||
|
||||
if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {
|
||||
@@ -519,15 +520,15 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
setSelectable();
|
||||
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
|
||||
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
|
||||
infoButton.getStyleClass().add("toggle-icon4");
|
||||
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFORMATION_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
|
||||
container.getChildren().setAll(checkBox, content, restoreButton, revealButton, infoButton);
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
}
|
||||
|
||||
private Node createCustomIcon() {
|
||||
Node shape = SVG.PLUS_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 32, 32);
|
||||
Node shape = SVG.ADD_CIRCLE.createIcon(Theme.blackFill(), 32);
|
||||
shape.setMouseTransparent(true);
|
||||
RipplerContainer container = new RipplerContainer(shape);
|
||||
FXUtils.setLimitWidth(container, 36);
|
||||
|
||||
@@ -66,7 +66,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
{
|
||||
public VersionPage() {
|
||||
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
|
||||
@@ -77,7 +77,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
tab.select(versionSettingsTab);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
@@ -241,7 +242,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
AdvancedListItem versionSettingsItem = new AdvancedListItem();
|
||||
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
|
||||
versionSettingsItem.setTitle(i18n("settings.game"));
|
||||
versionSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
|
||||
versionSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
|
||||
versionSettingsItem.setActionButtonVisible(false);
|
||||
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
|
||||
versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab));
|
||||
@@ -249,7 +250,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
AdvancedListItem installerListItem = new AdvancedListItem();
|
||||
installerListItem.getStyleClass().add("navigation-drawer-item");
|
||||
installerListItem.setTitle(i18n("settings.tabs.installers"));
|
||||
installerListItem.setLeftGraphic(wrap(SVG.CUBE));
|
||||
installerListItem.setLeftGraphic(wrap(SVG.DEPLOYED_CODE));
|
||||
installerListItem.setActionButtonVisible(false);
|
||||
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
|
||||
installerListItem.setOnAction(e -> control.tab.select(control.installerListTab));
|
||||
@@ -257,7 +258,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
AdvancedListItem modListItem = new AdvancedListItem();
|
||||
modListItem.getStyleClass().add("navigation-drawer-item");
|
||||
modListItem.setTitle(i18n("mods.manage"));
|
||||
modListItem.setLeftGraphic(wrap(SVG.PUZZLE));
|
||||
modListItem.setLeftGraphic(wrap(SVG.EXTENSION));
|
||||
modListItem.setActionButtonVisible(false);
|
||||
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
|
||||
modListItem.setOnAction(e -> control.tab.select(control.modListTab));
|
||||
@@ -265,7 +266,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
AdvancedListItem worldListItem = new AdvancedListItem();
|
||||
worldListItem.getStyleClass().add("navigation-drawer-item");
|
||||
worldListItem.setTitle(i18n("world.manage"));
|
||||
worldListItem.setLeftGraphic(wrap(SVG.EARTH));
|
||||
worldListItem.setLeftGraphic(wrap(SVG.PUBLIC));
|
||||
worldListItem.setActionButtonVisible(false);
|
||||
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
|
||||
worldListItem.setOnAction(e -> control.tab.select(control.worldListTab));
|
||||
@@ -280,26 +281,26 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
PopupMenu browseList = new PopupMenu();
|
||||
JFXPopup browsePopup = new JFXPopup(browseList);
|
||||
browseList.getContent().setAll(
|
||||
new IconedMenuItem(SVG.GAMEPAD, i18n("folder.game"), () -> control.onBrowse(""), browsePopup),
|
||||
new IconedMenuItem(SVG.PUZZLE, i18n("folder.mod"), () -> control.onBrowse("mods"), browsePopup),
|
||||
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
|
||||
new IconedMenuItem(SVG.STADIA_CONTROLLER, i18n("folder.game"), () -> control.onBrowse(""), browsePopup),
|
||||
new IconedMenuItem(SVG.EXTENSION, i18n("folder.mod"), () -> control.onBrowse("mods"), browsePopup),
|
||||
new IconedMenuItem(SVG.SETTINGS, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
|
||||
new IconedMenuItem(SVG.TEXTURE, i18n("folder.resourcepacks"), () -> control.onBrowse("resourcepacks"), browsePopup),
|
||||
new IconedMenuItem(SVG.APPLICATION_OUTLINE, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
|
||||
new IconedMenuItem(SVG.MONITOR_SCREENSHOT, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
|
||||
new IconedMenuItem(SVG.EARTH, i18n("folder.saves"), () -> control.onBrowse("saves"), browsePopup),
|
||||
new IconedMenuItem(SVG.WB_SUNNY, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
|
||||
new IconedMenuItem(SVG.SCREENSHOT_MONITOR, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
|
||||
new IconedMenuItem(SVG.PUBLIC, i18n("folder.saves"), () -> control.onBrowse("saves"), browsePopup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("folder.logs"), () -> control.onBrowse("logs"), browsePopup)
|
||||
);
|
||||
|
||||
PopupMenu managementList = new PopupMenu();
|
||||
JFXPopup managementPopup = new JFXPopup(managementList);
|
||||
managementList.getContent().setAll(
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH_OUTLINE, i18n("version.launch.test"), control::testGame, managementPopup),
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), control::testGame, managementPopup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), control::generateLaunchScript, managementPopup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.PENCIL, i18n("version.manage.rename"), control::rename, managementPopup),
|
||||
new IconedMenuItem(SVG.COPY, i18n("version.manage.duplicate"), control::duplicate, managementPopup),
|
||||
new IconedMenuItem(SVG.DELETE_OUTLINE, i18n("version.manage.remove"), control::remove, managementPopup),
|
||||
new IconedMenuItem(SVG.EXPORT, i18n("modpack.export"), control::export, managementPopup),
|
||||
new IconedMenuItem(SVG.EDIT, i18n("version.manage.rename"), control::rename, managementPopup),
|
||||
new IconedMenuItem(SVG.FOLDER_COPY, i18n("version.manage.duplicate"), control::duplicate, managementPopup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("version.manage.remove"), control::remove, managementPopup),
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), control::export, managementPopup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), control::redownloadAssetIndex, managementPopup),
|
||||
new IconedMenuItem(null, i18n("version.manage.remove_assets"), control::clearAssets, managementPopup),
|
||||
@@ -308,25 +309,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
);
|
||||
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(upgradeItem -> {
|
||||
upgradeItem.setTitle(i18n("version.update"));
|
||||
upgradeItem.setLeftGraphic(wrap(SVG.UPDATE));
|
||||
.addNavigationDrawerItem(i18n("version.update"), SVG.UPDATE, control::updateGame, upgradeItem -> {
|
||||
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
||||
upgradeItem.setOnAction(e -> control.updateGame());
|
||||
})
|
||||
.addNavigationDrawerItem(testGameItem -> {
|
||||
testGameItem.setTitle(i18n("version.launch.test"));
|
||||
testGameItem.setLeftGraphic(wrap(SVG.ROCKET_LAUNCH_OUTLINE));
|
||||
testGameItem.setOnAction(e -> control.testGame());
|
||||
})
|
||||
.addNavigationDrawerItem(browseMenuItem -> {
|
||||
browseMenuItem.setTitle(i18n("settings.game.exploration"));
|
||||
browseMenuItem.setLeftGraphic(wrap(SVG.FOLDER_OUTLINE));
|
||||
.addNavigationDrawerItem(i18n("version.launch.test"), SVG.ROCKET_LAUNCH, control::testGame)
|
||||
.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OPEN, null, browseMenuItem -> {
|
||||
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
||||
})
|
||||
.addNavigationDrawerItem(managementItem -> {
|
||||
managementItem.setTitle(i18n("settings.game.management"));
|
||||
managementItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
|
||||
.addNavigationDrawerItem(i18n("settings.game.management"), SVG.MENU, null, managementItem -> {
|
||||
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
||||
});
|
||||
toolbar.getStyleClass().add("advanced-list-box-clear-padding");
|
||||
@@ -356,7 +346,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
public static Node wrap(SVG svg) {
|
||||
return wrap(svg.createIcon((Paint) null, 20, 20));
|
||||
return wrap(svg.createIcon((Paint) null, 20));
|
||||
}
|
||||
|
||||
public interface VersionLoadable {
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
@@ -65,6 +66,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
private static final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private static TimerTask memoryStatusUpdateTask;
|
||||
|
||||
private static void initMemoryStatusUpdateTask() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (memoryStatusUpdateTask != null)
|
||||
@@ -135,10 +137,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
rootPane.getStyleClass().add("card-list");
|
||||
|
||||
if (globalSetting) {
|
||||
HintPane skinHint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
skinHint.setText(i18n("settings.skin"));
|
||||
rootPane.getChildren().add(skinHint);
|
||||
|
||||
HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
Text text = new Text();
|
||||
text.textProperty().bind(BindingMapping.of(selectedVersion)
|
||||
@@ -190,6 +188,31 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
componentList = new ComponentList();
|
||||
componentList.setDepth(1);
|
||||
|
||||
if (!globalSetting) {
|
||||
BorderPane copyGlobalPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("settings.game.copy_global"));
|
||||
copyGlobalPane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
JFXButton copyAll = new JFXButton(i18n("settings.game.copy_global.copy_all"));
|
||||
copyAll.disableProperty().bind(modpack);
|
||||
copyGlobalPane.setRight(copyAll);
|
||||
copyAll.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
|
||||
Set<String> ignored = new HashSet<>(Arrays.asList(
|
||||
"usesGlobal",
|
||||
"versionIcon"
|
||||
));
|
||||
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
|
||||
}, null));
|
||||
copyAll.getStyleClass().add("jfx-button-border");
|
||||
BorderPane.setAlignment(copyAll, Pos.CENTER_RIGHT);
|
||||
}
|
||||
|
||||
componentList.getContent().add(copyGlobalPane);
|
||||
}
|
||||
|
||||
javaItem = new MultiFileItem<>();
|
||||
javaSublist = new ComponentSublist();
|
||||
javaSublist.getContent().add(javaItem);
|
||||
@@ -446,7 +469,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
showAdvancedSettingPane.setRight(button);
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(
|
||||
componentList.getContent().addAll(
|
||||
javaSublist,
|
||||
gameDirSublist,
|
||||
maxMemoryPane,
|
||||
@@ -528,11 +551,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty());
|
||||
gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty());
|
||||
FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty());
|
||||
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
|
||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||
chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());
|
||||
chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
FXUtils.unbindEnum(cboProcessPriority);
|
||||
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
|
||||
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
||||
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
||||
@@ -563,8 +586,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());
|
||||
gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());
|
||||
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
|
||||
FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty());
|
||||
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
|
||||
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
|
||||
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldBackupTask extends Task<Path> {
|
||||
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
|
||||
public WorldBackupTask(World world, Path backupsDir) {
|
||||
this.world = world;
|
||||
this.backupsDir = backupsDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
try (FileChannel lockChannel = world.lock()) {
|
||||
Files.createDirectories(backupsDir);
|
||||
String time = LocalDateTime.now().format(WorldBackupsPage.TIME_FORMATTER);
|
||||
String baseName = time + "_" + world.getFileName();
|
||||
Path backupFile = null;
|
||||
OutputStream outputStream = null;
|
||||
|
||||
int count;
|
||||
for (count = 0; count < 256; count++) {
|
||||
try {
|
||||
backupFile = backupsDir.resolve(baseName + (count == 0 ? "" : " " + count) + ".zip").toAbsolutePath();
|
||||
outputStream = Files.newOutputStream(backupFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
break;
|
||||
} catch (FileAlreadyExistsException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (outputStream == null)
|
||||
throw new IOException("Too many attempts");
|
||||
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(outputStream))) {
|
||||
String rootName = world.getFileName();
|
||||
Path rootDir = this.world.getFile();
|
||||
Files.walkFileTree(this.world.getFile(), new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
|
||||
if (path.endsWith("session.lock")) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
zipOutputStream.putNextEntry(new ZipEntry(rootName + "/" + rootDir.relativize(path).toString().replace('\\', '/')));
|
||||
Files.copy(path, zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setResult(backupFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
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.game.World;
|
||||
import org.jackhuang.hmcl.game.WorldLockedException;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
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.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.BackupInfo> implements DecoratorPage {
|
||||
static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
|
||||
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
private final Pattern backupFileNamePattern;
|
||||
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
|
||||
|
||||
public WorldBackupsPage(World world, Path backupsDir) {
|
||||
this.backupsDir = backupsDir;
|
||||
this.world = world;
|
||||
this.backupFileNamePattern = Pattern.compile("(?<datetime>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_" + Pattern.quote(world.getFileName()) + "( (?<count>[0-9]+))?\\.zip");
|
||||
this.state.set(State.fromTitle(i18n("world.backup.title", world.getWorldName())));
|
||||
loadBackups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
loadBackups();
|
||||
}
|
||||
|
||||
private void loadBackups() {
|
||||
setLoading(true);
|
||||
Task.supplyAsync(() -> {
|
||||
if (Files.isDirectory(backupsDir)) {
|
||||
try (Stream<Path> paths = Files.list(backupsDir)) {
|
||||
ArrayList<BackupInfo> result = new ArrayList<>();
|
||||
|
||||
paths.forEach(path -> {
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
|
||||
if (matcher.matches()) {
|
||||
LocalDateTime time = LocalDateTime.parse(matcher.group("datetime"), TIME_FORMATTER);
|
||||
int count = 0;
|
||||
|
||||
if (matcher.group("count") != null) {
|
||||
count = Integer.parseInt(matcher.group("count"));
|
||||
}
|
||||
|
||||
result.add(new BackupInfo(path, new World(path), time, count));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load backup file " + path, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
result.sort(Comparator.naturalOrder());
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
return new ArrayList<BackupInfo>();
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
this.setLoading(false);
|
||||
if (exception == null) {
|
||||
this.setItems(FXCollections.observableList(result));
|
||||
} else {
|
||||
LOG.warning("Failed to load backups", exception);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new WorldBackupsPageSkin();
|
||||
}
|
||||
|
||||
void createBackup() {
|
||||
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup")).thenApplyAsync(path -> {
|
||||
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
|
||||
if (!matcher.matches()) {
|
||||
throw new AssertionError("Wrong backup file name" + path);
|
||||
}
|
||||
|
||||
LocalDateTime time = LocalDateTime.parse(matcher.group("datetime"), TIME_FORMATTER);
|
||||
int count = 0;
|
||||
|
||||
if (matcher.group("count") != null) {
|
||||
count = Integer.parseInt(matcher.group("count"));
|
||||
}
|
||||
|
||||
return Pair.pair(path, new BackupInfo(path, new World(path), time, count));
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
WorldBackupsPage.this.getItems().add(result.getValue());
|
||||
WorldBackupsPage.this.getItems().sort(Comparator.naturalOrder());
|
||||
Controllers.dialog(i18n("world.backup.create.success", result.getKey()), null, MessageDialogPane.MessageType.INFO);
|
||||
} else if (exception instanceof WorldLockedException) {
|
||||
Controllers.dialog(i18n("world.backup.create.locked"), null, MessageDialogPane.MessageType.WARNING);
|
||||
} else {
|
||||
LOG.warning("Failed to create backup", exception);
|
||||
Controllers.dialog(i18n("world.backup.create.failed", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);
|
||||
}
|
||||
}), i18n("world.backup"), null);
|
||||
}
|
||||
|
||||
private final class WorldBackupsPageSkin extends ToolbarListPageSkin<WorldBackupsPage> {
|
||||
|
||||
WorldBackupsPageSkin() {
|
||||
super(WorldBackupsPage.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {
|
||||
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup"), SVG.ARCHIVE, skinnable::createBackup));
|
||||
}
|
||||
}
|
||||
|
||||
public final class BackupInfo extends Control implements Comparable<BackupInfo> {
|
||||
private final Path file;
|
||||
private final World backupWorld;
|
||||
private final LocalDateTime backupTime;
|
||||
private final int count;
|
||||
|
||||
public BackupInfo(Path file, World backupWorld, LocalDateTime backupTime, int count) {
|
||||
this.file = file;
|
||||
this.backupWorld = backupWorld;
|
||||
this.backupTime = backupTime;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public World getBackupWorld() {
|
||||
return backupWorld;
|
||||
}
|
||||
|
||||
public LocalDateTime getBackupTime() {
|
||||
return backupTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new BackupInfoSkin(this);
|
||||
}
|
||||
|
||||
void onReveal() {
|
||||
FXUtils.showFileInExplorer(file);
|
||||
}
|
||||
|
||||
void onDelete() {
|
||||
WorldBackupsPage.this.getItems().remove(this);
|
||||
Task.runAsync(() -> Files.delete(file)).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull WorldBackupsPage.BackupInfo that) {
|
||||
int c = this.backupTime.compareTo(that.backupTime);
|
||||
return c != 0 ? c : Integer.compare(this.count, that.count);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BackupInfoSkin extends SkinBase<BackupInfo> {
|
||||
|
||||
BackupInfoSkin(BackupInfo skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
World world = skinnable.getBackupWorld();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
{
|
||||
StackPane left = new StackPane();
|
||||
root.setLeft(left);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
left.getChildren().add(imageView);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||
}
|
||||
|
||||
{
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
root.setCenter(item);
|
||||
|
||||
if (skinnable.getBackupWorld().getWorldName() != null)
|
||||
item.setTitle(parseColorEscapes(skinnable.getBackupWorld().getWorldName()));
|
||||
item.setSubtitle(formatDateTime(skinnable.getBackupTime()) + (skinnable.count == 0 ? "" : " (" + skinnable.count + ")"));
|
||||
|
||||
if (world.getGameVersion() != null) item.getTags().add(world.getGameVersion());
|
||||
}
|
||||
|
||||
{
|
||||
HBox right = new HBox(8);
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
right.getChildren().add(btnReveal);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("world.reveal"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
|
||||
btnReveal.setOnAction(event -> skinnable.onReveal());
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
right.getChildren().add(btnDelete);
|
||||
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
|
||||
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
|
||||
}
|
||||
|
||||
getChildren().setAll(new RipplerContainer(root));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,22 +27,19 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.DoubleValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.NumberValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
@@ -55,30 +52,70 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
private final World world;
|
||||
private CompoundTag levelDat;
|
||||
|
||||
private final ObjectProperty<State> stateProperty;
|
||||
|
||||
private FileChannel sessionLockChannel;
|
||||
|
||||
@Override
|
||||
public boolean back() {
|
||||
closePage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePage() {
|
||||
if (sessionLockChannel != null) {
|
||||
try {
|
||||
sessionLockChannel.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to close session lock channel", e);
|
||||
}
|
||||
|
||||
sessionLockChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
public WorldInfoPage(World world) {
|
||||
this.world = world;
|
||||
this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName())));
|
||||
|
||||
this.getChildren().add(new ProgressIndicator());
|
||||
Task.supplyAsync(world::readLevelDat)
|
||||
this.getStyleClass().add("gray-background");
|
||||
|
||||
this.setLoading(true);
|
||||
Task.supplyAsync(this::loadWorldInfo)
|
||||
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
this.levelDat = result;
|
||||
loadWorldInfo();
|
||||
updateControls();
|
||||
setLoading(false);
|
||||
} else {
|
||||
LOG.warning("Failed to load level.dat", exception);
|
||||
this.getChildren().setAll(new Label(i18n("world.info.failed")));
|
||||
setFailedReason(i18n("world.info.failed"));
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
private void loadWorldInfo() {
|
||||
private CompoundTag loadWorldInfo() throws IOException {
|
||||
if (!Files.isDirectory(world.getFile()))
|
||||
throw new IOException("Not a valid world directory");
|
||||
|
||||
try {
|
||||
sessionLockChannel = world.lock();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
return world.readLevelDat();
|
||||
}
|
||||
|
||||
private boolean isReadOnly() {
|
||||
return sessionLockChannel == null;
|
||||
}
|
||||
|
||||
private void updateControls() {
|
||||
CompoundTag dataTag = levelDat.get("Data");
|
||||
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
|
||||
|
||||
@@ -86,7 +123,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
|
||||
getChildren().setAll(scrollPane);
|
||||
setContent(scrollPane);
|
||||
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setFillWidth(true);
|
||||
@@ -103,6 +140,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
Label worldNameLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(worldNameLabel);
|
||||
worldNameLabel.setText(world.getWorldName());
|
||||
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
|
||||
worldNamePane.setRight(worldNameLabel);
|
||||
@@ -115,6 +153,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
gameVersionPane.setLeft(label);
|
||||
|
||||
Label gameVersionLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(gameVersionLabel);
|
||||
gameVersionLabel.setText(world.getGameVersion());
|
||||
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
|
||||
gameVersionPane.setRight(gameVersionLabel);
|
||||
@@ -127,6 +166,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
randomSeedPane.setLeft(label);
|
||||
|
||||
Label randomSeedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(randomSeedLabel);
|
||||
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
|
||||
randomSeedPane.setRight(randomSeedLabel);
|
||||
|
||||
@@ -143,6 +183,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
lastPlayedPane.setLeft(label);
|
||||
|
||||
Label lastPlayedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(lastPlayedLabel);
|
||||
lastPlayedLabel.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));
|
||||
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
|
||||
lastPlayedPane.setRight(lastPlayedLabel);
|
||||
@@ -155,6 +196,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
timePane.setLeft(label);
|
||||
|
||||
Label timeLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(timeLabel);
|
||||
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
|
||||
timePane.setRight(timeLabel);
|
||||
|
||||
@@ -168,6 +210,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
OptionToggleButton allowCheatsButton = new OptionToggleButton();
|
||||
{
|
||||
allowCheatsButton.setTitle(i18n("world.info.allow_cheats"));
|
||||
allowCheatsButton.setDisable(isReadOnly());
|
||||
Tag tag = dataTag.get("allowCommands");
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
@@ -190,6 +233,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
OptionToggleButton generateFeaturesButton = new OptionToggleButton();
|
||||
{
|
||||
generateFeaturesButton.setTitle(i18n("world.info.generate_features"));
|
||||
generateFeaturesButton.setDisable(isReadOnly());
|
||||
Tag tag = worldGenSettings != null ? worldGenSettings.get("generate_features") : dataTag.get("MapFeatures");
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
@@ -216,6 +260,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
difficultyPane.setLeft(label);
|
||||
|
||||
JFXComboBox<Difficulty> difficultyBox = new JFXComboBox<>(Difficulty.items);
|
||||
difficultyBox.setDisable(isReadOnly());
|
||||
BorderPane.setAlignment(difficultyBox, Pos.CENTER_RIGHT);
|
||||
difficultyPane.setRight(difficultyBox);
|
||||
|
||||
@@ -258,6 +303,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
locationPane.setLeft(label);
|
||||
|
||||
Label locationLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(locationLabel);
|
||||
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
|
||||
locationPane.setRight(locationLabel);
|
||||
|
||||
@@ -276,6 +322,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
lastDeathLocationPane.setLeft(label);
|
||||
|
||||
Label lastDeathLocationLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(lastDeathLocationLabel);
|
||||
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
|
||||
lastDeathLocationPane.setRight(lastDeathLocationLabel);
|
||||
|
||||
@@ -297,6 +344,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
spawnPane.setLeft(label);
|
||||
|
||||
Label spawnLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(spawnLabel);
|
||||
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
|
||||
spawnPane.setRight(spawnLabel);
|
||||
|
||||
@@ -318,6 +366,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
playerGameTypePane.setLeft(label);
|
||||
|
||||
JFXComboBox<GameType> gameTypeBox = new JFXComboBox<>(GameType.items);
|
||||
gameTypeBox.setDisable(isReadOnly());
|
||||
BorderPane.setAlignment(gameTypeBox, Pos.CENTER_RIGHT);
|
||||
playerGameTypePane.setRight(gameTypeBox);
|
||||
|
||||
@@ -348,6 +397,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
healthPane.setLeft(label);
|
||||
|
||||
JFXTextField healthField = new JFXTextField();
|
||||
healthField.setDisable(isReadOnly());
|
||||
healthField.setPrefWidth(50);
|
||||
healthField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(healthField, Pos.CENTER_RIGHT);
|
||||
@@ -381,6 +431,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
foodLevelPane.setLeft(label);
|
||||
|
||||
JFXTextField foodLevelField = new JFXTextField();
|
||||
foodLevelField.setDisable(isReadOnly());
|
||||
foodLevelField.setPrefWidth(50);
|
||||
foodLevelField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(foodLevelField, Pos.CENTER_RIGHT);
|
||||
@@ -414,6 +465,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
xpLevelPane.setLeft(label);
|
||||
|
||||
JFXTextField xpLevelField = new JFXTextField();
|
||||
xpLevelField.setDisable(isReadOnly());
|
||||
xpLevelField.setPrefWidth(50);
|
||||
xpLevelField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(xpLevelField, Pos.CENTER_RIGHT);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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
|
||||
@@ -17,13 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
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;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -32,26 +27,17 @@ import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItem extends Control {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
public final class WorldListItem extends Control {
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
|
||||
public WorldListItem(World world) {
|
||||
public WorldListItem(World world, Path backupsDir) {
|
||||
this.world = world;
|
||||
|
||||
title.set(parseColorEscapes(world.getWorldName()));
|
||||
|
||||
subtitle.set(i18n("world.description", world.getFileName(), formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
|
||||
FXUtils.onClicked(this, this::showInfo);
|
||||
this.backupsDir = backupsDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,16 +45,8 @@ public class WorldListItem extends Control {
|
||||
return new WorldListItemSkin(this);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public void export() {
|
||||
@@ -85,11 +63,7 @@ public class WorldListItem extends Control {
|
||||
}
|
||||
|
||||
public void reveal() {
|
||||
try {
|
||||
FXUtils.openFolder(world.getFile().toFile());
|
||||
} catch (UnsupportedOperationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
FXUtils.openFolder(world.getFile().toFile());
|
||||
}
|
||||
|
||||
public void manageDatapacks() {
|
||||
@@ -104,4 +78,8 @@ public class WorldListItem extends Control {
|
||||
public void showInfo() {
|
||||
Controllers.navigate(new WorldInfoPage(world));
|
||||
}
|
||||
|
||||
public void showBackupPage() {
|
||||
Controllers.navigate(new WorldBackupsPage(world, backupsDir));
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
@@ -26,66 +25,96 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
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.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
|
||||
public WorldListItemSkin(WorldListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
World world = skinnable.getWorld();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setMouseTransparent(true);
|
||||
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);
|
||||
root.setCenter(center);
|
||||
|
||||
PopupMenu menu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("world.datapack"), skinnable::manageDatapacks, popup),
|
||||
new IconedMenuItem(SVG.EXPORT, i18n("world.export"), skinnable::export, popup),
|
||||
new IconedMenuItem(SVG.FOLDER_OUTLINE, i18n("world.reveal"), skinnable::reveal, popup));
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnManage = new JFXButton();
|
||||
btnManage.setOnAction(e -> popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), -1, -1));
|
||||
right.getChildren().add(btnManage);
|
||||
root.setRight(right);
|
||||
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
{
|
||||
StackPane left = new StackPane();
|
||||
FXUtils.installSlowTooltip(left, world.getFile().toString());
|
||||
root.setLeft(left);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
left.getChildren().add(imageView);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||
}
|
||||
|
||||
{
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
root.setCenter(item);
|
||||
if (world.getWorldName() != null)
|
||||
item.setTitle(parseColorEscapes(world.getWorldName()));
|
||||
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
|
||||
if (world.getGameVersion() != null)
|
||||
item.getTags().add(world.getGameVersion());
|
||||
if (world.isLocked())
|
||||
item.getTags().add(i18n("world.locked"));
|
||||
}
|
||||
|
||||
{
|
||||
HBox right = new HBox(8);
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
right.getChildren().add(btnReveal);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("world.reveal"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
|
||||
btnReveal.setOnAction(event -> skinnable.reveal());
|
||||
|
||||
JFXButton btnExport = new JFXButton();
|
||||
right.getChildren().add(btnExport);
|
||||
FXUtils.installFastTooltip(btnExport, i18n("world.export"));
|
||||
btnExport.getStyleClass().add("toggle-icon4");
|
||||
btnExport.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
|
||||
btnExport.setOnAction(event -> skinnable.export());
|
||||
|
||||
JFXButton btnBackup = new JFXButton();
|
||||
right.getChildren().add(btnBackup);
|
||||
FXUtils.installFastTooltip(btnBackup, i18n("world.backup"));
|
||||
btnBackup.getStyleClass().add("toggle-icon4");
|
||||
btnBackup.setGraphic(SVG.ARCHIVE.createIcon(Theme.blackFill(), -1));
|
||||
btnBackup.setOnAction(event -> skinnable.showBackupPage());
|
||||
|
||||
JFXButton btnDatapack = new JFXButton();
|
||||
right.getChildren().add(btnDatapack);
|
||||
FXUtils.installFastTooltip(btnDatapack, i18n("world.datapack"));
|
||||
btnDatapack.getStyleClass().add("toggle-icon4");
|
||||
btnDatapack.setGraphic(SVG.EXTENSION.createIcon(Theme.blackFill(), -1));
|
||||
btnDatapack.setOnAction(event -> skinnable.manageDatapacks());
|
||||
|
||||
JFXButton btnInfo = new JFXButton();
|
||||
right.getChildren().add(btnInfo);
|
||||
FXUtils.installFastTooltip(btnInfo, i18n("world.info"));
|
||||
btnInfo.getStyleClass().add("toggle-icon4");
|
||||
btnInfo.setGraphic(SVG.INFO.createIcon(Theme.blackFill(), -1));
|
||||
btnInfo.setOnAction(event -> skinnable.showInfo());
|
||||
}
|
||||
|
||||
getChildren().setAll(new RipplerContainer(root));
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.Node;
|
||||
@@ -37,17 +36,17 @@ import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
|
||||
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
|
||||
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
||||
|
||||
private Path savesDir;
|
||||
private Path backupsDir;
|
||||
private List<World> worlds;
|
||||
private Profile profile;
|
||||
private String id;
|
||||
@@ -62,7 +61,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
if (worlds != null)
|
||||
itemsProperty().setAll(worlds.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,36 +70,39 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
return new WorldListPageSkin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
this.id = id;
|
||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||
this.savesDir = profile.getRepository().getSavesDirectory(id);
|
||||
this.backupsDir = profile.getRepository().getBackupsDirectory(id);
|
||||
refresh();
|
||||
}
|
||||
|
||||
public CompletableFuture<?> refresh() {
|
||||
public void refresh() {
|
||||
if (profile == null || id == null)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return;
|
||||
|
||||
setLoading(true);
|
||||
return CompletableFuture
|
||||
.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
|
||||
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
|
||||
.thenApplyAsync(unused -> {
|
||||
try (Stream<World> stream = World.getWorlds(savesDir)) {
|
||||
return stream.parallel().collect(Collectors.toList());
|
||||
}
|
||||
})
|
||||
.whenCompleteAsync((result, exception) -> {
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
worlds = result;
|
||||
setLoading(false);
|
||||
if (exception == null)
|
||||
itemsProperty().setAll(result.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
if (exception == null) {
|
||||
itemsProperty().setAll(result.stream().filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
|
||||
} else {
|
||||
LOG.warning("Failed to load world list page", exception);
|
||||
}
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/938
|
||||
System.gc();
|
||||
}, Platform::runLater);
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void add() {
|
||||
@@ -126,7 +128,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.runAsync(() -> world.install(savesDir, name))
|
||||
.whenComplete(Schedulers.javafx(), () -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)), backupsDir));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
if (e instanceof FileAlreadyExistsException)
|
||||
@@ -155,7 +157,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
this.showAll.set(showAll);
|
||||
}
|
||||
|
||||
private class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
|
||||
private final class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
|
||||
|
||||
WorldListPageSkin() {
|
||||
super(WorldListPage.this);
|
||||
@@ -169,8 +171,8 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
|
||||
return Arrays.asList(chkShowAll,
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createToolbarButton2(i18n("world.add"), SVG.PLUS, skinnable::add),
|
||||
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD_OUTLINE, skinnable::download));
|
||||
createToolbarButton2(i18n("world.add"), SVG.ADD, skinnable::add),
|
||||
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD, skinnable::download));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -69,6 +70,12 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
} else {
|
||||
if (executor.getException() == null)
|
||||
return;
|
||||
|
||||
if (executor.getException() instanceof CancellationException) {
|
||||
onEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
String appendix = StringUtils.getStackTrace(executor.getException());
|
||||
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
|
||||
((WizardProvider.FailureCallback) settings.get("failure_callback")).onFail(settings, executor.getException(), () -> onEnd());
|
||||
|
||||
@@ -34,8 +34,8 @@ public final class Lazy<T> {
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
value = Objects.requireNonNull(supplier.get());
|
||||
if (supplier != null) {
|
||||
value = supplier.get();
|
||||
supplier = null;
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class ModuleHelper {
|
||||
|
||||
public static void addEnableNativeAccess(Class<?> clazzInModule) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
private ModuleHelper() {
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
@@ -60,7 +62,11 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
public static Version patchNative(Version version, String gameVersion, JavaRuntime javaVersion, VersionSetting settings) {
|
||||
public static Version patchNative(DefaultGameRepository repository,
|
||||
Version version, String gameVersion,
|
||||
JavaRuntime javaVersion,
|
||||
VersionSetting settings,
|
||||
List<String> javaArguments) {
|
||||
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
|
||||
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
|
||||
return version;
|
||||
@@ -83,7 +89,7 @@ public final class NativePatcher {
|
||||
final boolean useNativeOpenAL = settings.isUseNativeOpenAL();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
|
||||
&& GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
&& gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
|
||||
version = version.setLibraries(version.getLibraries().stream()
|
||||
.filter(library -> {
|
||||
@@ -124,6 +130,7 @@ public final class NativePatcher {
|
||||
return version;
|
||||
}
|
||||
|
||||
boolean lwjglVersionChanged = false;
|
||||
ArrayList<Library> newLibraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (!library.appliesToCurrentEnvironment())
|
||||
@@ -145,14 +152,33 @@ public final class NativePatcher {
|
||||
} else if (replacement != null) {
|
||||
LOG.info("Replace " + library.getName() + " with " + replacement.getName());
|
||||
newLibraries.add(replacement);
|
||||
|
||||
if ("org.lwjgl:lwjgl".equals(library.getName()) && !Objects.equals(library.getVersion(), replacement.getVersion())) {
|
||||
lwjglVersionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lwjglVersionChanged) {
|
||||
ModManager modManager = repository.getModManager(version.getId());
|
||||
try {
|
||||
for (LocalModFile mod : modManager.getMods()) {
|
||||
if ("sodium".equals(mod.getId())) {
|
||||
// https://github.com/CaffeineMC/sodium/issues/2561
|
||||
javaArguments.add("-Dsodium.checks.issue2561=false");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get mods", e);
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion) {
|
||||
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,9 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -74,13 +74,13 @@ public final class I18n {
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatDateTime(Instant instant) {
|
||||
public static String formatDateTime(TemporalAccessor time) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null) {
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format")).withZone(ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
return formatter.format(instant);
|
||||
return formatter.format(time);
|
||||
}
|
||||
|
||||
public static boolean hasKey(String key) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class ModuleHelper {
|
||||
|
||||
public static void addEnableNativeAccess(Class<?> clazzInModule) {
|
||||
Module module = clazzInModule.getModule();
|
||||
if (module.isNamed()) {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
|
||||
MethodHandle implAddEnableNativeAccess = lookup.findVirtual(Module.class, "implAddEnableNativeAccess", MethodType.methodType(Module.class));
|
||||
Module ignored = (Module) implAddEnableNativeAccess.invokeExact(module);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
} else {
|
||||
System.err.println("TODO: Add enable native access for anonymous modules is not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
private ModuleHelper() {
|
||||
}
|
||||
}
|
||||
@@ -152,15 +152,15 @@ fi
|
||||
|
||||
# Java not found
|
||||
|
||||
if [ "$_HMCL_OS" == "osx" ]; then
|
||||
_HMCL_DOWNLOAD_PAGE_OS="macos"
|
||||
else
|
||||
_HMCL_DOWNLOAD_PAGE_OS="$_HMCL_OS"
|
||||
fi
|
||||
|
||||
case "$_HMCL_OS-$_HMCL_ARCH" in
|
||||
windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-riscv64|linux-loongarch64|macos-x86_64|macos-arm64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_ARCH.html"
|
||||
windows-x86|windows-x86_64|windows-arm64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/windows/$_HMCL_ARCH.html"
|
||||
;;
|
||||
linux-x86|linux-x86_64|linux-arm64|linux-arm32|linux-riscv64|linux-loongarch64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/linux/$_HMCL_ARCH.html"
|
||||
;;
|
||||
osx-x86_64|osx-arm64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/macos/$_HMCL_ARCH.html"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -123,10 +123,11 @@ account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Pl
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
|
||||
account.methods.offline.name.invalid=Generally, game usernames are limited to English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
|
||||
account.methods.offline.name.invalid.tip=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.
|
||||
account.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\n\
|
||||
\n\
|
||||
\ · Valid usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Invalid usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\ · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Illegal: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\n\
|
||||
If you believe there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
|
||||
account.methods.offline.uuid=UUID
|
||||
@@ -141,18 +142,20 @@ account.move_to_global=Convert to Global Account\nThe account information will b
|
||||
account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL.
|
||||
account.not_logged_in=Not Logged in
|
||||
account.password=Password
|
||||
account.portable=Portable Account
|
||||
account.portable=Portable
|
||||
account.skin=Skin
|
||||
account.skin.file=Skin File
|
||||
account.skin.model=Model
|
||||
account.skin.model.default=Classic
|
||||
account.skin.model.slim=Slim
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Address
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.
|
||||
account.skin.type.local_file=Local Skin File
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=Upload/Edit Skin
|
||||
account.skin.upload.failed=Failed to upload skin.
|
||||
account.skin.invalid_skin=Invalid skin file.
|
||||
@@ -319,6 +322,8 @@ curse.sort.name=Name
|
||||
curse.sort.popularity=Popularity
|
||||
curse.sort.total_downloads=Total Downloads
|
||||
|
||||
datetime.format=MMM d, yyyy, h\:mm\:ss a
|
||||
|
||||
download=Download
|
||||
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
|
||||
download.code.404=File "%s" not found on the remote server.
|
||||
@@ -353,11 +358,11 @@ exception.access_denied=HMCL is unable to access the file "%s". It may be locked
|
||||
exception.artifact_malformed=Cannot verify the integrity of the downloaded files.
|
||||
exception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again.
|
||||
|
||||
extension.bat=Windows Batch File (.bat)
|
||||
extension.bat=Windows Batch File
|
||||
extension.mod=Mod File
|
||||
extension.png=Image File
|
||||
extension.ps1=Windows PowerShell Script (.ps1)
|
||||
extension.sh=Shell Script (.sh)
|
||||
extension.ps1=Windows PowerShell Script
|
||||
extension.sh=Shell Script
|
||||
|
||||
fatal.javafx.incompatible=Missing JavaFX environment.\n\
|
||||
Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\
|
||||
@@ -489,12 +494,12 @@ game.crash.reason.fabric_warnings=The Fabric Loader warned:\n\
|
||||
game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\
|
||||
\n\
|
||||
You can try backing up and deleting that file, then relaunching the game.
|
||||
game.crash.reason.file_changed=The game crashed because the file "%1$s" already exists.\n\
|
||||
game.crash.reason.file_changed=The game crashed because the file verification failed.\n\
|
||||
\n\
|
||||
You can try backing up and deleting that file, then relaunching the game.
|
||||
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource packs.\n\
|
||||
If you modified the Minecraft.jar file, you will need to revert the modification or re-download the game.
|
||||
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource/texture packs.\n\
|
||||
\n\
|
||||
Please disable the mods, shaders, or resource packs you are using and then try again.
|
||||
Please disable the mods, shaders, or resource/texture packs you are using and then try again.
|
||||
game.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\n\
|
||||
\n\
|
||||
Please try again after updating your graphics driver to the latest version.\n\
|
||||
@@ -513,7 +518,7 @@ game.crash.reason.install_mixinbootstrap=The game crashed because of the missing
|
||||
You can try installing <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem.
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\n\
|
||||
\n\
|
||||
Please navigate to<a href="https://optifine.net/downloads">the official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
|
||||
Please navigate to the <a href="https://optifine.net/downloads">official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
|
||||
\n\
|
||||
After testing, we believe that too high or too low OptiFine versions may cause crashes.
|
||||
game.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\n\
|
||||
@@ -529,7 +534,7 @@ game.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Stat
|
||||
Click <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">here</a> for more details.
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed because you installed too many mods and exceeded the game ID limit.\n\
|
||||
\n\
|
||||
Please try installing<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> or deleting some large mods.
|
||||
Please try installing <a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> or deleting some large mods.
|
||||
game.crash.reason.night_config_fixes=The game crashed because of some problems with Night Config.\n\
|
||||
\n\
|
||||
You can try installing the <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> mod, which may help you with this problem.\n\
|
||||
@@ -557,7 +562,7 @@ game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or O
|
||||
Please try updating Forge and OptiFine, or try using Java 8u251 or previous versions.
|
||||
game.crash.reason.forge_repeat_installation=The game crashed because of a duplicate Forge installation. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">This is a known problem</a>\n\
|
||||
\n\
|
||||
It is recommended to upload the log feedback to GitHub so that we can find more clues and resolve this problem.\n\
|
||||
It is recommended to submit feedback on GitHub along with this log so we can find more clues and resolve the issue.\n\
|
||||
\n\
|
||||
Currently you can uninstall Forge and reinstall it in "Edit Instance → Loaders".
|
||||
game.crash.reason.optifine_repeat_installation=The game crashed because of a duplicate OptiFine installation.\n\
|
||||
@@ -584,9 +589,9 @@ game.crash.reason.forge_found_duplicate_mods=The game crashed because of a dupli
|
||||
%1$s
|
||||
game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\
|
||||
\n\
|
||||
"%1$s" requires "%2$s".\n\
|
||||
"%1$s" requires mod "%2$s".\n\
|
||||
\n\
|
||||
You need to upgrade or downgrade %3$s before continuing.
|
||||
You need to upgrade or downgrade "%3$s" before continuing.
|
||||
game.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\n\
|
||||
\n\
|
||||
"%1$s" is incompatible with "%2$s".
|
||||
@@ -607,7 +612,7 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version)
|
||||
game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation. Please try updating Forge.
|
||||
game.crash.reason.no_class_def_found_error=The game cannot continue to run because of incomplete code.\n\
|
||||
\n\
|
||||
Your game instance is missing %1$s. This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\
|
||||
Your game instance is missing "%1$s". This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\
|
||||
\n\
|
||||
You may need to reinstall the game and all mods or ask someone for help.
|
||||
game.crash.reason.no_such_method_error=The game cannot continue to run because of incomplete code.\n\
|
||||
@@ -628,9 +633,9 @@ game.crash.reason.out_of_memory=The game crashed because the computer ran out of
|
||||
Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
|
||||
\n\
|
||||
If you still encounter these problems, you may need a better computer.
|
||||
game.crash.reason.resolution_too_high=The game crashed because the resource pack resolution is too high.\n\
|
||||
game.crash.reason.resolution_too_high=The game crashed because the resource/texture pack resolution is too high.\n\
|
||||
\n\
|
||||
You should switch to a resource pack with lower resolution or consider buying a better graphics card with more VRAM.
|
||||
You should switch to a resource/texture pack with lower resolution or consider buying a better graphics card with more VRAM.
|
||||
game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\
|
||||
\n\
|
||||
There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\
|
||||
@@ -649,7 +654,7 @@ game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of m
|
||||
Otherwise, if you believe this is caused by HMCL, please provide feedback to us.
|
||||
game.crash.title=Game Crashed
|
||||
game.directory=Game Path
|
||||
game.version=Game Version
|
||||
game.version=Game Instance
|
||||
|
||||
help=Help
|
||||
help.doc=Hello Minecraft! Launcher Documentation
|
||||
@@ -677,7 +682,7 @@ install.installer.choose=Choose Your %s Version
|
||||
install.installer.depend=Requires %s
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API won't function. If you do want to change the directory, you should reinstall it.
|
||||
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not function. If you do want to change the directory, you should reinstall it.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
@@ -846,21 +851,22 @@ modpack.completion=Downloading dependencies
|
||||
modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported.
|
||||
modpack.description=Modpack Description
|
||||
modpack.download=Download Modpacks
|
||||
modpack.download.title=Download Modpack - %1s
|
||||
modpack.enter_name=Enter a name for this modpack.
|
||||
modpack.export=Export as Modpack
|
||||
modpack.export.as=Export Modpack As...
|
||||
modpack.file_api=Modpack URL Prefix
|
||||
modpack.files.blueprints=BuildCraft Blueprints
|
||||
modpack.files.config=Mod Configs
|
||||
modpack.files.dumps=NEI Debug Output File
|
||||
modpack.files.config=Mod Configuration Files
|
||||
modpack.files.dumps=NEI Debug Output Files
|
||||
modpack.files.hmclversion_cfg=Launcher Configuration File
|
||||
modpack.files.liteconfig=LiteLoader Related Files
|
||||
modpack.files.mods=Mods
|
||||
modpack.files.mods.voxelmods=VoxelMods Options
|
||||
modpack.files.options_txt=Minecraft Options File
|
||||
modpack.files.optionsshaders_txt=Shaders Settings File
|
||||
modpack.files.options_txt=Minecraft Option File
|
||||
modpack.files.optionsshaders_txt=Shader Option File
|
||||
modpack.files.resourcepacks=Resource/Texture Packs
|
||||
modpack.files.saves=Game Saves
|
||||
modpack.files.saves=Worlds
|
||||
modpack.files.scripts=MineTweaker Configuration File
|
||||
modpack.files.servers_dat=Server List File
|
||||
modpack.install=Install Modpack %s
|
||||
@@ -898,7 +904,7 @@ modpack.wizard.step.2=Choose Files
|
||||
modpack.wizard.step.2.title=Choose files you wanted to add to the modpack.
|
||||
modpack.wizard.step.3=Modpack Type
|
||||
modpack.wizard.step.3.title=Choose modpack type you wanted to export as.
|
||||
modpack.wizard.step.initialization.exported_version=Game version to export
|
||||
modpack.wizard.step.initialization.exported_version=Game Instance to Export
|
||||
modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server)
|
||||
modpack.wizard.step.initialization.include_launcher=Include the launcher
|
||||
modpack.wizard.step.initialization.save=Export to...
|
||||
@@ -1009,6 +1015,7 @@ mods.download=Download Mod
|
||||
mods.download.title=Download Mod - %1s
|
||||
mods.download.recommend=Recommended Mod Version - Minecraft %1s
|
||||
mods.enable=Enable
|
||||
mods.game.version=Game Version
|
||||
mods.manage=Mods
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MCMod
|
||||
@@ -1040,9 +1047,16 @@ web.view_in_browser=View in browser
|
||||
|
||||
world=Worlds
|
||||
world.add=Add World
|
||||
world.backup=Backup World
|
||||
world.backup.create.failed=Failed to create backup.\n%s
|
||||
world.backup.create.locked=The world is currently in use. Please close the game and try again.
|
||||
world.backup.create.success=Successfully created a new backup: %s
|
||||
world.backup.delete=Delete this backup
|
||||
world.backup.reveal=Show in folder
|
||||
world.backup.title=World [%s] - Backups
|
||||
world.datapack=Manage Datapacks
|
||||
world.datapack.1_13=Only Minecraft 1.13 or later supports datapacks.
|
||||
world.description=%s | Last played on %s | Game Version: %s
|
||||
world.datetime=Last played on %s
|
||||
world.download=Download World
|
||||
world.export=Export the World
|
||||
world.export.title=Choose the directory for this exported world
|
||||
@@ -1054,6 +1068,7 @@ world.import.already_exists=This world already exists.
|
||||
world.import.choose=Choose world archive you want to import
|
||||
world.import.failed=Failed to import this world: %s
|
||||
world.import.invalid=Failed to parse the world.
|
||||
world.info=World Information
|
||||
world.info.title=World [%s] - Information
|
||||
world.info.basic=Basic Information
|
||||
world.info.allow_cheats=Allow Commands/Cheats
|
||||
@@ -1083,12 +1098,12 @@ world.info.player.xp_level=Experience Level
|
||||
world.info.random_seed=Seed
|
||||
world.info.time=Game Time
|
||||
world.info.time.format=%s days
|
||||
world.locked=In use
|
||||
world.manage=Worlds
|
||||
world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.reveal=Reveal in Explorer
|
||||
world.show_all=Show All
|
||||
world.time=h\:mm\:ss a, EEE, MMM d yyyy
|
||||
|
||||
profile=Game Directories
|
||||
profile.already_exists=This name already exists. Please use a different name.
|
||||
@@ -1204,6 +1219,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
|
||||
settings.custom=Custom
|
||||
|
||||
settings.game=Settings
|
||||
settings.game.copy_global=Copy from Global Settings
|
||||
settings.game.copy_global.copy_all=Copy All
|
||||
settings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!
|
||||
settings.game.current=Game
|
||||
settings.game.dimension=Resolution
|
||||
settings.game.exploration=Explore
|
||||
@@ -1240,7 +1258,7 @@ settings.launcher.font=Font
|
||||
settings.launcher.general=General
|
||||
settings.launcher.language=Language (Applies After Restart)
|
||||
settings.launcher.launcher_log.export=Export Launcher Logs
|
||||
settings.launcher.launcher_log.reveal=Reveal Logs in Explorer
|
||||
settings.launcher.launcher_log.reveal=Reveal Logs in File Manager
|
||||
settings.launcher.launcher_log.export.failed=Failed to export logs.
|
||||
settings.launcher.launcher_log.export.success=Logs have been exported to "%s".
|
||||
settings.launcher.log=Logging
|
||||
@@ -1253,7 +1271,7 @@ settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=No Proxy
|
||||
settings.launcher.proxy.password=Password
|
||||
settings.launcher.proxy.port=Port
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.socks=SOCKS
|
||||
settings.launcher.proxy.username=Username
|
||||
settings.launcher.theme=Theme
|
||||
settings.launcher.title_transparent=Transparent Titlebar
|
||||
@@ -1270,7 +1288,6 @@ settings.memory.lower_bound=Minimum Memory
|
||||
settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.show_log=Show Logs
|
||||
settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer.
|
||||
settings.tabs.installers=Loaders
|
||||
settings.take_effect_after_restart=Applies After Restart
|
||||
settings.type=Settings Type of Instance
|
||||
@@ -1296,12 +1313,16 @@ update.changelog=Changelog
|
||||
update.channel.dev=Beta
|
||||
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
|
||||
\n\
|
||||
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
|
||||
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
|
||||
\n\
|
||||
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
|
||||
update.channel.dev.title=Beta Channel Notice
|
||||
update.channel.nightly=Nightly
|
||||
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\
|
||||
\n\
|
||||
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
|
||||
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
|
||||
\n\
|
||||
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
|
||||
update.channel.nightly.title=Nightly Channel Notice
|
||||
update.channel.stable=Release
|
||||
update.checking=Checking for Updates
|
||||
|
||||
@@ -123,7 +123,8 @@ account.methods.microsoft.snapshot=Está utilizando una versión no oficial de H
|
||||
account.methods.microsoft.snapshot.website=Sitio web oficial
|
||||
account.methods.offline=Sin conexión
|
||||
account.methods.offline.name.special_characters=Se recomienda utilizar letras en inglés, números y guiones bajos
|
||||
account.methods.offline.name.invalid=Por lo general, los nombres de usuario de los juegos sólo admiten letras en inglés, números y guiones bajos, y no pueden superar los 16 caracteres de longitud.\n\
|
||||
account.methods.offline.name.invalid.tip=Se recomienda utilizar sólo letras en inglés, números y guiones bajos para el nombre de usuario, y la longitud no debe superar los 16 caracteres.
|
||||
account.methods.offline.name.invalid=Se recomienda utilizar sólo letras en inglés, números y guiones bajos para el nombre de usuario, y la longitud no debe superar los 16 caracteres.\n\
|
||||
\n\
|
||||
\ · Algunos nombres de usuario legítimos: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Algunos nombres de usuario ilegales: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
@@ -147,12 +148,14 @@ account.skin.file=Archivo de aspecto
|
||||
account.skin.model=Modelo
|
||||
account.skin.model.default=Clásico
|
||||
account.skin.model.slim=Slim
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing skin
|
||||
account.skin.type.csl_api.location=URL
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin.hint=Tienes que crear un jugador con el mismo nombre de jugador que tu cuenta sin conexión en el sitio web del proveedor de aspectos. Tu aspecto se ajustará ahora al aspecto asignado a tu jugador en el sitio web del proveedor de aspectos.
|
||||
account.skin.type.local_file=Archivo local de aspecto
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=Subir/editar aspecto
|
||||
account.skin.upload.failed=No se pudo subir el aspecto.
|
||||
account.skin.invalid_skin=Archivo de aspecto inválido.
|
||||
@@ -202,14 +205,14 @@ curse.category.4474=Ciencia Ficción
|
||||
curse.category.4481=Pequeño / Ligero
|
||||
curse.category.4483=Combate
|
||||
curse.category.4477=Mini Juego
|
||||
curse.category.4478=Quests
|
||||
curse.category.4478=Misiones
|
||||
curse.category.4484=Multijugador
|
||||
curse.category.4476=Exploración
|
||||
curse.category.4736=Skyblock
|
||||
curse.category.4475=Aventura y RPG
|
||||
curse.category.4487=FTB
|
||||
curse.category.4480=Basado en mapa
|
||||
curse.category.4479=Hardcore
|
||||
curse.category.4479=Extremo
|
||||
curse.category.4482=Extra grande
|
||||
curse.category.4472=Tecnología
|
||||
curse.category.4473=Magia
|
||||
@@ -223,10 +226,10 @@ curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utilidad y calidad de vida
|
||||
curse.category.5190=Calidad de vida
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.6814=Performance
|
||||
curse.category.6814=Rendimiento
|
||||
curse.category.6954=Integrated Dynamics
|
||||
curse.category.6484=Create
|
||||
curse.category.6821=Bug Fixes
|
||||
curse.category.6821=Corrección de errores
|
||||
curse.category.5191=Utilidad y calidad de vida
|
||||
curse.category.5192=Menú elegante
|
||||
curse.category.423=Mapa e información
|
||||
@@ -263,7 +266,7 @@ curse.category.4780=Fabric
|
||||
curse.category.424=Cosmética
|
||||
curse.category.406=Generación de mundos
|
||||
curse.category.435=Utilidades del servidor
|
||||
curse.category.411=Mobs
|
||||
curse.category.411=Criaturas
|
||||
curse.category.407=Biomas
|
||||
curse.category.427=Thermal Expansion
|
||||
curse.category.410=Dimensiones
|
||||
@@ -273,8 +276,8 @@ curse.category.4843=Automatización
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=Fontpack
|
||||
curse.category.5193=Datapack
|
||||
curse.category.5244=Paquete de fuentes
|
||||
curse.category.5193=Paquete de datos
|
||||
curse.category.399=Steampunk
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x y superiores
|
||||
@@ -319,6 +322,8 @@ curse.sort.name=Nombre
|
||||
curse.sort.popularity=Popularidad
|
||||
curse.sort.total_downloads=Descargas totales
|
||||
|
||||
datetime.format=d MMM yyyy, H\:mm\:ss
|
||||
|
||||
download=Descargar
|
||||
download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mundos.
|
||||
download.code.404=Archivo no encontrado en el servidor remoto: %s
|
||||
@@ -353,11 +358,11 @@ exception.access_denied=HMCL no puede acceder al archivo %s. Puede estar bloquea
|
||||
exception.artifact_malformed=No se puede verificar la integridad de los archivos descargados.
|
||||
exception.ssl_handshake=No se pudo establecer una conexión SSL porque falta el certificado SSL en la instalación actual de Java. Puede intentar abrir HMCL con otro Java y volver a intentarlo.
|
||||
|
||||
extension.bat=Archivo por lotes de Windows (.bat)
|
||||
extension.mod=Archivo mod.
|
||||
extension.bat=Archivo por lotes de Windows
|
||||
extension.mod=Archivo mod
|
||||
extension.png=Archivo de imagen
|
||||
extension.ps1=Script de Windows PowerShell (.ps1)
|
||||
extension.sh=Script de Shell Script (.sh)
|
||||
extension.ps1=Windows PowerShell Script
|
||||
extension.sh=Shell Script
|
||||
|
||||
fatal.javafx.incompatible=No se encontró un entorno JavaFX.\n\
|
||||
Hello Minecraft! Launcher no puede instalar automáticamente JavaFX con versiones de Java inferiores a la 11.\n\
|
||||
@@ -413,9 +418,9 @@ feedback.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios!
|
||||
|
||||
file=Archivo
|
||||
|
||||
folder.config=Configuraciones
|
||||
folder.config=Configuraciones de mod
|
||||
folder.game=Directorio de trabajo
|
||||
folder.logs=Logs
|
||||
folder.logs=Registros
|
||||
folder.mod=Mods
|
||||
folder.resourcepacks=Paquetes de recursos
|
||||
folder.shaderpacks=Paquetes de sombreados
|
||||
@@ -423,180 +428,237 @@ folder.saves=Mundos
|
||||
folder.screenshots=Capturas de pantalla
|
||||
|
||||
game=Juegos
|
||||
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, por favor haga clic en <b>"Exportar registros de errores"</b> y envíe el archivo exportado a otras personas para su análisis.
|
||||
game.crash.info=Información sobre el fallo
|
||||
game.crash.reason=Causa del fallo
|
||||
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen.
|
||||
game.crash.info=Información de colgado
|
||||
game.crash.reason=Motivo del colgado
|
||||
game.crash.reason.analyzing=Analizando...
|
||||
game.crash.reason.multiple=Se han detectado varias razones:\n\n
|
||||
game.crash.reason.block=El juego se ha bloqueado debido a un error.\n\
|
||||
\n\
|
||||
Puede tratar de eliminar este bloque utilizando MCEdit o eliminar el mod que lo ha añadido.\n\
|
||||
\n\
|
||||
Tipo de bloque: %1$s\n\
|
||||
Ubicación: %2$s
|
||||
game.crash.reason.bootstrap_failed=El juego se ha bloqueado debido al mod %1$s.\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.config=El juego se ha bloqueado porque un mod %1$s no puede analizar su archivo de configuración %2$s.
|
||||
game.crash.reason.debug_crash=El juego se ha bloqueado porque lo has activado manualmente.\n\
|
||||
\n\
|
||||
Entonces, probablemente sabes por qué.
|
||||
game.crash.reason.mixin_apply_mod_failed=El juego se ha bloqueado porque Mixin no puede aplicar el mod %1$s, no se puede continuar.\n\
|
||||
Puedes intentar eliminar o actualizar ese mod para solucionar el problema.
|
||||
game.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: %1$s.\n\
|
||||
\n\
|
||||
%2$s\n\
|
||||
\n\
|
||||
Cada mod sólo puede instalarse una vez, por favor, elimine el mod duplicado e inténtelo de nuevo.
|
||||
game.crash.reason.entity=El juego se ha bloqueado debido a una entidad.\n\
|
||||
\n\
|
||||
Puede intentar eliminar esta entidad utilizando MCEdit o eliminar el mod que la ha añadido.\n\
|
||||
\n\
|
||||
Tipo de bloque: %1$s\n\
|
||||
Ubicación: %2$s
|
||||
game.crash.reason.fabric_version_0_12=La versión de Fabric 0.12 o superior es incompatible con los mods instalados actualmente. Necesitas bajarlo a la versión 0.11.7.
|
||||
game.crash.reason.fabric_warnings=El cargador de mods de Fabric ha advertido:\n\
|
||||
%1$s
|
||||
game.crash.reason.file_already_exists=El juego se ha bloqueado porque el archivo %1$s ya existe.\n\
|
||||
\n\
|
||||
Puedes intentar hacer una copia de seguridad y eliminar ese archivo, y luego volver a ejecutar el juego.
|
||||
game.crash.reason.file_changed=El juego se ha bloqueado porque no ha pasado las comprobaciones de integridad.\n\
|
||||
\n\
|
||||
Si has modificado el jar de Minecraft, tendrás que deshacer los cambios, o volver a descargar el juego.
|
||||
game.crash.reason.gl_operation_failure=El juego se estrelló debido a algunos mods, shaders y/o resource packs.\n\
|
||||
\n\
|
||||
Por favor, desactiva los mods/shaders/resource packs que estés utilizando y vuelve a intentarlo.
|
||||
game.crash.reason.graphics_driver=El juego se ha bloqueado debido a un problema con el controlador gráfico.\n\
|
||||
\n\
|
||||
Por favor, inténtelo de nuevo después de actualizar su controlador gráfico a la última versión.\n\
|
||||
\n\
|
||||
Si tu ordenador tiene una tarjeta gráfica discreta, tienes que comprobar si el juego utiliza gráficos integrados/núcleo. Si es así, inicie el launcher utilizando su tarjeta gráfica discreta. Si el problema persiste, probablemente debería considerar la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\n\
|
||||
\n\
|
||||
Si utiliza su tarjeta gráfica integrada, tenga en cuenta que Minecraft 1.16.5 o anterior requiere <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 o más antiguo</a> para la serie de procesadores Intel(R) Core(TM) 3000 o anteriores.
|
||||
game.crash.reason.macos_failed_to_find_service_port_for_display=El juego actual no puede continuar debido a un fallo al inicializar la ventana de OpenGL en la plataforma de Apple silicon.\nPara este problema, HMCL no tiene soluciones directas por el momento. Intente abrir cualquier navegador y ponerlo en pantalla completa, luego vuelva a HMCL, inicie el juego y <b>vuelva rápidamente a la página del navegador</b> antes de que aparezca la ventana del juego, espere a que aparezca la ventana del juego y luego vuelva a la ventana del juego.
|
||||
game.crash.reason.illegal_access_error=El juego se bloquea por culpa de algún(os) mod(s).\n\
|
||||
\n\
|
||||
Si sabes: %1$s, puedes actualizar o eliminar los mods y volver a intentarlo.
|
||||
game.crash.reason.install_mixinbootstrap=El juego actual no se puede seguir ejecutando debido a la falta de MixinBootstrap. \nPuedes intentar instalar <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> para resolver el problema. Si se bloquea tras la instalación, prueba a añadir un "!" delante del nombre de archivo del módulo para intentar solucionar el problema. delante del nombre de archivo del módulo para intentar solucionar el problema.
|
||||
game.crash.reason.jdk_9=El juego no puede ejecutarse porque la versión de Java es demasiado nueva para esta instancia.\n\
|
||||
\n\
|
||||
Es necesario descargar e instalar Java 8 y seleccionarlo en la configuración de la instancia.
|
||||
game.crash.reason.need_jdk11=El juego actual no puede continuar ejecutándose debido a una versión inapropiada de la máquina virtual Java.\nDebes descargar e instalar Java 11 y configurar Java a una versión que comience con 11 en la configuración global (específica) del juego.
|
||||
game.crash.reason.jvm_32bit=El juego se ha bloqueado porque la asignación de memoria actual supera el límite de la VM Java de 32 bits.\n\
|
||||
\n\
|
||||
Si su sistema operativo es de 64 bits, instale y utilice una versión de 64 bits de Java. De lo contrario, es posible que tenga que volver a instalar un sistema operativo de 64 bits o conseguir un equipo moderno.\n\
|
||||
\n\
|
||||
O bien, puede desactivar la asignación automática de memoria y establecer el tamaño máximo de asignación de memoria a 1024MB o menos.
|
||||
game.crash.reason.loading_crashed_forge=El juego se ha bloqueado debido al mod %1$s (%2$s).\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.loading_crashed_fabric=El juego se ha bloqueado debido al mod %1$s.\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.memory_exceeded=El juego se ha bloqueado debido a que se ha asignado demasiada memoria para un pequeño archivo de paginación.\n\
|
||||
\n\
|
||||
Puede intentar desactivar la opción de asignación automática de memoria en la configuración y ajustar el valor hasta que el juego se inicie.\n\
|
||||
También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema.
|
||||
game.crash.reason.mod=El juego se ha bloqueado debido al mod %1$s.\n\
|
||||
\n\
|
||||
Puedes actualizar o eliminar el mod y volver a intentarlo.
|
||||
game.crash.reason.mod_resolution=El juego se ha bloqueado debido a un fallo en la resolución del mod.\n\
|
||||
\n\
|
||||
Fabric proporcionó los siguientes detalles\:\n\
|
||||
%1$s
|
||||
game.crash.reason.forgemod_resolution=El juego se ha bloqueado debido a un fallo en la resolución del mod.\n\
|
||||
\n\
|
||||
Forge proporcionó los siguientes detalles\:\n\
|
||||
%1$s
|
||||
game.crash.reason.forge_found_duplicate_mods=El juego no puede continuar debido a un problema de duplicación de mods. Forge proporciona la siguiente información: \n%1$s
|
||||
game.crash.reason.night_config_fixes=El juego actual no puede seguir ejecutándose debido a algunos problemas con Night Config. \nPuedes intentar instalar el mod <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a>, que puede ayudarte con este problema. \nPara obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
|
||||
game.crash.reason.mod_resolution_collection=El juego se ha bloqueado porque la versión del mod no es compatible.\n\
|
||||
\n\
|
||||
%1$s requiere %2$s.\n\
|
||||
\n\
|
||||
Necesitas actualizar o degradar %3$s antes de continuar.
|
||||
game.crash.reason.mod_resolution_conflict=El juego se ha bloqueado debido a un conflicto de mods.\n\
|
||||
\n\
|
||||
%1$s es incompatible con %2$s.
|
||||
game.crash.reason.mod_resolution_missing=El juego se ha bloqueado porque algunos mods de dependencia no están instalados.\n\
|
||||
\n\
|
||||
%1$s requiere mod: %2$s.\n\
|
||||
\n\
|
||||
Esto significa que tienes que descargar e instalar %2$s primero para continuar jugando.
|
||||
game.crash.reason.mod_resolution_missing_minecraft=El juego se ha bloqueado porque un mod es incompatible con la versión actual de Minecraft.\n\
|
||||
\n\
|
||||
%1$s requiere la versión de Minecraft %2$s.\n\
|
||||
\n\
|
||||
Si quieres jugar con esta versión del mod instalada, debes cambiar la versión de la instancia.\n\
|
||||
En caso contrario, debes instalar una versión compatible con esta versión de Minecraft.
|
||||
game.crash.reason.block=El juego se ha colgado debido a un bloqueo en el mundo.\n\
|
||||
\n\
|
||||
Puedes intentar eliminar este bloque usando MCEdit o borrando el mod que lo añadió.\n\
|
||||
\n\
|
||||
Tipo de bloque: %1$s\n\
|
||||
Ubicación: %2$s
|
||||
game.crash.reason.bootstrap_failed=El juego se ha colgado debido al mod "%1$s".\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.config=El juego se ha colgado debido a que el mod "%1$s" no ha podido analizar su archivo de configuración "%2$s".
|
||||
game.crash.reason.debug_crash=El juego se ha colgado porque lo has activado manualmente. Así que probablemente sepas por qué :)
|
||||
game.crash.reason.mixin_apply_mod_failed=El juego se ha colgado porque no se ha podido aplicar el mixin al mod "%1$s".\n\
|
||||
\n\
|
||||
Puedes probar a borrar o actualizar el mod para resolver el problema.
|
||||
game.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: "%1$s".\n\
|
||||
\n\
|
||||
%2$s\n\
|
||||
\n\
|
||||
Cada mod sólo puede instalarse una vez, por favor, elimine el mod duplicado e inténtelo de nuevo.
|
||||
game.crash.reason.entity=El juego se ha colgado debido a una entidad en el mundo.\n\
|
||||
\n\
|
||||
Puedes intentar eliminar la entidad usando MCEdit o borrando el mod que la añadió.\n\
|
||||
\n\
|
||||
Tipo de entidad: %1$s\n\
|
||||
Ubicación: %2$s
|
||||
game.crash.reason.fabric_version_0_12=La versión de Fabric Loader 0.12 o posterior es incompatible con los mods instalados actualmente. Necesitas bajarlo a la versión 0.11.7.
|
||||
game.crash.reason.fabric_warnings=El cargador de Fabric Loader ha advertido:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.file_already_exists=El juego se ha colgado porque el archivo "%1$s" ya existe.\n\
|
||||
\n\
|
||||
Puedes intentar hacer una copia de seguridad y eliminar ese archivo, y luego volver a ejecutar el juego.
|
||||
game.crash.reason.file_changed=El juego se ha colgado porque ha fallado la verificación de archivos.\n\
|
||||
\n\
|
||||
Si has modificado el jar de Minecraft, tendrás que deshacer los cambios, o volver a descargar el juego.
|
||||
game.crash.reason.gl_operation_failure=El juego se ha colgado por culpa de algunos mods, shaders o paquetes de recursos/texturas.\n\
|
||||
\n\
|
||||
Por favor, desactiva los mods, shaders o paquetes de recursos/texturas que estés utilizando y vuelve a intentarlo.
|
||||
game.crash.reason.graphics_driver=El juego se ha colgado debido a un problema con el controlador de gráficos.\n\
|
||||
\n\
|
||||
Por favor, inténtelo de nuevo después de actualizar su controlador gráfico a la última versión.\n\
|
||||
\n\
|
||||
Si tu ordenador tiene una tarjeta gráfica discreta, tienes que comprobar si el juego utiliza gráficos integrados/núcleo. Si es así, inicie el launcher utilizando su tarjeta gráfica discreta. Si el problema persiste, probablemente debería considerar la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\n\
|
||||
\n\
|
||||
Si utiliza su tarjeta gráfica integrada, tenga en cuenta que Minecraft 1.16.5 o anterior requiere <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 o más antiguo</a> para la serie de procesadores Intel(R) Core(TM) 3000 o anteriores.
|
||||
game.crash.reason.macos_failed_to_find_service_port_for_display=El juego no puede continuar debido a un fallo al inicializar la ventana OpenGL en la plataforma Apple silicon.\n\
|
||||
\n\
|
||||
Para este problema, HMCL no tiene soluciones directas por el momento. Intente abrir cualquier navegador y ponerlo en pantalla completa, luego vuelva a HMCL, inicie el juego y <b>vuelva rápidamente a la página del navegador</b> antes de que aparezca la ventana del juego, espere a que aparezca la ventana del juego y luego vuelva a la ventana del juego.
|
||||
game.crash.reason.illegal_access_error=El juego se ha colgado por culpa de algún(os) mod(s).\n\
|
||||
\n\
|
||||
Si sabes: %1$s, puedes actualizar o eliminar los mods y volver a intentarlo.
|
||||
game.crash.reason.install_mixinbootstrap=El juego se ha colgado debido a la falta de MixinBootstrap.\n\
|
||||
\n\
|
||||
Puedes intentar instalar <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> para resolver el problema. Si se cuelga después de la instalación, prueba a añadir un signo de exclamación (!) delante del nombre de archivo de este mod para intentar resolver el problema.
|
||||
game.crash.reason.jdk_9=El juego se ha colgado porque la versión de Java es demasiado nueva para esta instancia.\n\
|
||||
\n\
|
||||
Tienes que descargar e instalar Java 8 y elegirlo en "Config. Global/Específica de instancia → Java".
|
||||
game.crash.reason.need_jdk11=El juego se ha colgado debido a una versión inadecuada de Java.\n\
|
||||
\n\
|
||||
Debes descargar e instalar Java 11 y configurarlo en "Config. Global/Específica de instancia → Java".
|
||||
game.crash.reason.jvm_32bit=El juego se ha colgado porque la asignación de memoria actual excedía el límite de 32 bits JVM.\n\
|
||||
\n\
|
||||
Si su sistema operativo es de 64 bits, instale y utilice una versión de Java de 64 bits. De lo contrario, es posible que tenga que volver a instalar un sistema operativo de 64 bits o adquirir un ordenador más moderno.\n\
|
||||
\n\
|
||||
O bien, puede desactivar la opción "Asignar automáticamente" en "Config. Global/Específica de instancia → Memoria" y establecer el tamaño máximo de asignación de memoria en 1024 MB o menos.
|
||||
game.crash.reason.loading_crashed_forge=El juego se ha colgado debido al mod "%1$s" (%2$s).\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.loading_crashed_fabric=El juego se ha colgado debido al mod "%1$s".\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.memory_exceeded=El juego se ha colgado debido a que se ha asignado demasiada memoria a un pequeño archivo de paginación.\n\
|
||||
\n\
|
||||
Puedes probar a desactivar la opción "Asignar automáticamente" en "Config. Global/Específica de instancia → Memoria" y ajustar el valor hasta que se inicie el juego.\n\
|
||||
\n\
|
||||
También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema.
|
||||
game.crash.reason.mod=El juego se ha colgado debido al mod "%1$s".\n\
|
||||
\n\
|
||||
Puedes actualizar o eliminar el mod y volver a intentarlo.
|
||||
game.crash.reason.mod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\n\
|
||||
\n\
|
||||
Fabric proporcionó los siguientes detalles:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.forgemod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\n\
|
||||
\n\
|
||||
Forge/NeoForge proporcionó los siguientes detalles:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.forge_found_duplicate_mods=El juego se colgó debido a un problema de mod duplicado. Forge/NeoForge proporcionó la siguiente información:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.night_config_fixes=El juego se colgó debido a algunos problemas con Night Config.\n\
|
||||
\n\
|
||||
Puedes intentar instalar el mod <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a>, que puede ayudarte con este problema.\n\
|
||||
\n\
|
||||
Para obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
|
||||
game.crash.reason.mod_resolution_collection=El juego se colgó porque la versión del mod no es compatible.\n\
|
||||
\n\
|
||||
"%1$s" requiere mod "%2$s".\n\
|
||||
\n\
|
||||
Necesitas actualizar o degradar "%3$s" antes de continuar.
|
||||
game.crash.reason.mod_resolution_conflict=El juego se ha colgado debido a mods conflictivos.\n\
|
||||
\n\
|
||||
"%1$s" es incompatible con "%2$s".
|
||||
game.crash.reason.mod_resolution_missing=El juego se ha colgado porque algunos mods dependientes no estaban instalados.\n\
|
||||
\n\
|
||||
"%1$s" requiere mod "%2$s".\n\
|
||||
\n\
|
||||
Esto significa que tienes que descargar e instalar "%2$s" primero para continuar jugando.
|
||||
game.crash.reason.mod_resolution_missing_minecraft=El juego se ha colgado porque un mod es incompatible con la versión actual de Minecraft.\n\
|
||||
\n\
|
||||
"%1$s" requiere la versión de Minecraft %2$s.\n\
|
||||
\n\
|
||||
Si quieres jugar con esta versión del mod instalada, debes cambiar la versión del juego de tu instancia.\n\
|
||||
\n\
|
||||
En caso contrario, debes instalar una versión compatible con esta versión de Minecraft.
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s (Versión: %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (Cualquier versión)
|
||||
game.crash.reason.forge_repeat_installation=El juego actual no puede continuar ejecutándose debido a una instalación duplicada de Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">Este es un problema conocido</a>\nSe recomienda cargar los comentarios del registro en GitHub para que podamos encontrar más pistas y arreglar esta pregunta. \nActualmente puede ir a la instalación automática para desinstalar Forge y volver a instalarlo.
|
||||
game.crash.reason.optifine_repeat_installation=El juego actual no puede seguir ejecutándose debido a la instalación repetida de OptiFine. \nElimine OptiFine de la carpeta Mod o vaya a Gestión de juegos-Instalación automática para desinstalar OptiFine que se instala automáticamente.
|
||||
game.crash.reason.modmixin_failure=El juego actual no puede continuar ejecutándose debido a algunas fallas en la inyección de mods. \nEsto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \nPuede consultar el registro para ver si hay un mod incorrecto.
|
||||
game.crash.reason.mod_repeat_installation=El juego actual tiene múltiples Mods idénticos instalados repetidamente, y cada Mod solo puede aparecer una vez. Elimina los Mods repetidos y luego inicia el juego.
|
||||
game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s
|
||||
game.crash.reason.mod_resolution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto.
|
||||
game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
|
||||
game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan.
|
||||
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática.
|
||||
game.crash.reason.modlauncher_8=El juego se ha bloqueado porque tu versión actual de Forge no es compatible con tu instalación de Java. Por favor, intente actualizar Forge, o intente utilizar Java 8u312/11.0.13/17.0.1 o versiones anteriores.\n\
|
||||
\n\
|
||||
8u312 y anteriores\:\n\
|
||||
https://oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html\n\
|
||||
\n\
|
||||
11.0.13 y anteriores\:\n\
|
||||
https://oracle.com/java/technologies/javase/jdk11-archive-downloads.html\n\
|
||||
\n\
|
||||
17.0.1 y anteriores\:\n\
|
||||
https://oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
||||
game.crash.reason.forge_repeat_installation=El juego se ha colgado debido a una instalación duplicada de Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">Este es un problema conocido</a>\n\
|
||||
\n\
|
||||
Se recomienda enviar comentarios en GitHub junto con este registro para que podamos encontrar más pistas y resolver el problema.\n\
|
||||
\n\
|
||||
Actualmente puede desinstalar Forge y volver a instalarlo en "Editar Instancia → Cargadores".
|
||||
game.crash.reason.optifine_repeat_installation=El juego se ha colgado debido a una instalación duplicada de OptiFine.\n\
|
||||
\n\
|
||||
Elimine OptiFine del directorio "mods" o desinstálelo en "Editar instancia → Cargadores".
|
||||
game.crash.reason.modmixin_failure=El juego se ha colgado debido a que algunos mods no se han inyectado.\n\
|
||||
\n\
|
||||
Esto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \n\
|
||||
\n\
|
||||
Puede consultar el registro para ver si hay un mod incorrecto.
|
||||
game.crash.reason.mod_repeat_installation=El juego se ha colgado debido a los mods duplicados.\n\
|
||||
\n\
|
||||
Cada mod sólo puede instalarse una vez. Elimine el mod duplicado y vuelva a ejecutar el juego.
|
||||
game.crash.reason.forge_error=Forge/NeoForge puede haber proporcionado información errónea.\n\
|
||||
\n\
|
||||
Puede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\n\
|
||||
\n\
|
||||
Si no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.mod_resolution0=El juego se ha colgado debido a algunos problemas con el mod. Puede consultar el registro para ver si hay un mod incorrecto.
|
||||
game.crash.reason.java_version_is_too_high=El juego se ha colgado debido a que la versión de Java es demasiado nueva para seguir ejecutándose.\n\
|
||||
\n\
|
||||
Cambia la versión principal anterior de Java en "Config. Global/Específica de instancia → Java" y, a continuación, inicia el juego.\n\
|
||||
\n\
|
||||
Si no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java 8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java 17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
|
||||
game.crash.reason.mod_name=El juego se ha colgado debido a problemas con el nombre de archivo del mod.\n\
|
||||
\n\
|
||||
Los nombres de los archivos Mod deben usar sólo letras inglesas (A~Z, a~z), números (0~9), líneas horizontales(-), subrayado (_) y puntos (.) a media altura.\n\
|
||||
\n\
|
||||
Por favor, vaya al directorio "mods" y cambie todos los nombres de archivo de mods no conformes utilizando los caracteres conformes anteriores.
|
||||
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\n\
|
||||
\n\
|
||||
Reinstale o Forge/NeoForge em "Editar Instância → Carregadores".
|
||||
game.crash.reason.modlauncher_8=El juego se ha colgado debido a que la versión actual de Forge no es compatible con tu instalación de Java. Intenta actualizar Forge.
|
||||
game.crash.reason.no_class_def_found_error=El juego no puede ejecutarse debido a un código incompleto.\n\
|
||||
\n\
|
||||
A su instancia de juego le falta %1$s, esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\n\
|
||||
\n\
|
||||
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
|
||||
\n\
|
||||
A su instancia de juego le falta "%1$s", esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\n\
|
||||
\n\
|
||||
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
|
||||
game.crash.reason.no_such_method_error=El juego no puede ejecutarse debido a un código incompleto.\n\
|
||||
\n\
|
||||
Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\n\
|
||||
\n\
|
||||
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
|
||||
game.crash.reason.opengl_not_supported=El juego se ha bloqueado porque OpenGL no es compatible con su controlador de gráficos.\n\
|
||||
\n\
|
||||
Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\n\
|
||||
O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\n\
|
||||
\n\
|
||||
Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.
|
||||
game.crash.reason.openj9=El juego no puede ejecutarse en una máquina virtual OpenJ9. Por favor, cambia a una VM Java Hotspot en la configuración del juego y vuelve a ejecutar el juego. Si no tienes una, puedes descargar una en línea.
|
||||
game.crash.reason.out_of_memory=El juego se ha bloqueado porque se ha quedado sin memoria.\n\
|
||||
\n\
|
||||
Esto puede deberse a que no hay suficiente memoria disponible, o a que hay demasiados mods instalados. Puedes intentar solucionarlo aumentando la memoria asignada en la configuración del juego.\n\
|
||||
Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.
|
||||
game.crash.reason.resolution_too_high=El juego se ha bloqueado porque estás utilizando un resource packs cuya resolución de textura es demasiado alta.\n\
|
||||
\n\
|
||||
Deberías cambiar a un resource packs con menor resolución, o considerar la compra de una mejor tarjeta gráfica con más VRAM.
|
||||
game.crash.reason.shaders_mod=El juego actual no puede seguir ejecutándose porque tanto OptiFine como Shaders Mod están instalados. \nDebido a que OptiFine ha integrado las funciones de Shaders Mod, simplemente elimine Shaders Mod.
|
||||
game.crash.reason.rtss_forest_sodium=El juego actual se bloquea debido a una incompatibilidad entre RivaTuner Statistics Server (RTSS) y Sodium, lo que provoca el fallo del juego. Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
|
||||
game.crash.reason.stacktrace=El motivo del fallo es desconocido. Puede ver los detalles haciendo clic en el botón "Registros".\n\
|
||||
\n\
|
||||
Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo.\n\
|
||||
\n\
|
||||
%s
|
||||
game.crash.reason.too_old_java=El juego se ha bloqueado porque estás utilizando una versión histórica de Java VM..\n\
|
||||
\n\
|
||||
Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
|
||||
game.crash.reason.unknown=No somos capaces de averiguar por qué el juego se estrelló, por favor refiérase a los registros del juego.\n\
|
||||
\n\
|
||||
<b>Cuando pidas ayuda a otra persona, por favor, comparte con ella el registro completo del juego y el archivo de informe de fallos relacionado.</b>\n\
|
||||
\n\
|
||||
¿Esta caída es causada por un problema común que casi todo el mundo tiene? Puedes proporcionar tus registros en un nuevo tema de GitHub para mejorar el launcher.
|
||||
\n\
|
||||
Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\n\
|
||||
\n\
|
||||
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
|
||||
game.crash.reason.opengl_not_supported=El juego se ha colgado porque tu controlador gráfico no es compatible con OpenGL.\n\
|
||||
\n\
|
||||
Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\n\
|
||||
O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\n\
|
||||
\n\
|
||||
Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.
|
||||
game.crash.reason.openj9=El juego no puede ejecutarse en una JVM OpenJ9. Por favor, cambia a un Java que utilice la JVM Hotspot en "Config. Global/Específica de instancia → Java" y vuelve a ejecutar el juego. Si no dispones de una, puedes descargarla.
|
||||
game.crash.reason.out_of_memory=El juego se ha colgado porque el ordenador se ha quedado sin memoria.\n\
|
||||
\n\
|
||||
Puede que no haya suficiente memoria disponible o que haya demasiados mods instalados. Puedes intentar resolverlo aumentando la memoria asignada en "Config. Global/Específica de instancia → Memoria".\n\
|
||||
\n\
|
||||
Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.
|
||||
game.crash.reason.resolution_too_high=El juego se ha colgado porque la resolución del paquete de recursos/texturas es demasiado alta.\n\
|
||||
\n\
|
||||
Deberías cambiar a un paquete de recursos/texturas con menor resolución o considerar la compra de una tarjeta gráfica mejor con más VRAM.
|
||||
game.crash.reason.shaders_mod=El juego se ha colgado porque OptiFine y Shaders mod están instalados al mismo tiempo.\n\
|
||||
\n\
|
||||
Simplemente elimine el mod Shader porque OptiFine tiene soporte incorporado para shaders.
|
||||
game.crash.reason.rtss_forest_sodium=El juego se colgó porque el RivaTuner Statistical Server (RTSS) no es compatible con Sodium.\n\
|
||||
\n\
|
||||
Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
|
||||
game.crash.reason.stacktrace=El motivo del cuelgue es desconocido. Puedes ver los detalles haciendo clic en el botón "Registros".\n\
|
||||
\n\
|
||||
Hay algunas palabras clave que pueden contener algunos nombres de mods. Puedes buscarlas en Internet para averiguar el problema tú mismo.\n\
|
||||
\n\
|
||||
%s
|
||||
game.crash.reason.too_old_java=El juego se ha colgado porque estás utilizando una versión de Java VM obsoleta.\n\
|
||||
\n\
|
||||
Tienes que cambiar a una versión de Java más reciente (%1$s) en "Config. Global/Específica de instancia → Java" y, a continuación, volver a ejecutar el juego. Puede descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">aquí</a>.
|
||||
game.crash.reason.unknown=No somos capaces de averiguar por qué se ha colgado el juego. Por favor, refiérase a los registros del juego.
|
||||
game.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque faltan bibliotecas: %1$s.\n\
|
||||
\n\
|
||||
Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\
|
||||
Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\
|
||||
De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.
|
||||
game.crash.title=Juego interrumpido
|
||||
\n\
|
||||
Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\
|
||||
\n\
|
||||
Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\
|
||||
\n\
|
||||
De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.
|
||||
game.crash.reason.mac_jdk_8u261=El juego se ha colgado porque tu versión actual de Forge u OptiFine es incompatible con tu instalación de Java.\n\
|
||||
\n\
|
||||
Por favor, intente actualizar Forge y OptiFine, o intente utilizar Java 8u251 o versiones anteriores.
|
||||
game.crash.reason.mod_files_are_decompressed=El juego se ha colgado porque se ha extraído el archivo del mod.\n\
|
||||
\n\
|
||||
Por favor, ¡pon el archivo mod completo directamente en el directorio mod!\n\
|
||||
\n\
|
||||
Si la extracción provoca errores en el juego, elimine el mod extraído en el directorio de mods y, a continuación, inicie el juego.
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=Es posible que el juego no siga ejecutándose debido al OptiFine.\n\
|
||||
\n\
|
||||
Este problema solo se produce en una versión específica de OptiFine. Puede probar a cambiar la versión de OptiFine en "Editar instancia → Cargadores".
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=El juego se ha colgado porque OptiFine no es compatible con la instalación actual de Forge.\n\
|
||||
\n\
|
||||
Por favor, navegue hasta la <a href="https://optifine.net/downloads">página oficial de OptiFine</a>, compruebe si la versión de Forge es compatible con OptiFine, y reinstale la instancia en estricta conformidad con la versión correspondiente, o cambie la versión de OptiFine en "Editar Instancia → Cargadores".\n\
|
||||
\n\
|
||||
Tras realizar pruebas, creemos que las versiones de OptiFine demasiado altas o demasiado bajas pueden provocar fallos.
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=El juego se ha colgado porque has instalado demasiados mods y has superado el límite de ID del juego.\n\
|
||||
\n\
|
||||
Por favor, intenta instalar <a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> o eliminar algunos mods grandes.
|
||||
game.crash.title=Juego colgado
|
||||
game.directory=Ruta del juego
|
||||
game.version=Versión del juego
|
||||
game.version=Instancia del juego
|
||||
|
||||
help=Ayuda
|
||||
help.doc=Documentación de Hello Minecraft! Launcher
|
||||
@@ -678,7 +740,7 @@ java.install.failed.invalid=Este archivo no es un paquete de instalación de Jav
|
||||
java.install.failed.unsupported_platform=Este Java no es compatible con la plataforma actual, por lo que no puede instalarse.
|
||||
java.install.name=Nombre
|
||||
java.install.warning.invalid_character=Carácter ilegal en el nombre
|
||||
java.reveal=Revelar el directorio de Java
|
||||
java.reveal=Abrir el directorio Java
|
||||
java.uninstall=Desinstalar Java
|
||||
java.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer!
|
||||
|
||||
@@ -738,7 +800,7 @@ launcher.background=Imagen de fondo
|
||||
launcher.background.choose=Elige una imagen de fondo
|
||||
launcher.background.classic=Clásico
|
||||
launcher.background.default=Por defecto
|
||||
launcher.background.default.tooltip=O "background.png/.jpg/.gif/.webp", o imágenes en la carpeta "bg"
|
||||
launcher.background.default.tooltip=O "background.png/.jpg/.gif/.webp" y las imágenes en el directorio "bg".
|
||||
launcher.background.network=Desde la URL
|
||||
launcher.background.translucent=Translúcido
|
||||
launcher.cache_directory=Directorio de la caché
|
||||
@@ -761,7 +823,7 @@ logwindow.terminate_game=Terminar proceso
|
||||
logwindow.title=Registro
|
||||
logwindow.help=Puede ir a la comunidad HMCL y encontrar a otros para ayudar
|
||||
logwindow.autoscroll=Desplazamiento automático
|
||||
logwindow.export_game_crash_logs=Exportar registros de errores
|
||||
logwindow.export_game_crash_logs=Exportar registros de colgado
|
||||
logwindow.export_dump=Exportar volcado de pila de juegos
|
||||
logwindow.export_dump.no_dependency=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a nuestro Discord o grupo QQ para obtener ayuda.
|
||||
|
||||
@@ -793,13 +855,14 @@ modpack.completion=Descargando dependencias
|
||||
modpack.desc=Describa su modpack, incluyendo una introducción y probablemente algún registro de cambios. Actualmente se admiten Markdown e imágenes desde URL.
|
||||
modpack.description=Descripción
|
||||
modpack.download=Descargar Modpacks
|
||||
modpack.download.title=Descargar Modpack - %1s
|
||||
modpack.enter_name=Introduzca un nombre para este modpack.
|
||||
modpack.export=Exportar modpack
|
||||
modpack.export=Exportar como modpack
|
||||
modpack.export.as=Exportar Modpack como...
|
||||
modpack.file_api=Prefijo de la URL del modpack
|
||||
modpack.files.blueprints=Plantillas de BuildCraft
|
||||
modpack.files.config=Archivo de configuración del Mod
|
||||
modpack.files.dumps=Archivo de salida de depuración NEI
|
||||
modpack.files.config=Archivos de configuración del mod
|
||||
modpack.files.dumps=Archivos de salida de depuración NEI
|
||||
modpack.files.hmclversion_cfg=Archivo de configuración del launcher
|
||||
modpack.files.liteconfig=Archivos relacionados con LiteLoader
|
||||
modpack.files.mods=Mods
|
||||
@@ -807,7 +870,7 @@ modpack.files.mods.voxelmods=Opciones de VoxelMods
|
||||
modpack.files.options_txt=Archivo de configuración de Minecraft
|
||||
modpack.files.optionsshaders_txt=Archivo de configuración de shaders
|
||||
modpack.files.resourcepacks=Paquetes de recursos/texturas
|
||||
modpack.files.saves=Mundos de juego
|
||||
modpack.files.saves=Mundos
|
||||
modpack.files.scripts=Archivo de configuración de MineTweaker
|
||||
modpack.files.servers_dat=Archivo de lista de servidores
|
||||
modpack.install=Instalar Modpack %s
|
||||
@@ -845,7 +908,7 @@ modpack.wizard.step.2=Elegir archivos
|
||||
modpack.wizard.step.2.title=Elige los archivos que quieras añadir al modpack.
|
||||
modpack.wizard.step.3=Tipo de modpack
|
||||
modpack.wizard.step.3.title=Elija el tipo de modpack que desea exportar.
|
||||
modpack.wizard.step.initialization.exported_version=Versión del juego a exportar
|
||||
modpack.wizard.step.initialization.exported_version=Instancia del juego a exportar
|
||||
modpack.wizard.step.initialization.force_update=Forzar la actualización del modpack a la última versión (necesitarás un servicio de alojamiento de archivos)
|
||||
modpack.wizard.step.initialization.include_launcher=Incluir el launcher
|
||||
modpack.wizard.step.initialization.save=Exportar a...
|
||||
@@ -956,6 +1019,7 @@ mods.download=Descargar mod
|
||||
mods.download.title=Descargar mod - %1s
|
||||
mods.download.recommend=Versión recomendada - Minecraft %1s
|
||||
mods.enable=Activar
|
||||
mods.game.version=Versión del juego
|
||||
mods.manage=Mods
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MCMod
|
||||
@@ -986,10 +1050,17 @@ web.open_in_browser=Desea abrir esta dirección en un navegador:\n%s
|
||||
web.view_in_browser=Ver en navegador
|
||||
|
||||
world=Mundos
|
||||
world.add=Añadir un mundo (.zip)
|
||||
world.add=Añadir mundo
|
||||
world.backup=Copia de seguridad
|
||||
world.backup.create.failed=No se ha podido crear la copia de seguridad.\n%s
|
||||
world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
|
||||
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
|
||||
world.backup.delete=Eliminar esta copia de seguridad
|
||||
world.backup.reveal=Abrir el directorio
|
||||
world.backup.title=Mundo [%s] - Copias de seguridad
|
||||
world.datapack=Gestionar paquetes de datos
|
||||
world.datapack.1_13=Sólo Minecraft 1.13 o posterior soporta paquetes de datos.
|
||||
world.description=%s | Jugado por última vez en %s | Versión del juego: %s
|
||||
world.datetime=Jugado por última vez en %s
|
||||
world.download=Descargar Mundo
|
||||
world.export=Exportar el mundo
|
||||
world.export.title=Elija el directorio para este mundo exportado
|
||||
@@ -1001,6 +1072,7 @@ world.import.already_exists=Este mundo ya existe.
|
||||
world.import.choose=Elija el archivo de mundo que desea importar
|
||||
world.import.failed=No se ha podido importar este mundo: %s
|
||||
world.import.invalid=No se ha podido analizar el mundo.
|
||||
world.info=Información del mundo
|
||||
world.info.title=Mundo [%s] - Información
|
||||
world.info.basic=Información básica
|
||||
world.info.allow_cheats=Permitir comandos/trucos
|
||||
@@ -1030,12 +1102,12 @@ world.info.player.xp_level=Nivel de experiencia
|
||||
world.info.random_seed=Semilla
|
||||
world.info.time=Tiempo de juego
|
||||
world.info.time.format=%s días
|
||||
world.locked=En uso
|
||||
world.manage=Mundos
|
||||
world.name=Nombre del mundo
|
||||
world.name.enter=Introducir el nombre del mundo
|
||||
world.reveal=Abrir en el Explorador
|
||||
world.reveal=Abrir el directorio
|
||||
world.show_all=Mostrar todo
|
||||
world.time=EEE, MMM d, yyyy HH\:mm\:ss
|
||||
|
||||
profile=Directorios del juego
|
||||
profile.already_exists=Este nombre ya existe, por favor, utilice un nombre diferente.
|
||||
@@ -1150,6 +1222,9 @@ settings.advanced.wrapper_launcher.prompt=Permite ejecutar el juego usando un pr
|
||||
settings.custom=Personalizado
|
||||
|
||||
settings.game=Configuración
|
||||
settings.game.copy_global=Copiar desde Config. Global
|
||||
settings.game.copy_global.copy_all=Copiar todo
|
||||
settings.game.copy_global.copy_all.confirm=¿Está seguro de que desea sobrescribir la configuración actual de la instancia? Esta acción no se puede deshacer.
|
||||
settings.game.current=Juego
|
||||
settings.game.dimension=Resolución
|
||||
settings.game.exploration=Explorar
|
||||
@@ -1188,7 +1263,7 @@ settings.launcher.language=Idioma
|
||||
settings.launcher.launcher_log.export=Exportar registros del launcher
|
||||
settings.launcher.launcher_log.export.failed=No se han podido exportar los registros
|
||||
settings.launcher.launcher_log.export.success=Los registros se han exportado a "%s"
|
||||
settings.launcher.launcher_log.reveal=Revelar registros en el Explorador
|
||||
settings.launcher.launcher_log.reveal=Abrir el directorio
|
||||
settings.launcher.log=Registro
|
||||
settings.launcher.log.font=Fuente
|
||||
settings.launcher.proxy=Proxy
|
||||
@@ -1199,7 +1274,7 @@ settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=Sin proxy
|
||||
settings.launcher.proxy.password=Contraseña
|
||||
settings.launcher.proxy.port=Puerto
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.socks=SOCKS
|
||||
settings.launcher.proxy.username=Nombre de usuario
|
||||
settings.launcher.theme=Tema
|
||||
settings.launcher.title_transparent=Barra de título transparente
|
||||
@@ -1216,16 +1291,15 @@ settings.memory.lower_bound=Memoria mínima
|
||||
settings.memory.used_per_total=%1$.1f GB Utilizados / %2$.1f GB Totales
|
||||
settings.physical_memory=Tamaño de la memoria física
|
||||
settings.show_log=Mostrar registros
|
||||
settings.skin=Ahora se admiten los skins para las cuentas sin conexión. Puedes ir a la configuración de la cuenta para cambiar tu skin y tu capa, pero otros jugadores no podrán verlo en el multijugador.
|
||||
settings.tabs.installers=Cargadores
|
||||
settings.take_effect_after_restart=Se aplica después de reiniciar
|
||||
settings.type=Tipo de configuración de la instancia
|
||||
settings.type.global=Config. Global (compartida)
|
||||
settings.type.global.manage=Config. Global
|
||||
settings.type.global.edit=Editar Config. Global
|
||||
settings.type.special.enable=Habilitar ajustes personalizados para la instancia actual
|
||||
settings.type.special.enable=Activar Config. Específica de la instancia
|
||||
settings.type.special.edit=Editar configuración de la instancia actual
|
||||
settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones.
|
||||
settings.type.special.edit.hint=La instancia actual "%s" ha activado la "Configuración específica de la instancia". Todas las configuraciones de esta página NO afectarán a esa instancia. Haga clic aquí para editar la configuración de la instancia.
|
||||
|
||||
sponsor=Donantes
|
||||
sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información.
|
||||
@@ -1292,7 +1366,7 @@ version.manage.manage.title=Editar instancia - %1s
|
||||
version.manage.redownload_assets_index=Actualizar activos del juego
|
||||
version.manage.remove=Eliminar instancia
|
||||
version.manage.remove.confirm=¿Está seguro de que quiere eliminar permanentemente la instancia "%s"? ¡Esta operación no se puede deshacer!
|
||||
version.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la instancia "%s"? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de «%s».
|
||||
version.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la instancia "%s"? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de "%s".
|
||||
version.manage.remove.confirm.independent=Dado que esta instancia está almacenada en un directorio aislado, al eliminarla también se eliminarán sus guardados y otros datos. ¿Aún quieres borrar la instancia %s?
|
||||
version.manage.remove_assets=Borrar todas las activos del juego
|
||||
version.manage.remove_libraries=Borrar todas las bibliotecas
|
||||
|
||||