Compare commits
97
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f972c49762 | ||
|
|
5afeb3d019 | ||
|
|
38897fbd2a | ||
|
|
0562042218 | ||
|
|
c5e7ee8aef | ||
|
|
35b288b800 | ||
|
|
a609d32081 | ||
|
|
72d8b75643 | ||
|
|
01f9a452f1 | ||
|
|
f2edc8001c | ||
|
|
d2df515325 | ||
|
|
95e39fe87a | ||
|
|
662bf1a9d4 | ||
|
|
bfda297838 | ||
|
|
d80f61e2e7 | ||
|
|
1f7b156423 | ||
|
|
56d20a5d08 | ||
|
|
b5cea83a8a | ||
|
|
397edaec5d | ||
|
|
39b8de4824 | ||
|
|
5197157a97 | ||
|
|
d61826ba4c | ||
|
|
85886da770 | ||
|
|
6f53da159a | ||
|
|
78e15d130b | ||
|
|
7d12ef6b09 | ||
|
|
260f4b6c5d | ||
|
|
104534d12f | ||
|
|
81b3911dc9 | ||
|
|
c5b8aabc57 | ||
|
|
f0a064afdd | ||
|
|
8a4f6af193 | ||
|
|
e63ee49eaa | ||
|
|
d5408c38b3 | ||
|
|
5fd52bb808 | ||
|
|
cb2b3525b8 | ||
|
|
4c6fdf49de | ||
|
|
981b55a10e | ||
|
|
8460df7d5d | ||
|
|
727e6746d2 | ||
|
|
8e0f0e2a38 | ||
|
|
3df52b0e8f | ||
|
|
6f0e133bb0 | ||
|
|
36da64f796 | ||
|
|
4c242d0c80 | ||
|
|
0a230ef6b5 | ||
|
|
c12ae9fdf8 | ||
|
|
773ef4282c | ||
|
|
9edfe29659 | ||
|
|
5591d92e25 | ||
|
|
65bdb29e71 | ||
|
|
355df211db | ||
|
|
fa66549f53 | ||
|
|
6e48dbd6c6 | ||
|
|
e3c7278759 | ||
|
|
dd1829d8ca | ||
|
|
60ac16a3ea | ||
|
|
b400a9a3ef | ||
|
|
c7cc2d0733 | ||
|
|
861223e909 | ||
|
|
70ecbc734e | ||
|
|
2ca7e7c830 | ||
|
|
9a0db56ac3 | ||
|
|
c1ae5875ba | ||
|
|
1d0ae427bf | ||
|
|
3dde2d19e2 | ||
|
|
d5ec6d7b85 | ||
|
|
13856b71a0 | ||
|
|
c6658771ca | ||
|
|
406f36584d | ||
|
|
898750d058 | ||
|
|
09e9e934d3 | ||
|
|
3d170267b7 | ||
|
|
3ca7e3cf88 | ||
|
|
1378d1445a | ||
|
|
16d304dbb4 | ||
|
|
aff5db7759 | ||
|
|
0697aa9755 | ||
|
|
995d3bd4b9 | ||
|
|
3bf5204e79 | ||
|
|
0b1ce6e94a | ||
|
|
a66ccd7385 | ||
|
|
7a3f125802 | ||
|
|
4a8dcce3d4 | ||
|
|
adcc3eb43f | ||
|
|
8bbd1a7cf4 | ||
|
|
467ff26278 | ||
|
|
d6a267afa4 | ||
|
|
743e934436 | ||
|
|
f89ac2231a | ||
|
|
1877a18ebb | ||
|
|
c99f85d9d5 | ||
|
|
228942c60a | ||
|
|
fecd567eab | ||
|
|
602840519b | ||
|
|
959adf1075 | ||
|
|
422f681fe6 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
name: Bug 反馈 | Bug Feedback
|
||||
name: Bug 反馈 | Bug Report
|
||||
description:
|
||||
反馈一个 HMCL 错误。| Bug Feedback.
|
||||
反馈一个 HMCL 错误。| File a bug report for HMCL.
|
||||
title: "[Bug] "
|
||||
labels: bug
|
||||
body:
|
||||
@@ -8,29 +8,35 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
提交前请确认:
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
* 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新快照版本。
|
||||
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
|
||||
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
|
||||
* 你的启动器版本是**最新的预览版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新预览版本。
|
||||
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
|
||||
|
||||
Before submitting, please confirm:
|
||||
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version.
|
||||
|
||||
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
|
||||
* The issue is indeed **a bug of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* Your launcher is the **latest nightly build**. You can click [here](https://zkitefly.github.io/HMCL-Snapshot-Update/en) to download the latest nightly build.
|
||||
|
||||
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台 | Platform
|
||||
description: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug.
|
||||
description: |
|
||||
请输入你遇到问题的平台。
|
||||
Please enter the platform on which you encountered the bug.
|
||||
placeholder: e.g. Windows 11
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-report
|
||||
attributes:
|
||||
label: 问题描述 | Problem Description
|
||||
description: 请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。 Please describe the problem you met in as much detail as possible. Besides, please describe how to trigger this problem again.
|
||||
label: 问题描述 | Bug Description
|
||||
description: |
|
||||
请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。
|
||||
Please describe the bug you met in as much detail as possible. Additionally, describe the steps to reproduce this bug.
|
||||
placeholder: |
|
||||
1. 点击 HMCL 上的某个按钮 | Click a button named ...
|
||||
2. 向下翻页 | Scroll down
|
||||
@@ -42,12 +48,12 @@ body:
|
||||
attributes:
|
||||
label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
|
||||
description: |
|
||||
如果您的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
|
||||
如果您的启动器没有崩溃,请在遇到问题后不要退出启动器,在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。
|
||||
如果你的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
|
||||
如果你的启动器没有崩溃,请在遇到问题后**不要退出启动器**,在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”,并将导出的日志拖到下方的输入栏中。
|
||||
**请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!**
|
||||
|
||||
If your launcher crashes, please fill it in (or drag the file in) below.
|
||||
If your launcher doesn't crash, please DO NOT EXIT your launcher, click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below.
|
||||
**Attention: The crash report or the log file is the key to solve the problem. Please update it!**
|
||||
If your launcher crashes, please fill in (or drag the file in) the following input field with the crash report.
|
||||
If your launcher does not crash, please DO NOT EXIT your launcher, click "Export Launcher Logs" in the "Settings → General → Debug" of the launcher, and drag the exported log to the following input field.
|
||||
**ATTENTION: The crash report or log file is the key to resolving the bug. Please upload them!**
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,11 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: QQ 群 | QQ Groups
|
||||
- name: QQ 群 | QQ Group
|
||||
url: https://docs.hmcl.net/groups.html
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher.
|
||||
- name: Discord 频道 | Discord Channel
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ group of Hello Minecraft! Launcher.
|
||||
- name: Discord 服务器 | Discord Server
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 服务器。| The official Discord server of Hello Minecraft! Launcher.
|
||||
- name: 其他反馈 | Others
|
||||
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
|
||||
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
|
||||
@@ -1,22 +1,28 @@
|
||||
name: 新功能 | Feature Request
|
||||
description: 为 HMCL 提出新功能 | A new feature for HMCL
|
||||
description: 为 HMCL 提出新功能。| Suggest a new feature or enhancement for HMCL.
|
||||
title: "[Feature] "
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: 请确认 Issues 列表无重复的项目。| Please make sure that no duplicated issues has already been delivered.
|
||||
value: |
|
||||
请确认 Issues 列表无重复的项目。
|
||||
Please make sure that no duplicate issues have already been submitted.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 描述 | Description
|
||||
description: 请详细描述你想加入的新功能。| Please describe the new feature detaily.
|
||||
description: |
|
||||
请详细描述你想加入的新功能。
|
||||
Please describe the new feature in detail.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reason
|
||||
attributes:
|
||||
label: 原因 | Reason
|
||||
description: 请描述该功能带来的好处及原因。| Please describe why you want to add the feature into HMCL.
|
||||
description: |
|
||||
请描述该功能带来的好处及原因。
|
||||
Please describe why you want to add the feature or enhancement to HMCL.
|
||||
validations:
|
||||
required: true
|
||||
@@ -50,16 +50,19 @@ jobs:
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
@@ -69,6 +72,7 @@ jobs:
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
prerelease: true
|
||||
stable-check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
needs: dev-check-update
|
||||
@@ -114,16 +118,18 @@ jobs:
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
|
||||
+6
-21
@@ -25,7 +25,7 @@ val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
|
||||
if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT"
|
||||
}
|
||||
}
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.6"
|
||||
val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial"
|
||||
|
||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
@@ -37,13 +37,13 @@ version = "$versionRoot.$buildNumber"
|
||||
dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
implementation("libs:JFoenix")
|
||||
implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0")
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
|
||||
fun createChecksum(file: File) {
|
||||
val algorithms = linkedMapOf(
|
||||
"MD5" to "md5",
|
||||
"SHA-1" to "sha1",
|
||||
"SHA-256" to "sha256",
|
||||
"SHA-512" to "sha512"
|
||||
@@ -111,6 +111,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
exclude("**/package-info.class")
|
||||
exclude("META-INF/maven/**")
|
||||
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageReaderSpi")
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
|
||||
|
||||
minimize {
|
||||
exclude(dependency("com.google.code.gson:.*:.*"))
|
||||
exclude(dependency("libs:JFoenix:.*"))
|
||||
@@ -118,7 +121,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
|
||||
manifest {
|
||||
attributes(
|
||||
"Created-By" to "Copyright(c) 2013-2024 huangyuhui.",
|
||||
"Created-By" to "Copyright(c) 2013-2025 huangyuhui.",
|
||||
"Main-Class" to "org.jackhuang.hmcl.Main",
|
||||
"Multi-Release" to "true",
|
||||
"Implementation-Version" to project.version,
|
||||
@@ -167,24 +170,6 @@ fun createExecutable(suffix: String, header: String) {
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
fun convertToBSS(resource: String) {
|
||||
doFirst {
|
||||
val cssFile = File(projectDir, "src/main/resources/$resource")
|
||||
val bssFile = File(projectDir, "build/compiled-resources/${resource.substring(0, resource.length - 4)}.bss")
|
||||
bssFile.parentFile.mkdirs()
|
||||
javaexec {
|
||||
classpath = sourceSets["main"].compileClasspath
|
||||
mainClass.set("com.sun.javafx.css.parser.Css2Bin")
|
||||
args(cssFile, bssFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
from("build/compiled-resources")
|
||||
|
||||
convertToBSS("assets/css/root.css")
|
||||
convertToBSS("assets/css/blue.css")
|
||||
|
||||
into("META-INF/versions/11") {
|
||||
from(sourceSets["java11"].output)
|
||||
}
|
||||
|
||||
@@ -33,19 +33,19 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.ProxyManager;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
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;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
@@ -218,7 +218,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
private VersionSetting initLocalVersionSetting(String id, VersionSetting vs) {
|
||||
localVersionSettings.put(id, vs);
|
||||
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
|
||||
vs.addListener(a -> saveVersionSetting(id));
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -251,7 +251,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
VersionSetting vs = getLocalVersionSetting(id);
|
||||
if (vs == null || vs.isUsesGlobal()) {
|
||||
profile.getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
||||
profile.getGlobal().setUsesGlobal(true);
|
||||
return profile.getGlobal();
|
||||
} else
|
||||
@@ -261,24 +260,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public Optional<File> getVersionIconFile(String id) {
|
||||
File root = getVersionRoot(id);
|
||||
|
||||
File iconFile = new File(root, "icon.png");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
}
|
||||
|
||||
iconFile = new File(root, "icon.jpg");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
}
|
||||
|
||||
iconFile = new File(root, "icon.bmp");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
}
|
||||
|
||||
iconFile = new File(root, "icon.gif");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
File file = new File(root, "icon." + extension);
|
||||
if (file.exists()) {
|
||||
return Optional.of(file);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
@@ -286,7 +272,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public void setVersionIconFile(String id, File iconFile) throws IOException {
|
||||
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
|
||||
if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) {
|
||||
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
|
||||
throw new IllegalArgumentException("Unsupported icon file: " + ext);
|
||||
}
|
||||
|
||||
@@ -297,11 +283,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public void deleteIconFile(String id) {
|
||||
File root = getVersionRoot(id);
|
||||
|
||||
new File(root, "icon.png").delete();
|
||||
new File(root, "icon.jpg").delete();
|
||||
new File(root, "icon.bmp").delete();
|
||||
new File(root, "icon.gif").delete();
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
new File(root, "icon." + extension).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public Image getVersionIconImage(String id) {
|
||||
@@ -315,9 +299,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
Version version = getVersion(id).resolve(this);
|
||||
Optional<File> iconFile = getVersionIconFile(id);
|
||||
if (iconFile.isPresent()) {
|
||||
try (InputStream inputStream = new FileInputStream(iconFile.get())) {
|
||||
return new Image(inputStream);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
return FXUtils.loadImage(iconFile.get().toPath());
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load version icon of " + id, e);
|
||||
}
|
||||
}
|
||||
@@ -353,6 +337,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
return false;
|
||||
|
||||
LOG.info("Saving version setting: " + id);
|
||||
|
||||
try {
|
||||
FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id)));
|
||||
return true;
|
||||
@@ -373,7 +359,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs = createLocalVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
VersionIconType versionIcon = vs.getVersionIcon();
|
||||
if (vs.isUsesGlobal()) {
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), vs);
|
||||
vs.setUsesGlobal(false);
|
||||
}
|
||||
vs.setVersionIcon(versionIcon); // versionIcon is preserved
|
||||
return vs;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,14 @@ import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -60,9 +64,10 @@ import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
@@ -178,6 +183,9 @@ public final class LauncherHelper {
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
|
||||
|
||||
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));
|
||||
|
||||
return new HMCLGameLauncher(
|
||||
repository,
|
||||
version.get(),
|
||||
@@ -522,6 +530,9 @@ public final class LauncherHelper {
|
||||
case MODDED_JAVA_17:
|
||||
suggestions.add(i18n("launch.advice.modded_java", 17, gameVersion));
|
||||
break;
|
||||
case MODDED_JAVA_21:
|
||||
suggestions.add(i18n("launch.advice.modded_java", 21, gameVersion));
|
||||
break;
|
||||
case VANILLA_JAVA_8_51:
|
||||
suggestions.add(i18n("launch.advice.java8_51_1_13"));
|
||||
break;
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
|
||||
private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins");
|
||||
private static final Path TEXTURES_DIR = Metadata.HMCL_DIRECTORY.resolve("skins");
|
||||
|
||||
private static Path getTexturePath(Texture texture) {
|
||||
String url = texture.getUrl();
|
||||
|
||||
@@ -47,7 +47,7 @@ import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public final class Config implements Cloneable, Observable {
|
||||
public final class Config implements Observable {
|
||||
|
||||
public static final int CURRENT_UI_VERSION = 0;
|
||||
|
||||
@@ -223,11 +223,6 @@ public final class Config implements Cloneable, Observable {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
// Getters & Setters & Properties
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
|
||||
@@ -197,7 +197,7 @@ public final class ConfigHolder {
|
||||
}
|
||||
}
|
||||
|
||||
static void markConfigDirty() {
|
||||
private static void markConfigDirty() {
|
||||
configWriter.accept(configInstance.toJson());
|
||||
}
|
||||
|
||||
@@ -241,11 +241,7 @@ public final class ConfigHolder {
|
||||
}
|
||||
}
|
||||
|
||||
static void markGlobalConfigDirty() {
|
||||
private static void markGlobalConfigDirty() {
|
||||
globalConfigWriter.accept(globalConfigInstance.toJson());
|
||||
}
|
||||
|
||||
private static void saveGlobalConfigSync() throws IOException {
|
||||
writeToConfig(globalConfigInstance.toJson());
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
|
||||
@JsonAdapter(GlobalConfig.Serializer.class)
|
||||
public final class GlobalConfig implements Cloneable, Observable {
|
||||
public final class GlobalConfig implements Observable {
|
||||
|
||||
@Nullable
|
||||
public static GlobalConfig fromJson(String json) throws JsonParseException {
|
||||
@@ -79,11 +79,6 @@ public final class GlobalConfig implements Cloneable, Observable {
|
||||
return Config.CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalConfig clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
public int getAgreementVersion() {
|
||||
return agreementVersion.get();
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ public final class Profile implements Observable {
|
||||
global.addListener(listener);
|
||||
gameDir.addListener(listener);
|
||||
useRelativePath.addListener(listener);
|
||||
global.get().addPropertyChangedListener(listener);
|
||||
global.get().addListener(listener);
|
||||
selectedVersion.addListener(listener);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,8 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -36,11 +35,10 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -58,34 +56,6 @@ public class Theme {
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
private static Charset cssCharset;
|
||||
|
||||
private static Charset getCssCharset() {
|
||||
if (cssCharset != null)
|
||||
return cssCharset;
|
||||
|
||||
Charset defaultCharset = Charset.defaultCharset();
|
||||
if (defaultCharset != StandardCharsets.UTF_8) {
|
||||
// https://bugs.openjdk.org/browse/JDK-8279328
|
||||
// For JavaFX 17 or earlier, native encoding should be used
|
||||
String jfxVersion = System.getProperty("javafx.version");
|
||||
if (jfxVersion != null) {
|
||||
Matcher matcher = Pattern.compile("^(?<version>[0-9]+)").matcher(jfxVersion);
|
||||
if (matcher.find()) {
|
||||
int v = Lang.parseInt(matcher.group(), -1);
|
||||
if (v >= 18) {
|
||||
cssCharset = StandardCharsets.UTF_8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cssCharset == null)
|
||||
cssCharset = defaultCharset;
|
||||
|
||||
return cssCharset;
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static Optional<Font> font;
|
||||
|
||||
@@ -145,6 +115,14 @@ public class Theme {
|
||||
return isLight() ? Color.BLACK : Color.WHITE;
|
||||
}
|
||||
|
||||
private static String rgba(Color color, double opacity) {
|
||||
return String.format("rgba(%d, %d, %d, %.1f)",
|
||||
(int) Math.ceil(color.getRed() * 256),
|
||||
(int) Math.ceil(color.getGreen() * 256),
|
||||
(int) Math.ceil(color.getBlue() * 256),
|
||||
opacity);
|
||||
}
|
||||
|
||||
public String[] getStylesheets(String overrideFontFamily) {
|
||||
String css = "/assets/css/blue.css";
|
||||
|
||||
@@ -163,30 +141,44 @@ public class Theme {
|
||||
|
||||
if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) {
|
||||
Color textFill = getForegroundColor();
|
||||
String fontCss = "";
|
||||
if (fontFamily != null) {
|
||||
fontCss = "-fx-font-family: \"" + fontFamily + "\";";
|
||||
if (fontStyle != null && !fontStyle.isEmpty())
|
||||
fontCss += " -fx-font-style: \"" + fontStyle + "\";";
|
||||
}
|
||||
|
||||
try {
|
||||
File temp = File.createTempFile("hmcl", ".css");
|
||||
String themeText = IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
|
||||
.replace("%base-color%", color)
|
||||
.replace("%base-red%", Integer.toString((int) Math.ceil(paint.getRed() * 256)))
|
||||
.replace("%base-green%", Integer.toString((int) Math.ceil(paint.getGreen() * 256)))
|
||||
.replace("%base-blue%", Integer.toString((int) Math.ceil(paint.getBlue() * 256)))
|
||||
.replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int) Math.ceil(paint.getRed() * 256), (int) Math.ceil(paint.getGreen() * 256), (int) Math.ceil(paint.getBlue() * 256)))
|
||||
.replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256)))
|
||||
.replace("%font-color%", getColorDisplayName(getForegroundColor()))
|
||||
.replace("%font%", fontCss);
|
||||
FileUtils.writeText(temp, themeText, getCssCharset());
|
||||
temp.deleteOnExit();
|
||||
css = temp.toURI().toString();
|
||||
} catch (IOException | NullPointerException e) {
|
||||
LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e);
|
||||
}
|
||||
StringBuilder themeBuilder = new StringBuilder(512);
|
||||
themeBuilder.append(".root {")
|
||||
.append("-fx-base-color:").append(color).append(';')
|
||||
.append("-fx-base-darker-color: derive(-fx-base-color, -10%);")
|
||||
.append("-fx-base-check-color: derive(-fx-base-color, 30%);")
|
||||
.append("-fx-rippler-color:").append(rgba(paint, 0.3)).append(';')
|
||||
.append("-fx-base-rippler-color: derive(").append(rgba(paint, 0.3)).append(", 100%);")
|
||||
.append("-fx-base-disabled-text-fill:").append(rgba(textFill, 0.7)).append(";")
|
||||
.append("-fx-base-text-fill:").append(getColorDisplayName(getForegroundColor())).append(";")
|
||||
.append("-theme-thumb:").append(rgba(paint, 0.7)).append(";");
|
||||
|
||||
if (fontFamily == null)
|
||||
// https://github.com/HMCL-dev/HMCL/pull/3423
|
||||
themeBuilder.append("-fx-font-family: -fx-base-font-family;");
|
||||
else
|
||||
themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
|
||||
if (fontStyle != null && !fontStyle.isEmpty())
|
||||
themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";");
|
||||
|
||||
themeBuilder.append('}');
|
||||
|
||||
if (FXUtils.JAVAFX_MAJOR_VERSION >= 17)
|
||||
// JavaFX 17+ support loading stylesheets from data URIs
|
||||
// https://bugs.openjdk.org/browse/JDK-8267554
|
||||
css = "data:text/css;charset=UTF-8;base64," + Base64.getEncoder().encodeToString(themeBuilder.toString().getBytes(StandardCharsets.UTF_8));
|
||||
else
|
||||
try {
|
||||
File temp = File.createTempFile("hmcl", ".css");
|
||||
// For JavaFX 17 or earlier, CssParser uses the default charset
|
||||
// https://bugs.openjdk.org/browse/JDK-8279328
|
||||
FileUtils.writeText(temp, themeBuilder.toString(), Charset.defaultCharset());
|
||||
temp.deleteOnExit();
|
||||
css = temp.toURI().toString();
|
||||
} catch (IOException | NullPointerException e) {
|
||||
LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new String[]{css, "/assets/css/root.css"};
|
||||
@@ -235,7 +227,7 @@ public class Theme {
|
||||
}
|
||||
|
||||
public static String getColorDisplayName(Color c) {
|
||||
return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase(Locale.ROOT) : null;
|
||||
return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null;
|
||||
}
|
||||
|
||||
private static ObjectBinding<Color> FOREGROUND_FILL;
|
||||
|
||||
@@ -20,11 +20,14 @@ package org.jackhuang.hmcl.setting;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
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.GameVersionNumber;
|
||||
@@ -42,16 +45,12 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@JsonAdapter(VersionSetting.Serializer.class)
|
||||
public final class VersionSetting implements Cloneable {
|
||||
public final class VersionSetting implements Cloneable, Observable {
|
||||
|
||||
private boolean global = false;
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
public boolean isGlobal() {
|
||||
return global;
|
||||
}
|
||||
|
||||
public void setGlobal(boolean global) {
|
||||
this.global = global;
|
||||
public VersionSetting() {
|
||||
PropertyUtils.attachListener(this, helper);
|
||||
}
|
||||
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", true);
|
||||
@@ -708,79 +707,21 @@ public final class VersionSetting implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
usesGlobalProperty.addListener(listener);
|
||||
javaVersionProperty.addListener(listener);
|
||||
javaDirProperty.addListener(listener);
|
||||
wrapperProperty.addListener(listener);
|
||||
permSizeProperty.addListener(listener);
|
||||
maxMemoryProperty.addListener(listener);
|
||||
minMemoryProperty.addListener(listener);
|
||||
autoMemory.addListener(listener);
|
||||
preLaunchCommandProperty.addListener(listener);
|
||||
postExitCommand.addListener(listener);
|
||||
javaArgsProperty.addListener(listener);
|
||||
minecraftArgsProperty.addListener(listener);
|
||||
environmentVariablesProperty.addListener(listener);
|
||||
noJVMArgsProperty.addListener(listener);
|
||||
notCheckGameProperty.addListener(listener);
|
||||
notCheckJVMProperty.addListener(listener);
|
||||
notPatchNativesProperty.addListener(listener);
|
||||
showLogsProperty.addListener(listener);
|
||||
serverIpProperty.addListener(listener);
|
||||
fullscreenProperty.addListener(listener);
|
||||
widthProperty.addListener(listener);
|
||||
heightProperty.addListener(listener);
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
processPriorityProperty.addListener(listener);
|
||||
rendererProperty.addListener(listener);
|
||||
useNativeGLFW.addListener(listener);
|
||||
useNativeOpenAL.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
nativesDirProperty.addListener(listener);
|
||||
nativesDirTypeProperty.addListener(listener);
|
||||
versionIcon.addListener(listener);
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionSetting clone() {
|
||||
VersionSetting versionSetting = new VersionSetting();
|
||||
versionSetting.setUsesGlobal(isUsesGlobal());
|
||||
versionSetting.setJavaVersionType(getJavaVersionType());
|
||||
versionSetting.setJavaVersion(getJavaVersion());
|
||||
versionSetting.setDefaultJavaPath(getDefaultJavaPath());
|
||||
versionSetting.setJavaDir(getJavaDir());
|
||||
versionSetting.setWrapper(getWrapper());
|
||||
versionSetting.setPermSize(getPermSize());
|
||||
versionSetting.setMaxMemory(getMaxMemory());
|
||||
versionSetting.setMinMemory(getMinMemory());
|
||||
versionSetting.setAutoMemory(isAutoMemory());
|
||||
versionSetting.setPreLaunchCommand(getPreLaunchCommand());
|
||||
versionSetting.setPostExitCommand(getPostExitCommand());
|
||||
versionSetting.setJavaArgs(getJavaArgs());
|
||||
versionSetting.setMinecraftArgs(getMinecraftArgs());
|
||||
versionSetting.setEnvironmentVariables(getEnvironmentVariables());
|
||||
versionSetting.setNoJVMArgs(isNoJVMArgs());
|
||||
versionSetting.setNotCheckGame(isNotCheckGame());
|
||||
versionSetting.setNotCheckJVM(isNotCheckJVM());
|
||||
versionSetting.setNotPatchNatives(isNotPatchNatives());
|
||||
versionSetting.setShowLogs(isShowLogs());
|
||||
versionSetting.setServerIp(getServerIp());
|
||||
versionSetting.setFullscreen(isFullscreen());
|
||||
versionSetting.setWidth(getWidth());
|
||||
versionSetting.setHeight(getHeight());
|
||||
versionSetting.setGameDirType(getGameDirType());
|
||||
versionSetting.setGameDir(getGameDir());
|
||||
versionSetting.setProcessPriority(getProcessPriority());
|
||||
versionSetting.setRenderer(getRenderer());
|
||||
versionSetting.setUseNativeGLFW(isUseNativeGLFW());
|
||||
versionSetting.setUseNativeOpenAL(isUseNativeOpenAL());
|
||||
versionSetting.setLauncherVisibility(getLauncherVisibility());
|
||||
versionSetting.setNativesDir(getNativesDir());
|
||||
versionSetting.setVersionIcon(getVersionIcon());
|
||||
return versionSetting;
|
||||
VersionSetting cloned = new VersionSetting();
|
||||
PropertyUtils.copyProperties(this, cloned);
|
||||
return cloned;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
@@ -26,10 +27,14 @@ import javafx.beans.WeakInvalidationListener;
|
||||
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;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.*;
|
||||
@@ -44,6 +49,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
@@ -53,10 +59,9 @@ import org.glavo.png.PNGWriter;
|
||||
import org.glavo.png.javafx.PNGJavaFXUtils;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
@@ -67,15 +72,14 @@ import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
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.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -85,6 +89,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
@@ -96,8 +103,26 @@ public final class FXUtils {
|
||||
private FXUtils() {
|
||||
}
|
||||
|
||||
public static final int JAVAFX_MAJOR_VERSION;
|
||||
|
||||
static {
|
||||
String jfxVersion = System.getProperty("javafx.version");
|
||||
int majorVersion = -1;
|
||||
if (jfxVersion != null) {
|
||||
Matcher matcher = Pattern.compile("^(?<version>[0-9]+)").matcher(jfxVersion);
|
||||
if (matcher.find()) {
|
||||
majorVersion = Lang.parseInt(matcher.group(), -1);
|
||||
}
|
||||
}
|
||||
JAVAFX_MAJOR_VERSION = majorVersion;
|
||||
}
|
||||
|
||||
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
|
||||
|
||||
public static final List<String> IMAGE_EXTENSIONS = Lang.immutableListOf(
|
||||
"png", "jpg", "jpeg", "bmp", "gif", "webp"
|
||||
);
|
||||
|
||||
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Path> remoteImageCache = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -213,6 +238,21 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Event> void ignoreEvent(Node node, EventType<T> type, Predicate<? super T> filter) {
|
||||
EventDispatcher oldDispatcher = node.getEventDispatcher();
|
||||
node.setEventDispatcher((event, tail) -> {
|
||||
EventType<?> t = event.getEventType();
|
||||
while (t != null && t != type)
|
||||
t = t.getSuperType();
|
||||
if (t == type && filter.test((T) event)) {
|
||||
return tail.dispatchEvent(event);
|
||||
} else {
|
||||
return oldDispatcher.dispatchEvent(event, tail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
|
||||
column.setCellValueFactory(param -> {
|
||||
if (column.validateValue(param))
|
||||
@@ -298,8 +338,12 @@ public final class FXUtils {
|
||||
ScrollUtils.addSmoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
private static final Duration TOOLTIP_FAST_SHOW_DELAY = Duration.millis(50);
|
||||
private static final Duration TOOLTIP_SLOW_SHOW_DELAY = Duration.millis(500);
|
||||
private static final Duration TOOLTIP_SHOW_DURATION = Duration.millis(5000);
|
||||
|
||||
public static void installFastTooltip(Node node, Tooltip tooltip) {
|
||||
installTooltip(node, 50, 5000, 0, tooltip);
|
||||
runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_FAST_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip));
|
||||
}
|
||||
|
||||
public static void installFastTooltip(Node node, String tooltip) {
|
||||
@@ -307,39 +351,13 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void installSlowTooltip(Node node, Tooltip tooltip) {
|
||||
installTooltip(node, 500, 5000, 0, tooltip);
|
||||
runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_SLOW_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip));
|
||||
}
|
||||
|
||||
public static void installSlowTooltip(Node node, String tooltip) {
|
||||
installSlowTooltip(node, new Tooltip(tooltip));
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
runInFX(() -> {
|
||||
try {
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
try {
|
||||
// Java 9
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
LOG.error("Cannot install tooltip", e);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void playAnimation(Node node, String animationKey, Timeline timeline) {
|
||||
animationKey = "FXUTILS.ANIMATION." + animationKey;
|
||||
Object oldTimeline = node.getProperties().get(animationKey);
|
||||
@@ -522,10 +540,12 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static <T> void bind(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue()));
|
||||
TextFieldBindingListener<T> listener = new TextFieldBindingListener<>(textField, property, converter);
|
||||
textField.focusedProperty().addListener((ChangeListener<Boolean>) listener);
|
||||
property.addListener(listener);
|
||||
TextFieldBinding<T> binding = new TextFieldBinding<>(textField, property, converter);
|
||||
binding.updateTextField();
|
||||
textField.getProperties().put("FXUtils.bind.binding", binding);
|
||||
textField.focusedProperty().addListener(binding.focusedListener);
|
||||
textField.sceneProperty().addListener(binding.sceneListener);
|
||||
property.addListener(binding.propertyListener);
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
@@ -537,68 +557,66 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void unbind(JFXTextField textField, Property<?> property) {
|
||||
TextFieldBindingListener<?> listener = new TextFieldBindingListener<>(textField, property, null);
|
||||
textField.focusedProperty().removeListener((ChangeListener<Boolean>) listener);
|
||||
property.removeListener(listener);
|
||||
TextFieldBinding<?> binding = (TextFieldBinding<?>) textField.getProperties().remove("FXUtils.bind.binding");
|
||||
if (binding != null) {
|
||||
textField.focusedProperty().removeListener(binding.focusedListener);
|
||||
textField.sceneProperty().removeListener(binding.sceneListener);
|
||||
property.removeListener(binding.propertyListener);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TextFieldBindingListener<T> implements ChangeListener<Boolean>, InvalidationListener {
|
||||
private final int hashCode;
|
||||
private final WeakReference<JFXTextField> textFieldRef;
|
||||
private final WeakReference<Property<T>> propertyRef;
|
||||
private static final class TextFieldBinding<T> {
|
||||
private final JFXTextField textField;
|
||||
private final Property<T> property;
|
||||
private final StringConverter<T> converter;
|
||||
|
||||
TextFieldBindingListener(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
this.textFieldRef = new WeakReference<>(textField);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
public final ChangeListener<Boolean> focusedListener;
|
||||
public final ChangeListener<Scene> sceneListener;
|
||||
public final InvalidationListener propertyListener;
|
||||
|
||||
public TextFieldBinding(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
this.textField = textField;
|
||||
this.property = property;
|
||||
this.converter = converter;
|
||||
this.hashCode = System.identityHashCode(textField) ^ System.identityHashCode(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focused) { // On TextField changed
|
||||
JFXTextField textField = textFieldRef.get();
|
||||
Property<T> property = this.propertyRef.get();
|
||||
|
||||
if (textField != null && property != null && oldValue == Boolean.TRUE && focused == Boolean.FALSE) {
|
||||
if (textField.validate()) {
|
||||
String newText = textField.getText();
|
||||
@SuppressWarnings("unchecked")
|
||||
T newValue = converter == null ? (T) newText : converter.fromString(newText);
|
||||
|
||||
if (!Objects.equals(newValue, property.getValue()))
|
||||
property.setValue(newValue);
|
||||
} else {
|
||||
// Rollback to old value
|
||||
invalidated(null);
|
||||
focusedListener = (observable, oldFocused, newFocused) -> {
|
||||
if (oldFocused && !newFocused) {
|
||||
if (textField.validate()) {
|
||||
uppdateProperty();
|
||||
} else {
|
||||
// Rollback to old value
|
||||
updateTextField();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sceneListener = (observable, oldScene, newScene) -> {
|
||||
if (oldScene != null && newScene == null) {
|
||||
// Component is being removed from scene
|
||||
if (textField.validate()) {
|
||||
uppdateProperty();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
propertyListener = observable -> {
|
||||
updateTextField();
|
||||
};
|
||||
}
|
||||
|
||||
public void uppdateProperty() {
|
||||
String newText = textField.getText();
|
||||
@SuppressWarnings("unchecked")
|
||||
T newValue = converter == null ? (T) newText : converter.fromString(newText);
|
||||
|
||||
if (!Objects.equals(newValue, property.getValue())) {
|
||||
property.setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) { // On property change
|
||||
JFXTextField textField = textFieldRef.get();
|
||||
Property<T> property = this.propertyRef.get();
|
||||
|
||||
if (textField != null && property != null) {
|
||||
T value = property.getValue();
|
||||
textField.setText(converter == null ? (String) value : converter.toString(value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof TextFieldBindingListener))
|
||||
return false;
|
||||
TextFieldBindingListener<?> other = (TextFieldBindingListener<?>) obj;
|
||||
return this.hashCode == other.hashCode
|
||||
&& this.textFieldRef.get() == other.textFieldRef.get()
|
||||
&& this.propertyRef.get() == other.propertyRef.get();
|
||||
public void updateTextField() {
|
||||
T value = property.getValue();
|
||||
textField.setText(converter == null ? (String) value : converter.toString(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,6 +738,50 @@ public final class FXUtils {
|
||||
stage.getIcons().add(newBuiltinImage(icon));
|
||||
}
|
||||
|
||||
private static Image loadWebPImage(InputStream input) throws IOException {
|
||||
WebPImageReaderSpi spi = new WebPImageReaderSpi();
|
||||
ImageReader reader = spi.createReaderInstance(null);
|
||||
|
||||
try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) {
|
||||
reader.setInput(imageInput, true, true);
|
||||
return SwingFXUtils.toFXImage(reader.read(0, reader.getDefaultReadParam()), null);
|
||||
} finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(Path path) throws Exception {
|
||||
try (InputStream input = Files.newInputStream(path)) {
|
||||
if ("webp".equalsIgnoreCase(FileUtils.getExtension(path)))
|
||||
return loadWebPImage(input);
|
||||
else {
|
||||
Image image = new Image(input);
|
||||
if (image.isError())
|
||||
throw image.getException();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(URL url) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(url);
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
String path = url.getPath();
|
||||
if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.')))
|
||||
return loadWebPImage(input);
|
||||
else {
|
||||
Image image = new Image(input);
|
||||
if (image.isError())
|
||||
throw image.getException();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
|
||||
*
|
||||
@@ -1041,4 +1103,9 @@ public final class FXUtils {
|
||||
|
||||
return String.format("#%02x%02x%02x", r, g, b);
|
||||
}
|
||||
|
||||
public static FileChooser.ExtensionFilter getImageExtensionFilter() {
|
||||
return new FileChooser.ExtensionFilter(i18n("extension.png"),
|
||||
IMAGE_EXTENSIONS.stream().map(ext -> "*." + ext).toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,6 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -387,13 +386,13 @@ public class GameCrashWindow extends Stage {
|
||||
gameDir.getStyleClass().setAll("two-line-item-second-large");
|
||||
gameDir.setTitle(i18n("game.directory"));
|
||||
gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath());
|
||||
runInFX(() -> FXUtils.installFastTooltip(gameDir, i18n("game.directory")));
|
||||
FXUtils.installFastTooltip(gameDir, i18n("game.directory"));
|
||||
|
||||
TwoLineListItem javaDir = new TwoLineListItem();
|
||||
javaDir.getStyleClass().setAll("two-line-item-second-large");
|
||||
javaDir.setTitle(i18n("settings.game.java_directory"));
|
||||
javaDir.setSubtitle(launchOptions.getJava().getBinary().toAbsolutePath().toString());
|
||||
runInFX(() -> FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory")));
|
||||
FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory"));
|
||||
|
||||
Label reasonTitle = new Label(i18n("game.crash.reason"));
|
||||
reasonTitle.getStyleClass().add("two-line-item-second-large-title");
|
||||
@@ -424,7 +423,7 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
JFXButton helpButton = FXUtils.newRaisedButton(i18n("help"));
|
||||
helpButton.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
runInFX(() -> FXUtils.installFastTooltip(helpButton, i18n("logwindow.help")));
|
||||
FXUtils.installFastTooltip(helpButton, i18n("logwindow.help"));
|
||||
|
||||
|
||||
toolBar.setPadding(new Insets(8));
|
||||
|
||||
@@ -32,7 +32,7 @@ public enum SVG {
|
||||
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("M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"),
|
||||
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"),
|
||||
@@ -59,6 +59,7 @@ public enum SVG {
|
||||
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"),
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 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;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public class TooltipInstaller {
|
||||
public static final TooltipInstaller INSTALLER;
|
||||
|
||||
static {
|
||||
TooltipInstaller installer = null;
|
||||
|
||||
try {
|
||||
installer = new NewInstaller();
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
installer = new OldInstaller();
|
||||
} catch (Throwable e2) {
|
||||
e2.addSuppressed(e);
|
||||
LOG.warning("Failed to initialize TooltipInstaller", e2);
|
||||
}
|
||||
}
|
||||
|
||||
INSTALLER = installer != null ? installer : new TooltipInstaller();
|
||||
}
|
||||
|
||||
TooltipInstaller() {
|
||||
}
|
||||
|
||||
public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
|
||||
// For Java 8
|
||||
private static final class OldInstaller extends TooltipInstaller {
|
||||
private static final Constructor<?> createTooltipBehavior;
|
||||
private static final Method installTooltipBehavior;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
createTooltipBehavior = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
createTooltipBehavior.setAccessible(true);
|
||||
installTooltipBehavior = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installTooltipBehavior.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {
|
||||
try {
|
||||
Object behavior = createTooltipBehavior.newInstance(showDelay, showDuration, hideDelay, false);
|
||||
installTooltipBehavior.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
LOG.warning("Failed to set tooltip show delay", e);
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For Java 9+
|
||||
private static final class NewInstaller extends TooltipInstaller {
|
||||
private static final MethodHandle setTooltipShowDelay;
|
||||
private static final MethodHandle setTooltipShowDuration;
|
||||
private static final MethodHandle setTooltipHideDelay;
|
||||
|
||||
static {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
MethodType methodType = MethodType.methodType(void.class, Duration.class);
|
||||
|
||||
setTooltipShowDelay = lookup.findVirtual(Tooltip.class, "setShowDelay", methodType);
|
||||
setTooltipShowDuration = lookup.findVirtual(Tooltip.class, "setShowDuration", methodType);
|
||||
setTooltipHideDelay = lookup.findVirtual(Tooltip.class, "setHideDelay", methodType);
|
||||
} catch (Exception e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {
|
||||
try {
|
||||
setTooltipShowDelay.invokeExact(tooltip, showDelay);
|
||||
setTooltipShowDuration.invokeExact(tooltip, showDuration);
|
||||
setTooltipHideDelay.invokeExact(tooltip, hideDelay);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to set tooltip show delay", e);
|
||||
}
|
||||
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class UpgradeDialog extends JFXDialogLayout {
|
||||
public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) {
|
||||
maxWidthProperty().bind(Controllers.getScene().widthProperty().multiply(0.7));
|
||||
maxHeightProperty().bind(Controllers.getScene().heightProperty().multiply(0.7));
|
||||
|
||||
setHeading(new Label(i18n("update.changelog")));
|
||||
setBody(new ProgressIndicator());
|
||||
|
||||
|
||||
@@ -42,10 +42,9 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
|
||||
public AccountListItemSkin(AccountListItem skinnable) {
|
||||
super(skinnable);
|
||||
@@ -118,10 +117,10 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
btnMove.getStyleClass().add("toggle-icon4");
|
||||
if (skinnable.getAccount().isPortable()) {
|
||||
btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global")));
|
||||
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
|
||||
} else {
|
||||
btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable")));
|
||||
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
|
||||
}
|
||||
spinnerMove.setContent(btnMove);
|
||||
right.getChildren().add(spinnerMove);
|
||||
@@ -143,7 +142,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
});
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
|
||||
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
|
||||
spinnerRefresh.setContent(btnRefresh);
|
||||
right.getChildren().add(spinnerRefresh);
|
||||
|
||||
@@ -160,7 +159,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
});
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload")));
|
||||
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
|
||||
spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty());
|
||||
spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin());
|
||||
spinnerUpload.setContent(btnUpload);
|
||||
@@ -170,9 +169,10 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
JFXButton btnCopyUUID = new JFXButton();
|
||||
SpinnerPane spinnerCopyUUID = new SpinnerPane();
|
||||
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));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid")));
|
||||
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
|
||||
spinnerCopyUUID.setContent(btnCopyUUID);
|
||||
right.getChildren().add(spinnerCopyUUID);
|
||||
|
||||
@@ -180,8 +180,8 @@ public 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.createIcon(Theme.blackFill(), -1, -1));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
|
||||
btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1));
|
||||
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
confirmServerPane.setActions(prevButton, cancelButton, finishButton);
|
||||
}
|
||||
|
||||
this.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
this.setContent(addServerPane, ContainerAnimations.NONE);
|
||||
|
||||
lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty());
|
||||
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
|
||||
@@ -198,7 +198,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
|
||||
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT);
|
||||
} else {
|
||||
LOG.warning("Failed to resolve auth server: " + url, exception);
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
||||
@@ -208,7 +208,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
}
|
||||
|
||||
private void onAddPrev() {
|
||||
this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT);
|
||||
}
|
||||
|
||||
private void onAddFinish() {
|
||||
|
||||
@@ -317,7 +317,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
|
||||
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
|
||||
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
|
||||
forgotpasswordLink.setExternalLink("https://www.minecraft.net/password/forgot");
|
||||
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
|
||||
JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings"));
|
||||
createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile");
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink);
|
||||
@@ -510,7 +510,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
|
||||
if (factory instanceof OfflineAccountFactory) {
|
||||
txtUsername.setPromptText(i18n("account.methods.offline.name.special_characters"));
|
||||
runInFX(() -> FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters")));
|
||||
FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters"));
|
||||
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
|
||||
@@ -28,9 +28,9 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.*;
|
||||
import moe.mickey.minecraft.skin.fx.SkinCanvas;
|
||||
import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning;
|
||||
import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
|
||||
import org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning;
|
||||
import org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.Skin;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
|
||||
@@ -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
|
||||
@@ -27,165 +27,225 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public enum ContainerAnimations {
|
||||
NONE(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c -> Collections.emptyList()),
|
||||
public enum ContainerAnimations implements AnimationProducer {
|
||||
NONE {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
FADE(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
FADE {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
FADE_IN(c -> {
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
FADE_IN {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)))),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
FADE_OUT(c -> {
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
FADE_OUT {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)))),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_IN(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
ZOOM_IN {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_OUT(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
(Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
ZOOM_OUT {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_LEFT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
SWIPE_LEFT {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_RIGHT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
SWIPE_RIGHT {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)));
|
||||
}
|
||||
},
|
||||
|
||||
SWIPE_LEFT_FADE_SHORT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
SWIPE_LEFT_FADE_SHORT {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 50, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
||||
@@ -194,19 +254,26 @@ public enum ContainerAnimations {
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), -50, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
||||
}
|
||||
},
|
||||
|
||||
SWIPE_RIGHT_FADE_SHORT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
SWIPE_RIGHT_FADE_SHORT {
|
||||
@Override
|
||||
public void init(AnimationHandler c) {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler c) {
|
||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), -50, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
||||
@@ -215,38 +282,12 @@ public enum ContainerAnimations {
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 50, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))));
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
||||
}
|
||||
};
|
||||
|
||||
private final AnimationProducer animationProducer;
|
||||
private ContainerAnimations opposite;
|
||||
|
||||
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
|
||||
this.animationProducer = new AnimationProducer() {
|
||||
@Override
|
||||
public void init(AnimationHandler handler) {
|
||||
init.accept(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler handler) {
|
||||
return animationProducer.apply(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AnimationProducer opposite() {
|
||||
return opposite != null ? opposite.getAnimationProducer() : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public AnimationProducer getAnimationProducer() {
|
||||
return animationProducer;
|
||||
}
|
||||
|
||||
public ContainerAnimations getOpposite() {
|
||||
return opposite;
|
||||
}
|
||||
|
||||
static {
|
||||
NONE.opposite = NONE;
|
||||
FADE.opposite = FADE;
|
||||
@@ -257,4 +298,15 @@ public enum ContainerAnimations {
|
||||
ZOOM_IN.opposite = ZOOM_OUT;
|
||||
ZOOM_OUT.opposite = ZOOM_IN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void init(AnimationHandler handler);
|
||||
|
||||
@Override
|
||||
public abstract List<KeyFrame> animate(AnimationHandler handler);
|
||||
|
||||
@Override
|
||||
public @Nullable ContainerAnimations opposite() {
|
||||
return opposite;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,8 @@ public class FontComboBox extends JFXComboBox<String> {
|
||||
private boolean loaded = false;
|
||||
|
||||
public FontComboBox() {
|
||||
setMinWidth(260);
|
||||
|
||||
styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\""));
|
||||
|
||||
setCellFactory(listView -> new JFXListCell<String>() {
|
||||
|
||||
@@ -17,7 +17,6 @@ import javafx.scene.layout.Priority;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
public class IconedTwoLineListItem extends HBox {
|
||||
@@ -29,13 +28,8 @@ public class IconedTwoLineListItem extends HBox {
|
||||
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final TwoLineListItem twoLineListItem = new TwoLineListItem();
|
||||
private final Lazy<JFXButton> externalLinkButton = new Lazy<>(() -> {
|
||||
JFXButton button = new JFXButton();
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
|
||||
button.setOnAction(e -> FXUtils.openLink(externalLink.get()));
|
||||
return button;
|
||||
});
|
||||
private JFXButton externalLinkButton;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final InvalidationListener observer;
|
||||
|
||||
@@ -52,7 +46,7 @@ public class IconedTwoLineListItem extends HBox {
|
||||
getChildren().clear();
|
||||
if (image.get() != null) getChildren().add(imageView);
|
||||
getChildren().add(twoLineListItem);
|
||||
if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(externalLinkButton.get());
|
||||
if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(getExternalLinkButton());
|
||||
}, image, externalLink);
|
||||
}
|
||||
|
||||
@@ -111,4 +105,14 @@ public class IconedTwoLineListItem extends HBox {
|
||||
public ImageView getImageView() {
|
||||
return imageView;
|
||||
}
|
||||
|
||||
public JFXButton getExternalLinkButton() {
|
||||
if (externalLinkButton == null) {
|
||||
externalLinkButton = new JFXButton();
|
||||
externalLinkButton.getStyleClass().add("toggle-icon4");
|
||||
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
|
||||
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
|
||||
}
|
||||
return externalLinkButton;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InputDialogPane extends JFXDialogLayout {
|
||||
@@ -68,6 +69,8 @@ public class InputDialogPane extends JFXDialogLayout {
|
||||
lblCreationWarning.setText(msg);
|
||||
});
|
||||
});
|
||||
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
|
||||
public CompletableFuture<String> getCompletableFuture() {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -45,9 +46,11 @@ public abstract class MDListCell<T> extends ListCell<T> {
|
||||
Region clippedContainer = (Region) listView.lookup(".clipped-container");
|
||||
setPrefWidth(0);
|
||||
if (clippedContainer != null) {
|
||||
maxWidthProperty().bind(clippedContainer.widthProperty());
|
||||
prefWidthProperty().bind(clippedContainer.widthProperty());
|
||||
minWidthProperty().bind(clippedContainer.widthProperty());
|
||||
DoubleBinding converted = clippedContainer.widthProperty().subtract(1);
|
||||
|
||||
maxWidthProperty().bind(converted);
|
||||
prefWidthProperty().bind(converted);
|
||||
minWidthProperty().bind(converted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
@@ -114,6 +113,7 @@ public final class MultiFileItem<T> extends VBox {
|
||||
public static class Option<T> {
|
||||
protected final String title;
|
||||
protected String subtitle;
|
||||
protected String tooltip;
|
||||
protected final T data;
|
||||
protected final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
protected final JFXRadioButton left = new JFXRadioButton();
|
||||
@@ -140,6 +140,11 @@ public final class MultiFileItem<T> extends VBox {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Option<T> setTooltip(String tooltip) {
|
||||
this.tooltip = tooltip;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return left.isSelected();
|
||||
}
|
||||
@@ -161,6 +166,8 @@ public final class MultiFileItem<T> extends VBox {
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
left.setToggleGroup(group);
|
||||
left.setUserData(data);
|
||||
if (StringUtils.isNotBlank(tooltip))
|
||||
FXUtils.installFastTooltip(left, tooltip);
|
||||
pane.setLeft(left);
|
||||
|
||||
if (StringUtils.isNotBlank(subtitle)) {
|
||||
@@ -268,8 +275,9 @@ public final class MultiFileItem<T> extends VBox {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
|
||||
return selector.getExtensionFilters();
|
||||
public FileOption<T> addExtensionFilter(FileChooser.ExtensionFilter filter) {
|
||||
selector.getExtensionFilters().add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -126,7 +126,7 @@ public class Navigator extends TransitionPane {
|
||||
if (obj instanceof AnimationProducer) {
|
||||
setContent(node, (AnimationProducer) obj);
|
||||
} else {
|
||||
setContent(node, ContainerAnimations.NONE.getAnimationProducer());
|
||||
setContent(node, ContainerAnimations.NONE);
|
||||
}
|
||||
|
||||
NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.NAVIGATED);
|
||||
|
||||
@@ -146,12 +146,12 @@ public class SpinnerPane extends Control {
|
||||
|
||||
observer = FXUtils.observeWeak(() -> {
|
||||
if (getSkinnable().getFailedReason() != null) {
|
||||
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(failedPane, ContainerAnimations.FADE);
|
||||
failedReasonLabel.setText(getSkinnable().getFailedReason());
|
||||
} else if (getSkinnable().isLoading()) {
|
||||
root.setContent(topPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(topPane, ContainerAnimations.FADE);
|
||||
} else {
|
||||
root.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(contentPane, ContainerAnimations.FADE);
|
||||
}
|
||||
}, getSkinnable().loadingProperty(), getSkinnable().failedReasonProperty());
|
||||
}
|
||||
|
||||
@@ -49,13 +49,10 @@ import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -180,23 +177,13 @@ public class DecoratorController {
|
||||
case CUSTOM:
|
||||
String backgroundImage = config().getBackgroundImage();
|
||||
if (backgroundImage != null)
|
||||
image = tryLoadImage(Paths.get(backgroundImage)).orElse(null);
|
||||
image = tryLoadImage(Paths.get(backgroundImage));
|
||||
break;
|
||||
case NETWORK:
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl));
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
image = new Image(input);
|
||||
if (image.isError()) {
|
||||
throw image.getException();
|
||||
}
|
||||
}
|
||||
image = FXUtils.loadImage(new URL(backgroundImageUrl));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
@@ -218,73 +205,57 @@ public class DecoratorController {
|
||||
* Load background image from bg/, background.png, background.jpg, background.gif
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.png"));
|
||||
Image image = randomImageIn(Paths.get("bg"));
|
||||
if (image != null)
|
||||
return image;
|
||||
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
image = tryLoadImage(Paths.get("background." + extension));
|
||||
if (image != null)
|
||||
return image;
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.jpg"));
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.gif"));
|
||||
}
|
||||
return image.orElseGet(() -> newBuiltinImage("/assets/img/background.jpg"));
|
||||
|
||||
return newBuiltinImage("/assets/img/background.jpg");
|
||||
}
|
||||
|
||||
private Optional<Image> randomImageIn(Path imageDir) {
|
||||
private @Nullable Image randomImageIn(Path imageDir) {
|
||||
if (!Files.isDirectory(imageDir)) {
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
try (Stream<Path> stream = Files.list(imageDir)) {
|
||||
candidates = stream
|
||||
.filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT)))
|
||||
.filter(Files::isReadable)
|
||||
.filter(it -> {
|
||||
String ext = getExtension(it).toLowerCase(Locale.ROOT);
|
||||
return ext.equals("png") || ext.equals("jpg") || ext.equals("gif");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list files in ./bg", e);
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
while (candidates.size() > 0) {
|
||||
while (!candidates.isEmpty()) {
|
||||
int selected = rnd.nextInt(candidates.size());
|
||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded.isPresent()) {
|
||||
Image loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded != null)
|
||||
return loaded;
|
||||
} else {
|
||||
else
|
||||
candidates.remove(selected);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
private @Nullable Image tryLoadImage(Path path) {
|
||||
if (!Files.isReadable(path))
|
||||
return Optional.empty();
|
||||
return null;
|
||||
|
||||
return tryLoadImage(path.toAbsolutePath().toUri().toString());
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(String url) {
|
||||
Image img;
|
||||
try {
|
||||
img = new Image(url);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return FXUtils.loadImage(path);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (img.getException() != null) {
|
||||
LOG.warning("Couldn't load background image", img.getException());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(img);
|
||||
}
|
||||
|
||||
// ==== Navigation ====
|
||||
@@ -457,7 +428,7 @@ public class DecoratorController {
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer());
|
||||
navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE);
|
||||
}
|
||||
|
||||
// ==== Authlib Injector DnD ====
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -29,11 +27,10 @@ import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
@@ -44,20 +41,14 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
private static final PseudoClass TRANSPARENT = PseudoClass.getPseudoClass("transparent");
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
|
||||
private final StackPane root, parent;
|
||||
private final StackPane titleContainer;
|
||||
private final Stage primaryStage;
|
||||
private final TransitionPane navBarPane;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean titleBarTransparent = true;
|
||||
private double mouseInitX, mouseInitY, stageInitX, stageInitY, stageInitWidth, stageInitHeight;
|
||||
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
@@ -69,14 +60,13 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
primaryStage = control.getPrimaryStage();
|
||||
|
||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
|
||||
Decorator skinnable = getSkinnable();
|
||||
root = new StackPane();
|
||||
root.getStyleClass().add("window");
|
||||
|
||||
StackPane shadowContainer = new StackPane();
|
||||
shadowContainer.getStyleClass().add("body");
|
||||
shadowContainer.setEffect(new DropShadow(BlurType.ONE_PASS_BOX, Color.rgb(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0));
|
||||
|
||||
parent = new StackPane();
|
||||
Rectangle clip = new Rectangle();
|
||||
@@ -174,11 +164,11 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
if (s.isAnimate()) {
|
||||
AnimationProducer animation;
|
||||
if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.NEXT) {
|
||||
animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT.getAnimationProducer();
|
||||
animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT;
|
||||
} else if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.PREVIOUS) {
|
||||
animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT.getAnimationProducer();
|
||||
animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT;
|
||||
} else {
|
||||
animation = ContainerAnimations.FADE.getAnimationProducer();
|
||||
animation = ContainerAnimations.FADE;
|
||||
}
|
||||
skinnable.setNavigationDirection(Navigation.NavigationDirection.START);
|
||||
navBarPane.setContent(node, animation);
|
||||
@@ -202,9 +192,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
StackPane pane = new StackPane(minus);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
btnMin.setGraphic(pane);
|
||||
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnMin.getStyleClass().add("jfx-decorator-button");
|
||||
btnMin.setOnAction(e -> skinnable.minimize());
|
||||
|
||||
@@ -310,13 +298,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
return navBar;
|
||||
}
|
||||
|
||||
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
||||
initX = mouseEvent.getScreenX();
|
||||
initY = mouseEvent.getScreenY();
|
||||
xOffset = mouseEvent.getSceneX();
|
||||
yOffset = mouseEvent.getSceneY();
|
||||
}
|
||||
|
||||
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x < root.getWidth() && x >= root.getWidth() - root.snappedLeftInset();
|
||||
}
|
||||
@@ -333,146 +314,139 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
return x >= 0 && x <= root.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean setStageWidth(double width) {
|
||||
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
||||
primaryStage.setWidth(width);
|
||||
initX = newX;
|
||||
return true;
|
||||
} else {
|
||||
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
||||
primaryStage.setWidth(titleContainer.getMinWidth());
|
||||
private void resizeStage(double newWidth, double newHeight) {
|
||||
if (newWidth < 0)
|
||||
newWidth = primaryStage.getWidth();
|
||||
if (newWidth < primaryStage.getMinWidth())
|
||||
newWidth = primaryStage.getMinWidth();
|
||||
if (newWidth < titleContainer.getMinWidth())
|
||||
newWidth = titleContainer.getMinWidth();
|
||||
|
||||
return false;
|
||||
}
|
||||
if (newHeight < 0)
|
||||
newHeight = primaryStage.getHeight();
|
||||
if (newHeight < primaryStage.getMinHeight())
|
||||
newHeight = primaryStage.getMinHeight();
|
||||
if (newHeight < titleContainer.getMinHeight())
|
||||
newHeight = titleContainer.getMinHeight();
|
||||
|
||||
// Width and height must be set simultaneously to avoid JDK-8344372 (https://github.com/openjdk/jfx/pull/1654)
|
||||
primaryStage.setWidth(newWidth);
|
||||
primaryStage.setHeight(newHeight);
|
||||
}
|
||||
|
||||
private boolean setStageHeight(double height) {
|
||||
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
||||
primaryStage.setHeight(height);
|
||||
initY = newY;
|
||||
return true;
|
||||
} else {
|
||||
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
||||
primaryStage.setHeight(titleContainer.getHeight());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
protected void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isFullScreen()) {
|
||||
updateInitMouseValues(mouseEvent);
|
||||
if (primaryStage.isResizable()) {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = root.getBoundsInParent();
|
||||
double diagonalSize = root.snappedLeftInset() + 10;
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.N_RESIZE);
|
||||
}
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.S_RESIZE);
|
||||
}
|
||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isFullScreen() && primaryStage.isResizable()) {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = root.getBoundsInParent();
|
||||
double diagonalSize = root.snappedLeftInset() + 10;
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.DEFAULT);
|
||||
root.setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.N_RESIZE);
|
||||
}
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.S_RESIZE);
|
||||
}
|
||||
} else {
|
||||
root.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
} else {
|
||||
root.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMouseReleased(MouseEvent mouseEvent) {
|
||||
private void onMouseReleased(MouseEvent mouseEvent) {
|
||||
getSkinnable().setDragging(false);
|
||||
}
|
||||
|
||||
protected void onMouseDragged(MouseEvent mouseEvent) {
|
||||
getSkinnable().setDragging(true);
|
||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) {
|
||||
this.newX = mouseEvent.getScreenX();
|
||||
this.newY = mouseEvent.getScreenY();
|
||||
double deltaX = this.newX - this.initX;
|
||||
double deltaY = this.newY - this.initY;
|
||||
Cursor cursor = root.getCursor();
|
||||
if (Cursor.E_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NE_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
private void onMouseDragged(MouseEvent mouseEvent) {
|
||||
if (!getSkinnable().isDragging()) {
|
||||
getSkinnable().setDragging(true);
|
||||
mouseInitX = mouseEvent.getScreenX();
|
||||
mouseInitY = mouseEvent.getScreenY();
|
||||
stageInitX = primaryStage.getX();
|
||||
stageInitY = primaryStage.getY();
|
||||
stageInitWidth = primaryStage.getWidth();
|
||||
stageInitHeight = primaryStage.getHeight();
|
||||
}
|
||||
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SE_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.S_RESIZE == cursor) {
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.W_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
if (primaryStage.isFullScreen() || !mouseEvent.isPrimaryButtonDown() || mouseEvent.isStillSincePress())
|
||||
return;
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
double dx = mouseEvent.getScreenX() - mouseInitX;
|
||||
double dy = mouseEvent.getScreenY() - mouseInitY;
|
||||
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
Cursor cursor = root.getCursor();
|
||||
if (getSkinnable().isAllowMove()) {
|
||||
if (cursor == Cursor.DEFAULT) {
|
||||
primaryStage.setX(stageInitX + dx);
|
||||
primaryStage.setY(stageInitY + dy);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
if (getSkinnable().isResizable()) {
|
||||
if (cursor == Cursor.E_RESIZE) {
|
||||
resizeStage(stageInitWidth + dx, -1);
|
||||
mouseEvent.consume();
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.N_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
} else if (cursor == Cursor.S_RESIZE) {
|
||||
resizeStage(-1, stageInitHeight + dy);
|
||||
mouseEvent.consume();
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (getSkinnable().isAllowMove()) {
|
||||
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
||||
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
} else if (cursor == Cursor.W_RESIZE) {
|
||||
resizeStage(stageInitWidth - dx, -1);
|
||||
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.N_RESIZE) {
|
||||
resizeStage(-1, stageInitHeight - dy);
|
||||
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.SE_RESIZE) {
|
||||
resizeStage(stageInitWidth + dx, stageInitHeight + dy);
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.SW_RESIZE) {
|
||||
resizeStage(stageInitWidth - dx, stageInitHeight + dy);
|
||||
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.NW_RESIZE) {
|
||||
resizeStage(stageInitWidth - dx, stageInitHeight - dy);
|
||||
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
|
||||
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.NE_RESIZE) {
|
||||
resizeStage(stageInitWidth + dx, stageInitHeight - dy);
|
||||
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public abstract class DecoratorTabPage extends DecoratorTransitionPage implement
|
||||
if (newValue.getNode() != null) {
|
||||
onNavigating(getCurrentPage());
|
||||
if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATING));
|
||||
navigate(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
navigate(newValue.getNode(), ContainerAnimations.FADE);
|
||||
onNavigated(getCurrentPage());
|
||||
if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATED));
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
displayer.navigateTo(page, nav);
|
||||
navigate(page, nav.getAnimation().getAnimationProducer());
|
||||
navigate(page, nav.getAnimation());
|
||||
|
||||
String prefix = category == null ? "" : category + " - ";
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
tab.select(newGameTab);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
@@ -121,8 +122,25 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
}
|
||||
|
||||
protected void onInstall() {
|
||||
controller.getSettings().put("name", txtName.getText());
|
||||
controller.onFinish();
|
||||
String name = txtName.getText();
|
||||
|
||||
// Check for non-ASCII characters.
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
controller.getSettings().put("name", name);
|
||||
controller.onFinish();
|
||||
}, () -> {
|
||||
// The user selects Cancel and does nothing.
|
||||
})
|
||||
.build());
|
||||
} else {
|
||||
controller.getSettings().put("name", name);
|
||||
controller.onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -149,10 +150,27 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
protected void onInstall() {
|
||||
if (!txtModpackName.validate()) return;
|
||||
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
|
||||
controller.getSettings().put(MODPACK_CHARSET, charset);
|
||||
controller.onFinish();
|
||||
String name = txtModpackName.getText();
|
||||
|
||||
// Check for non-ASCII characters.
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
controller.getSettings().put(MODPACK_NAME, name);
|
||||
controller.getSettings().put(MODPACK_CHARSET, charset);
|
||||
controller.onFinish();
|
||||
}, () -> {
|
||||
// The user selects Cancel and does nothing.
|
||||
})
|
||||
.build());
|
||||
} else {
|
||||
controller.getSettings().put(MODPACK_NAME, name);
|
||||
controller.getSettings().put(MODPACK_CHARSET, charset);
|
||||
controller.onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDescribe() {
|
||||
|
||||
@@ -21,12 +21,18 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
@@ -55,14 +61,20 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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;
|
||||
@@ -86,6 +98,11 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
private final VersionList<?> versionList;
|
||||
private CompletableFuture<?> executor;
|
||||
|
||||
private final TransitionPane toolbarPane;
|
||||
private final HBox searchBar;
|
||||
private final JFXTextField searchField;
|
||||
private boolean isSearching = false;
|
||||
|
||||
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
||||
this.title = title;
|
||||
this.gameVersion = gameVersion;
|
||||
@@ -136,6 +153,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
list.getStyleClass().add("jfx-list-view-float");
|
||||
VBox.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -163,7 +183,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
this.setCenter(root);
|
||||
|
||||
versionList = downloadProvider.getVersionListById(libraryId);
|
||||
if (versionList.hasType()) {
|
||||
boolean hasType = versionList.hasType();
|
||||
chkRelease.setManaged(hasType);
|
||||
chkRelease.setVisible(hasType);
|
||||
chkSnapshot.setManaged(hasType);
|
||||
chkSnapshot.setVisible(hasType);
|
||||
chkOld.setManaged(hasType);
|
||||
chkOld.setVisible(hasType);
|
||||
|
||||
if (hasType) {
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
} else {
|
||||
centrePane.getContent().setAll(list);
|
||||
@@ -178,7 +206,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)));
|
||||
|
||||
Holder<RemoteVersionListCell> lastCell = new Holder<>();
|
||||
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell));
|
||||
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, libraryId));
|
||||
|
||||
FXUtils.onClicked(list, () -> {
|
||||
if (list.getSelectionModel().getSelectedIndex() < 0)
|
||||
@@ -187,6 +215,50 @@ 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();
|
||||
}
|
||||
|
||||
@@ -210,7 +282,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@Override
|
||||
public void refresh() {
|
||||
VersionList<?> currentVersionList = versionList;
|
||||
root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(spinner, ContainerAnimations.FADE);
|
||||
executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> {
|
||||
if (exception == null) {
|
||||
List<RemoteVersion> items = loadVersions();
|
||||
@@ -218,7 +290,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
Platform.runLater(() -> {
|
||||
if (versionList != currentVersionList) return;
|
||||
if (currentVersionList.getVersions(gameVersion).isEmpty()) {
|
||||
root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(emptyPane, ContainerAnimations.FADE);
|
||||
} else {
|
||||
if (items.isEmpty()) {
|
||||
chkRelease.setSelected(true);
|
||||
@@ -227,14 +299,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
} else {
|
||||
list.getItems().setAll(items);
|
||||
}
|
||||
root.setContent(center, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(center, ContainerAnimations.FADE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.warning("Failed to fetch versions list", exception);
|
||||
Platform.runLater(() -> {
|
||||
if (versionList != currentVersionList) return;
|
||||
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(failedPane, ContainerAnimations.FADE);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,6 +337,37 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
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);
|
||||
@@ -272,8 +375,12 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
private final Holder<RemoteVersionListCell> lastCell;
|
||||
|
||||
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell) {
|
||||
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell, String libraryId) {
|
||||
this.lastCell = lastCell;
|
||||
if ("game".equals(libraryId)) {
|
||||
content.getExternalLinkButton().setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
|
||||
FXUtils.installFastTooltip(content.getExternalLinkButton(), i18n("wiki.tooltip"));
|
||||
}
|
||||
|
||||
pane.getStyleClass().add("md-list-cell");
|
||||
StackPane.setMargin(content, new Insets(10, 16, 10, 16));
|
||||
@@ -307,14 +414,19 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
case RELEASE:
|
||||
content.getTags().setAll(i18n("version.game.release"));
|
||||
content.setImage(VersionIconType.GRASS.getIcon());
|
||||
content.setExternalLink(i18n("wiki.version.game.release", remoteVersion.getGameVersion()));
|
||||
break;
|
||||
case SNAPSHOT:
|
||||
content.getTags().setAll(i18n("version.game.snapshot"));
|
||||
content.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
|
||||
content.setExternalLink(i18n("wiki.version.game.snapshot", remoteVersion.getGameVersion()));
|
||||
break;
|
||||
default:
|
||||
content.getTags().setAll(i18n("version.game.old"));
|
||||
content.setImage(VersionIconType.CRAFT_TABLE.getIcon());
|
||||
content.setExternalLink(null);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -339,6 +451,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
content.setSubtitle(remoteVersion.getGameVersion());
|
||||
else
|
||||
content.getTags().setAll(remoteVersion.getGameVersion());
|
||||
content.setExternalLink(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -129,17 +130,13 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
if (bg.isDirectory())
|
||||
zip.putDirectory(bg.toPath(), "bg");
|
||||
|
||||
File background_png = new File("background.png").getAbsoluteFile();
|
||||
if (background_png.isFile())
|
||||
zip.putFile(background_png, "background.png");
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
String fileName = "background." + extension;
|
||||
|
||||
File background_jpg = new File("background.jpg").getAbsoluteFile();
|
||||
if (background_jpg.isFile())
|
||||
zip.putFile(background_jpg, "background.jpg");
|
||||
|
||||
File background_gif = new File("background.gif").getAbsoluteFile();
|
||||
if (background_gif.isFile())
|
||||
zip.putFile(background_gif, "background.gif");
|
||||
File background = new File(fileName).getAbsoluteFile();
|
||||
if (background.isFile())
|
||||
zip.putFile(background, "background.png");
|
||||
}
|
||||
|
||||
zip.putFile(launcherJar, launcherJar.getFileName().toString());
|
||||
}
|
||||
@@ -195,7 +192,8 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
/* overrideJavaArgs */ true,
|
||||
/* overrideConsole */ true,
|
||||
/* overrideCommands */ true,
|
||||
/* overrideWindow */ true
|
||||
/* overrideWindow */ true,
|
||||
/* iconKey */ null // TODO
|
||||
), modpackFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -116,7 +117,7 @@ public final class AboutPage extends StackPane {
|
||||
return componentList;
|
||||
}
|
||||
|
||||
try (Reader reader = new InputStreamReader(input)) {
|
||||
try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {
|
||||
JsonArray array = JsonUtils.GSON.fromJson(reader, JsonArray.class);
|
||||
|
||||
for (JsonElement element : array) {
|
||||
|
||||
@@ -48,6 +48,8 @@ import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.io.File;
|
||||
@@ -289,6 +291,11 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
setHeading(new Label(i18n("java.download")));
|
||||
setBody(body);
|
||||
setActions(warningLabel, downloadButtonPane, cancelButton);
|
||||
if (platform.getOperatingSystem() == OperatingSystem.LINUX && platform.getArchitecture() == Architecture.RISCV64) {
|
||||
JFXHyperlink hyperlink = new JFXHyperlink(i18n("java.download.banshanjdk-8"));
|
||||
hyperlink.setExternalLink("https://www.zthread.cn/#product");
|
||||
getActions().add(0, hyperlink);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
|
||||
@@ -66,7 +66,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
gameTab.initializeIfNeeded();
|
||||
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
|
||||
@@ -66,7 +66,6 @@ import java.util.stream.IntStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
@@ -116,7 +115,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
announcementBox.getChildren().add(announcementCard);
|
||||
|
||||
announcementPane = new TransitionPane();
|
||||
announcementPane.setContent(announcementBox, ContainerAnimations.NONE.getAnimationProducer());
|
||||
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
|
||||
|
||||
getChildren().add(announcementPane);
|
||||
}
|
||||
@@ -224,7 +223,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(12);
|
||||
runInFX(() -> FXUtils.installFastTooltip(menuButton, i18n("version.switch")));
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(graphic);
|
||||
|
||||
launchPane.getChildren().setAll(launchButton, separator, menuButton);
|
||||
@@ -301,7 +300,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
public void hideAnnouncementPane() {
|
||||
if (announcementPane != null) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,11 +106,13 @@ public class PersonalizationPage extends StackPane {
|
||||
backgroundSublist.setHasSubtitle(true);
|
||||
|
||||
backgroundItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT),
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
.setTooltip(i18n("launcher.background.default.tooltip")),
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.classic"), EnumBackgroundImage.CLASSIC),
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.translucent"), EnumBackgroundImage.TRANSLUCENT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.background.choose"))
|
||||
.addExtensionFilter(FXUtils.getImageExtensionFilter())
|
||||
.bindBidirectional(config().backgroundImageProperty()),
|
||||
new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK)
|
||||
.setValidators(new URLValidator(true))
|
||||
|
||||
@@ -165,7 +165,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
downloadItem.setActionButtonVisible(false);
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
runInFX(() -> FXUtils.installFastTooltip(downloadItem, i18n("download.hint")));
|
||||
FXUtils.installFastTooltip(downloadItem, i18n("download.hint"));
|
||||
|
||||
// fifth item in left sidebar
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventHandler;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.scene.*;
|
||||
import javafx.scene.image.Image;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.scene.shape.Mesh;
|
||||
import javafx.scene.shape.MeshView;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.PixelReader;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.animation.Transition;
|
||||
import javafx.beans.value.WritableValue;
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
package moe.mickey.minecraft.skin.fx.animation;
|
||||
package org.jackhuang.hmcl.ui.skin.animation;
|
||||
|
||||
import javafx.util.Duration;
|
||||
import moe.mickey.minecraft.skin.fx.FunctionHelper;
|
||||
import moe.mickey.minecraft.skin.fx.SkinAnimation;
|
||||
import moe.mickey.minecraft.skin.fx.SkinCanvas;
|
||||
import moe.mickey.minecraft.skin.fx.SkinTransition;
|
||||
import org.jackhuang.hmcl.ui.skin.FunctionHelper;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinAnimation;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinTransition;
|
||||
|
||||
public final class SkinAniRunning extends SkinAnimation {
|
||||
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
package moe.mickey.minecraft.skin.fx.animation;
|
||||
package org.jackhuang.hmcl.ui.skin.animation;
|
||||
|
||||
import javafx.util.Duration;
|
||||
import moe.mickey.minecraft.skin.fx.FunctionHelper;
|
||||
import moe.mickey.minecraft.skin.fx.SkinAnimation;
|
||||
import moe.mickey.minecraft.skin.fx.SkinCanvas;
|
||||
import moe.mickey.minecraft.skin.fx.SkinTransition;
|
||||
import org.jackhuang.hmcl.ui.skin.FunctionHelper;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinAnimation;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinTransition;
|
||||
|
||||
public final class SkinAniWavingArms extends SkinAnimation {
|
||||
|
||||
@@ -27,6 +27,8 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -38,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -110,6 +113,9 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
Bindings.bindContent(listView.getItems(), skinnable.getItems());
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
center.setContent(listView);
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
@@ -64,7 +66,7 @@ import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
@@ -302,7 +304,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
lblGameVersion.visibleProperty().bind(hasVersion);
|
||||
gameVersionField.managedProperty().bind(hasVersion);
|
||||
gameVersionField.visibleProperty().bind(hasVersion);
|
||||
runInFX(() -> FXUtils.installFastTooltip(gameVersionField, i18n("search.enter")));
|
||||
FXUtils.installFastTooltip(gameVersionField, i18n("search.enter"));
|
||||
|
||||
FXUtils.onChangeAndOperate(getSkinnable().version, version -> {
|
||||
if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
@@ -494,6 +496,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback));
|
||||
});
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
listView.setCellFactory(x -> new FloatListCell<RemoteMod>(listView) {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
ImageView imageView = new ImageView();
|
||||
|
||||
@@ -62,7 +62,7 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DownloadPage extends Control implements DecoratorPage {
|
||||
@@ -241,14 +241,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod));
|
||||
descriptionPane.getChildren().add(openMcmodButton);
|
||||
openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")));
|
||||
FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod"));
|
||||
}
|
||||
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||
openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl());
|
||||
descriptionPane.getChildren().add(openUrlButton);
|
||||
openUrlButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage()));
|
||||
FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage());
|
||||
}
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
@@ -346,7 +346,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
FXUtils.onClicked(container, () -> Controllers.navigate(new DownloadPage(page, addon, version, callback)));
|
||||
FXUtils.onClicked(container, () -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(new DownloadPage(page, addon, version, callback));
|
||||
});
|
||||
getChildren().setAll(container);
|
||||
|
||||
if (addon != RemoteMod.BROKEN) {
|
||||
@@ -378,6 +381,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
HBox descPane = new HBox(8);
|
||||
descPane.setPadding(new Insets(0, 8, 0, 8));
|
||||
descPane.setAlignment(Pos.CENTER_LEFT);
|
||||
descPane.setMouseTransparent(true);
|
||||
|
||||
{
|
||||
StackPane graphicPane = new StackPane();
|
||||
@@ -443,7 +447,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
VBox box = new VBox(8);
|
||||
box.setPadding(new Insets(8));
|
||||
ModItem modItem = new ModItem(version, selfPage);
|
||||
FXUtils.onClicked(modItem, () -> fireEvent(new DialogCloseEvent()));
|
||||
modItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again
|
||||
box.getChildren().setAll(modItem);
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
@@ -486,6 +490,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
this.prefWidthProperty().bind(BindingMapping.of(Controllers.getStage().widthProperty()).map(w -> w.doubleValue() * 0.7));
|
||||
this.prefHeightProperty().bind(BindingMapping.of(Controllers.getStage().heightProperty()).map(w -> w.doubleValue() * 0.7));
|
||||
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
|
||||
private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) {
|
||||
@@ -505,7 +511,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
dependencies.put(dependency.getType(), list);
|
||||
}
|
||||
DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback);
|
||||
FXUtils.onClicked(dependencyModItem, () -> fireEvent(new DialogCloseEvent()));
|
||||
dependencies.get(dependency.getType()).add(dependencyModItem);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -34,15 +33,13 @@ import java.util.function.Consumer;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final Tooltip tooltip;
|
||||
private final ImageView imageView;
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
private Profile profile;
|
||||
@SuppressWarnings("unused")
|
||||
private Consumer<Event> onVersionIconChangedListener;
|
||||
|
||||
public GameAdvancedListItem() {
|
||||
tooltip = new Tooltip();
|
||||
|
||||
Pair<Node, ImageView> view = createImageView(null);
|
||||
setLeftGraphic(view.getKey());
|
||||
imageView = view.getValue();
|
||||
@@ -63,17 +60,13 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
}
|
||||
if (version != null && Profiles.getSelectedProfile() != null &&
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
setTitle(version);
|
||||
setSubtitle(null);
|
||||
setTitle(i18n("version.manage.manage"));
|
||||
setSubtitle(version);
|
||||
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
tooltip.setText(version);
|
||||
} else {
|
||||
Tooltip.uninstall(this,tooltip);
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
imageView.setImage(VersionIconType.DEFAULT.getIcon());
|
||||
tooltip.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
@@ -82,7 +81,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
btnUpgrade.setOnAction(e -> skinnable.update());
|
||||
btnUpgrade.getStyleClass().add("toggle-icon4");
|
||||
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")));
|
||||
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
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));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test")));
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
|
||||
right.getChildren().add(btnLaunch);
|
||||
}
|
||||
|
||||
@@ -105,7 +104,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));
|
||||
runInFX(() -> FXUtils.installFastTooltip(btnManage, i18n("settings.game.management")));
|
||||
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
|
||||
right.getChildren().add(btnManage);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null || result == null) {
|
||||
Controllers.dialog("Failed to check updates", "failed", MessageDialogPane.MessageType.ERROR);
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
} else if (result.isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.empty"));
|
||||
} else {
|
||||
|
||||
@@ -31,6 +31,8 @@ import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -70,6 +72,7 @@ import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -131,6 +134,8 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().getItems());
|
||||
});
|
||||
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
// Toolbar Normal
|
||||
@@ -168,6 +173,16 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
changeToolbar(toolbarSelecting);
|
||||
});
|
||||
root.getContent().add(toolbarPane);
|
||||
|
||||
// Clear selection when pressing ESC
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
if (listView.getSelectionModel().getSelectedItem() != null) {
|
||||
listView.getSelectionModel().clearSelection();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -186,6 +201,18 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
});
|
||||
|
||||
listView.setOnContextMenuRequested(event -> {
|
||||
ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) {
|
||||
listView.getSelectionModel().clearSelection();
|
||||
Controllers.dialog(new ModInfoDialog(selectedItem));
|
||||
}
|
||||
});
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it
|
||||
// So we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
center.setContent(listView);
|
||||
root.getContent().add(center);
|
||||
}
|
||||
@@ -204,7 +231,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
private void changeToolbar(HBox newToolbar) {
|
||||
Node oldToolbar = toolbarPane.getCurrentNode();
|
||||
if (newToolbar != oldToolbar) {
|
||||
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE.getAnimationProducer());
|
||||
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);
|
||||
if (newToolbar == searchBar) {
|
||||
searchField.requestFocus();
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
task.whenComplete(Schedulers.javafx(), exception -> {
|
||||
fireEvent(new PageCloseEvent());
|
||||
if (!task.getFailedMods().isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.failed") + "\n" +
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_download") + "\n" +
|
||||
task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")),
|
||||
i18n("install.failed"),
|
||||
MessageDialogPane.MessageType.ERROR);
|
||||
|
||||
@@ -72,7 +72,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
|
||||
private void exploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif"));
|
||||
chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter());
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
try {
|
||||
|
||||
@@ -78,7 +78,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
tab.select(versionSettingsTab);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
|
||||
@@ -57,10 +57,27 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.Lang.getTimer;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionSettingsPage extends StackPane implements DecoratorPage, VersionPage.VersionLoadable, PageAware {
|
||||
|
||||
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)
|
||||
return;
|
||||
memoryStatusUpdateTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Platform.runLater(() -> memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()));
|
||||
}
|
||||
};
|
||||
getTimer().scheduleAtFixedRate(memoryStatusUpdateTask, 0, 1000);
|
||||
}
|
||||
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(new State("", null, false, false, false));
|
||||
|
||||
private AdvancedVersionSettingPage advancedVersionSettingPage;
|
||||
@@ -92,7 +109,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final ImagePickerItem iconPickerItem;
|
||||
|
||||
private final ChangeListener<Collection<JavaRuntime>> javaListChangeListener;
|
||||
private final InvalidationListener specificSettingsListener;
|
||||
private final InvalidationListener usesGlobalListener;
|
||||
private final ChangeListener<Boolean> specificSettingsListener;
|
||||
private final InvalidationListener javaListener = any -> initJavaSubtitle();
|
||||
private boolean updatingJavaSetting = false;
|
||||
private boolean updatingSelectedJava = false;
|
||||
@@ -101,7 +119,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final IntegerProperty maxMemory = new SimpleIntegerProperty();
|
||||
private final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private final BooleanProperty modpack = new SimpleBooleanProperty();
|
||||
|
||||
public VersionSettingsPage(boolean globalSetting) {
|
||||
@@ -185,7 +202,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption = new MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null))
|
||||
.setChooserTitle(i18n("settings.game.java_directory.choose"));
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe"));
|
||||
javaCustomOption.addExtensionFilter(new FileChooser.ExtensionFilter("Java", "java.exe"));
|
||||
|
||||
javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> {
|
||||
List<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> options = new ArrayList<>();
|
||||
@@ -235,6 +252,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
VBox.setMargin(chkAutoAllocate, new Insets(0, 0, 8, 5));
|
||||
|
||||
HBox lowerBoundPane = new HBox(8);
|
||||
lowerBoundPane.setStyle("-fx-view-order: -1;"); // prevent the indicator from being covered by the progress bar
|
||||
lowerBoundPane.setAlignment(Pos.CENTER);
|
||||
VBox.setMargin(lowerBoundPane, new Insets(0, 0, 0, 16));
|
||||
{
|
||||
@@ -443,22 +461,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
rootPane.getChildren().add(componentList);
|
||||
|
||||
initialize();
|
||||
|
||||
specificSettingsListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
|
||||
|
||||
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
|
||||
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
cboProcessPriority.getItems().setAll(ProcessPriority.values());
|
||||
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
|
||||
enableSpecificSettings.addListener((a, b, newValue) -> {
|
||||
usesGlobalListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());
|
||||
specificSettingsListener = (a, b, newValue) -> {
|
||||
if (versionId == null) return;
|
||||
|
||||
// do not call versionSettings.setUsesGlobal(true/false)
|
||||
@@ -470,9 +474,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
profile.getRepository().globalizeVersionSetting(versionId);
|
||||
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
});
|
||||
};
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
|
||||
|
||||
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
|
||||
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
cboProcessPriority.getItems().setAll(ProcessPriority.values());
|
||||
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
|
||||
componentList.disableProperty().bind(enableSpecificSettings.not());
|
||||
|
||||
initMemoryStatusUpdateTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -519,7 +534,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
FXUtils.unbindEnum(cboProcessPriority);
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
||||
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
||||
lastVersionSetting.javaDirProperty().removeListener(javaListener);
|
||||
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
|
||||
@@ -528,6 +543,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirSublist.subtitleProperty().unbind();
|
||||
|
||||
enableSpecificSettings.removeListener(specificSettingsListener);
|
||||
|
||||
if (advancedVersionSettingPage != null) {
|
||||
advancedVersionSettingPage.unbindProperties();
|
||||
advancedVersionSettingPage = null;
|
||||
@@ -552,9 +569,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
|
||||
|
||||
versionSetting.usesGlobalProperty().addListener(specificSettingsListener);
|
||||
if (versionId != null)
|
||||
enableSpecificSettings.set(!versionSetting.isUsesGlobal());
|
||||
versionSetting.usesGlobalProperty().addListener(usesGlobalListener);
|
||||
enableSpecificSettings.addListener(specificSettingsListener);
|
||||
|
||||
javaItem.setToggleSelectedListener(newValue -> {
|
||||
if (javaItem.getSelectedData() == null || updatingSelectedJava)
|
||||
@@ -685,7 +703,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
version = null;
|
||||
} else {
|
||||
gameVersionNumber = GameVersionNumber.asGameVersion(repository.getGameVersion(versionId));
|
||||
version = repository.getVersion(versionId);
|
||||
version = repository.getResolvedVersion(versionId);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -83,7 +83,7 @@ public final class Versions {
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
Controllers.taskDialog(
|
||||
@@ -96,7 +96,7 @@ public final class Versions {
|
||||
} else {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}).executor(true),
|
||||
i18n("message.downloading"),
|
||||
|
||||
@@ -153,6 +153,6 @@ public final class NativePatcher {
|
||||
}
|
||||
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
|
||||
return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader");
|
||||
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copy from javafx.swing
|
||||
/*
|
||||
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.scene.image.*;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.awt.image.SinglePixelPackedSampleModel;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* This class provides utility methods for converting data types between
|
||||
* Swing/AWT and JavaFX formats.
|
||||
*
|
||||
* @since JavaFX 2.2
|
||||
*/
|
||||
public final class SwingFXUtils {
|
||||
private SwingFXUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshots the specified {@link BufferedImage} and stores a copy of
|
||||
* its pixels into a JavaFX {@link Image} object, creating a new
|
||||
* object if needed.
|
||||
* The returned {@code Image} will be a static snapshot of the state
|
||||
* of the pixels in the {@code BufferedImage} at the time the method
|
||||
* completes. Further changes to the {@code BufferedImage} will not
|
||||
* be reflected in the {@code Image}.
|
||||
* <p>
|
||||
* The optional JavaFX {@link WritableImage} parameter may be reused
|
||||
* to store the copy of the pixels.
|
||||
* A new {@code Image} will be created if the supplied object is null,
|
||||
* is too small or of a type which the image pixels cannot be easily
|
||||
* converted into.
|
||||
*
|
||||
* @param bimg the {@code BufferedImage} object to be converted
|
||||
* @param wimg an optional {@code WritableImage} object that can be
|
||||
* used to store the returned pixel data
|
||||
* @return an {@code Image} object representing a snapshot of the
|
||||
* current pixels in the {@code BufferedImage}.
|
||||
* @since JavaFX 2.2
|
||||
*/
|
||||
public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
|
||||
int bw = bimg.getWidth();
|
||||
int bh = bimg.getHeight();
|
||||
switch (bimg.getType()) {
|
||||
case BufferedImage.TYPE_INT_ARGB:
|
||||
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||
break;
|
||||
default:
|
||||
BufferedImage converted =
|
||||
new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
Graphics2D g2d = converted.createGraphics();
|
||||
g2d.drawImage(bimg, 0, 0, null);
|
||||
g2d.dispose();
|
||||
bimg = converted;
|
||||
break;
|
||||
}
|
||||
// assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
|
||||
if (wimg != null) {
|
||||
int iw = (int) wimg.getWidth();
|
||||
int ih = (int) wimg.getHeight();
|
||||
if (iw < bw || ih < bh) {
|
||||
wimg = null;
|
||||
} else if (bw < iw || bh < ih) {
|
||||
int[] empty = new int[iw];
|
||||
PixelWriter pw = wimg.getPixelWriter();
|
||||
PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();
|
||||
if (bw < iw) {
|
||||
pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0);
|
||||
}
|
||||
if (bh < ih) {
|
||||
pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wimg == null) {
|
||||
wimg = new WritableImage(bw, bh);
|
||||
}
|
||||
PixelWriter pw = wimg.getPixelWriter();
|
||||
DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();
|
||||
int[] data = db.getData();
|
||||
int offset = bimg.getRaster().getDataBuffer().getOffset();
|
||||
int scan = 0;
|
||||
SampleModel sm = bimg.getRaster().getSampleModel();
|
||||
if (sm instanceof SinglePixelPackedSampleModel) {
|
||||
scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
|
||||
}
|
||||
|
||||
PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
|
||||
PixelFormat.getIntArgbPreInstance() :
|
||||
PixelFormat.getIntArgbInstance());
|
||||
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
|
||||
return wimg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,16 +39,6 @@ public final class Locales {
|
||||
*/
|
||||
public static final SupportedLocale EN = new SupportedLocale(Locale.ROOT);
|
||||
|
||||
/**
|
||||
* Traditional Chinese
|
||||
*/
|
||||
public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE);
|
||||
|
||||
/**
|
||||
* Simplified Chinese
|
||||
*/
|
||||
public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE);
|
||||
|
||||
/**
|
||||
* Spanish
|
||||
*/
|
||||
@@ -64,23 +54,33 @@ public final class Locales {
|
||||
*/
|
||||
public static final SupportedLocale JA = new SupportedLocale(Locale.JAPANESE);
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH, ES, RU, JA);
|
||||
/**
|
||||
* Traditional Chinese
|
||||
*/
|
||||
public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE);
|
||||
|
||||
/**
|
||||
* Simplified Chinese
|
||||
*/
|
||||
public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE);
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ES, JA, RU, ZH_CN, ZH);
|
||||
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null) return DEFAULT;
|
||||
switch (name.toLowerCase(Locale.ROOT)) {
|
||||
case "en":
|
||||
return EN;
|
||||
case "es":
|
||||
return ES;
|
||||
case "ja":
|
||||
return JA;
|
||||
case "ru":
|
||||
return RU;
|
||||
case "zh":
|
||||
return ZH;
|
||||
case "zh_cn":
|
||||
return ZH_CN;
|
||||
case "es":
|
||||
return ES;
|
||||
case "ru":
|
||||
return RU;
|
||||
case "ja":
|
||||
return JA;
|
||||
default:
|
||||
return DEFAULT;
|
||||
}
|
||||
@@ -88,11 +88,11 @@ public final class Locales {
|
||||
|
||||
public static String getNameByLocale(SupportedLocale locale) {
|
||||
if (locale == EN) return "en";
|
||||
else if (locale == ZH) return "zh";
|
||||
else if (locale == ZH_CN) return "zh_CN";
|
||||
else if (locale == ES) return "es";
|
||||
else if (locale == RU) return "ru";
|
||||
else if (locale == JA) return "ja";
|
||||
else if (locale == ZH) return "zh";
|
||||
else if (locale == ZH_CN) return "zh_CN";
|
||||
else if (locale == DEFAULT) return "def";
|
||||
else throw new IllegalArgumentException("Unknown locale: " + locale);
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -33,6 +33,8 @@ case "$(uname -m)" in
|
||||
_HMCL_ARCH="arm64";;
|
||||
arm|arm32|aarch32|armv7*)
|
||||
_HMCL_ARCH="arm32";;
|
||||
riscv64)
|
||||
_HMCL_ARCH="riscv64";;
|
||||
loongarch64)
|
||||
_HMCL_ARCH="loongarch64";;
|
||||
*)
|
||||
@@ -56,17 +58,42 @@ else
|
||||
_HMCL_VM_OPTIONS="-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"
|
||||
fi
|
||||
|
||||
function show_warning_console() {
|
||||
echo -e "\033[1;31m$1\033[0m" >&2
|
||||
}
|
||||
|
||||
function show_warning_dialog() {
|
||||
if [ -n "$3" ]; then
|
||||
if [ -n "$(command -v xdg-open)" ]; then
|
||||
if [ -n "$(command -v zenity)" ]; then
|
||||
zenity --question --title="$1" --text="$2" && xdg-open "$3"
|
||||
elif [ -n "$(command -v kdialog)" ]; then
|
||||
kdialog --title "$1" --warningyesno "$2" && xdg-open "$3"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ -n "$(command -v zenity)" ]; then
|
||||
zenity --info --title="$1" --text="$2"
|
||||
elif [ -n "$(command -v kdialog)" ]; then
|
||||
kdialog --title "$1" --msgbox "$2"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function show_warning() {
|
||||
show_warning_console "$1: $2"
|
||||
show_warning_dialog "$1" "$2"
|
||||
}
|
||||
|
||||
# First, find Java in HMCL_JAVA_HOME
|
||||
if [ -n "${HMCL_JAVA_HOME+x}" ]; then
|
||||
if [ -x "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
exec "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
else
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。" 1>&2
|
||||
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
|
||||
show_warning "错误" "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
|
||||
else
|
||||
echo "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path." 1>&2
|
||||
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
|
||||
show_warning "Error" "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path.\nYou can visit the https://docs.hmcl.net/help.html page for help."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
@@ -97,6 +124,11 @@ case "$_HMCL_ARCH" in
|
||||
exec "$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
;;
|
||||
riscv64)
|
||||
if [ -x "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
exec "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
;;
|
||||
loongarch64)
|
||||
if [ -x "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
exec "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
@@ -114,6 +146,10 @@ if [ -x "$(command -v $_HMCL_JAVA_EXE_NAME)" ]; then
|
||||
exec $_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
|
||||
if [ "$_HMCL_OS" == "freebsd" ] && [ -x "$(command -v javavm)" ]; then
|
||||
exec javavm $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
|
||||
# Java not found
|
||||
|
||||
if [ "$_HMCL_OS" == "osx" ]; then
|
||||
@@ -123,28 +159,28 @@ else
|
||||
fi
|
||||
|
||||
case "$_HMCL_OS-$_HMCL_ARCH" in
|
||||
windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-loongarch64|macos-x86_64|macos-arm64)
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
|
||||
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
|
||||
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
|
||||
else
|
||||
echo "The Java runtime environment is required to run HMCL. " 1>&2
|
||||
echo "Please install Java and set the environment variables and try again." 1>&2
|
||||
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
|
||||
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
|
||||
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
|
||||
else
|
||||
echo "The Java runtime environment is required to run HMCL. " 1>&2
|
||||
echo "Please install Java and set the environment variables and try again." 1>&2
|
||||
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
|
||||
fi
|
||||
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"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
_HMCL_WARNING_MESSAGE="运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。"
|
||||
|
||||
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
|
||||
show_warning_console "错误: $_HMCL_WARNING_MESSAGE\n你可以前往此处下载:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
show_warning_dialog "错误" "$_HMCL_WARNING_MESSAGE\n\n是否要前往 Java 下载页面?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
else
|
||||
show_warning "错误" "$_HMCL_WARNING_MESSAGE\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
|
||||
fi
|
||||
else
|
||||
_HMCL_WARNING_MESSAGE="The Java runtime environment is required to run HMCL.\nPlease install Java and set the environment variables and try again."
|
||||
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
|
||||
show_warning_console "Error: $_HMCL_WARNING_MESSAGE\nYou can download it from here:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
show_warning_dialog "Error" "$_HMCL_WARNING_MESSAGE\n\nDo you want to go to the Java download page?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
else
|
||||
show_warning "Error" "$_HMCL_WARNING_MESSAGE\nYou can visit the https://docs.hmcl.net/help.html page for help."
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 1
|
||||
@@ -24,5 +24,8 @@
|
||||
-fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7);
|
||||
-fx-base-text-fill: white;
|
||||
|
||||
/* https://github.com/HMCL-dev/HMCL/pull/3423 */
|
||||
-fx-font-family: -fx-base-font-family;
|
||||
|
||||
-theme-thumb: rgba(92, 107, 192, 0.7);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Hello Minecraft! Launcher
|
||||
* 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
|
||||
* 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/>.
|
||||
*/
|
||||
.root {
|
||||
-fx-base-color: %base-color%;
|
||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-rippler-color: rgba(%base-red%, %base-green%, %base-blue%, 0.3);
|
||||
-fx-base-rippler-color: derive(%base-rippler-color%, 100%);
|
||||
-fx-base-disabled-text-fill: %disabled-font-color%;
|
||||
-fx-base-text-fill: %font-color%;
|
||||
|
||||
-theme-thumb: rgba(%base-red%, %base-green%, %base-blue%, 0.7);
|
||||
|
||||
%font%
|
||||
}
|
||||
@@ -1307,11 +1307,6 @@
|
||||
-fx-padding: 8;
|
||||
}
|
||||
|
||||
.body {
|
||||
-fx-border-radius: 5;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0);
|
||||
}
|
||||
|
||||
.debug-border {
|
||||
-fx-border-color: red;
|
||||
-fx-border-width: 1;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -18,31 +18,35 @@
|
||||
|
||||
# Contributors: zhixuan2333
|
||||
|
||||
about=About
|
||||
about=について
|
||||
about.copyright=著作権
|
||||
about.copyright.statement=Copyright © 2024 huangyuhui。
|
||||
about.author=Author
|
||||
about.author.statement=bilibili : @huanghongxun
|
||||
about.copyright.statement=Copyright © 2025 huangyuhui
|
||||
about.author=著者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
about.claim.statement=テキスト全体のリンクをクリックします。
|
||||
about.dependency=Dependencies
|
||||
about.claim.statement=全文はこのリンクをクリック
|
||||
about.dependency=依存関係
|
||||
about.legal=法的承認
|
||||
about.thanks_to=Thanks to
|
||||
about.thanks_to.bangbang93.statement=BMCLAPIダウンロードソースプロバイダー。
|
||||
about.thanks_to.contributors=問題、プルリクエストなどを介してこのプロジェクトに参加したすべての貢献者。
|
||||
about.thanks_to.contributors.statement=Hello Minecraftをサポートしてくれたオープンソースコミュニティに感謝します!ランチャー
|
||||
about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー。
|
||||
about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to=感謝します…
|
||||
about.thanks_to.bangbang93.statement=BMCLAPIダウンロードミラープロバイダー。寄付をご検討ください!
|
||||
about.thanks_to.burningtnt.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to.contributors=GitHubのすべての貢献者
|
||||
about.thanks_to.contributors.statement=HMCLをサポートしてくれたオープンソースコミュニティに感謝する
|
||||
about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー
|
||||
about.thanks_to.glavo.statement=HMCLのメンテナ
|
||||
about.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。
|
||||
about.thanks_to.mcmod=mcmod.cn
|
||||
about.thanks_to.mcmod.statement=Mod中国語の名前翻訳プロバイダー。
|
||||
about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー。
|
||||
about.thanks_to.zkitefly.statement=HMCLドキュメントメンテナ
|
||||
about.thanks_to.mcbbs=MCBBS (Minecraft中国語フォーラム)
|
||||
about.thanks_to.mcbbs.statement=中国本土ユーザー向けのmcbbs.netダウンロードミラープロバイダー(もう利用できません)
|
||||
about.thanks_to.mcmod=MCMod (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=様々なMODの簡体字中国語名前翻訳とウィキを提供する
|
||||
about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー
|
||||
about.thanks_to.shulkersakura.statement=HMCLロゴプロバイダー
|
||||
about.thanks_to.users=HMCLユーザーグループのメンバー
|
||||
about.thanks_to.users.statement=寄付、バグレポートなどに感謝します。
|
||||
about.thanks_to.yushijinhun.statement=authlib-インジェクターのサポート
|
||||
about.thanks_to.users.statement=寄付、バグレポートなどに感謝します
|
||||
about.thanks_to.yushijinhun.statement=authlib-injector関連のサポートを提供する
|
||||
about.open_source=オープンソース
|
||||
about.open_source.statement=GPL v3(https://github.com/HMCL-dev/HMCL/)
|
||||
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
|
||||
|
||||
account=アカウント
|
||||
account.cape=マント
|
||||
@@ -136,6 +140,7 @@ assets.index.malformed=アセットインデックスの形式が正しくあり
|
||||
button.cancel=キャンセル
|
||||
button.change_source=ダウンロードソースの変更
|
||||
button.clear=クリア
|
||||
button.copy_and_exit=コピーして終了
|
||||
button.delete=削除
|
||||
button.edit=編集
|
||||
button.install=インストール
|
||||
@@ -144,19 +149,23 @@ button.no=いいえ
|
||||
button.ok=OK
|
||||
button.refresh=更新
|
||||
button.remove=削除
|
||||
button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません!
|
||||
button.remove.confirm=本当に完全に削除しますか?この操作は元に戻せません!
|
||||
button.retry=リトライ
|
||||
button.save=保存
|
||||
button.save_as=名前を付けて保存
|
||||
button.select_all=すべて選択
|
||||
button.view=読む
|
||||
button.yes=はい
|
||||
|
||||
chat=グループチャット
|
||||
|
||||
color.recent=推奨
|
||||
color.custom=カスタムカラー
|
||||
|
||||
crash.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。
|
||||
crash.user_fault=OSまたはJava環境が正しくインストールされていない可能性があり、クラッシュする可能性があります。Javaランタイム環境またはコンピュータを確認してください。
|
||||
crash.NoClassDefFound=このソフトウェアの整合性を確認するか、Javaの更新をお試しください。
|
||||
crash.user_fault=JavaまたはOSの環境が壊れているため、ランチャーがクラッシュしました。JavaまたはOSが正しくインストールされているかご確認ください。
|
||||
|
||||
curse.category.0=すべて
|
||||
curse.category.0=全て
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/4471
|
||||
curse.category.4474=Sci-Fi
|
||||
@@ -350,12 +359,9 @@ game.crash.reason.entity=エンティティが原因でゲームを実行でき
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12以降は、現在インストールされているmodと互換性がありません。ファブリックを0.11.7にダウングレードする必要があります。
|
||||
game.crash.reason.fabric_warnings=Fabricはいくつかの警告を出します:\n%1$s
|
||||
game.crash.reason.modmixin_failure=mod インジェクションの失敗により、現在のゲームを続行できません。\nこれは通常、MOD にバグがあるか、現在の環境と互換性がないことを意味します。\n間違った mod のログを確認できます。
|
||||
game.crash.reason.file_or_content_verification_failed=一部のファイルまたはコンテンツの検証に失敗したため、現在のゲームに問題があります。\nこのバージョン (mod を含む) を削除して再ダウンロードするか、再ダウンロード時にプロキシを使用するなどしてください。
|
||||
game.crash.reason.mod_repeat_installation=現在のゲームには複数の同一の Mod が繰り返しインストールされており、各 Mod は 1 回しか表示できません。ゲームを開始する前に繰り返しの Mod を削除してください。
|
||||
game.crash.reason.forge_error=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。\n%1$s
|
||||
game.crash.reason.mod_resolution0=mod の問題により、現在のゲームを続行できません。 \n間違った mod のログを確認できます。
|
||||
game.crash.reason.mod_profile_causes_game_crash=mod プロファイルに問題があるため、現在のゲームを続行できません。 \n障害のある mod とその構成ファイルのログを確認できます。
|
||||
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。
|
||||
game.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \nそうでない場合は、<a href="https://www.java.com/download/">java.com (Java8)</a> または <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。
|
||||
game.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\nModファイル名は、英文の全半角の大文字と小文字(Aa ~ Zz)、数字(0 ~ 9)、横線(-)、アンダースコア(_)、点(.)のみを使用してください。\n上記のコンプライアンス文字をModフォルダに追加してください。
|
||||
game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。
|
||||
@@ -400,7 +406,6 @@ game.crash.reason.stacktrace=不明。[ログ]ボタンをクリックすると
|
||||
game.crash.reason.too_old_java=Java仮想マシンのバージョンが低すぎるため、ゲームを実行できません。\nゲーム設定でJava仮想マシンの新しいバージョン(%1$s)に切り替えて、ゲームを再起動する必要があります。そうでない場合は、オンラインでダウンロードできます。
|
||||
game.crash.reason.unknown=不明。「ログ」ボタンをクリックすると詳細を確認できます。
|
||||
game.crash.reason.unsatisfied_link_error=必要なネイティブライブラリがないため、ゲームを実行できません。\n不足しているネイティブライブラリ:%1$s。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。
|
||||
game.crash.reason.failed_to_load_a_library=ネイティブ ライブラリのロードに失敗したため、ゲームを実行できません。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。
|
||||
game.crash.title=ゲームがクラッシュしました
|
||||
game.directory=ゲームディレクトリ
|
||||
game.version=ゲームバージョン
|
||||
@@ -455,6 +460,36 @@ install.new_game.malformed=無効な名前
|
||||
install.select=操作を選択します
|
||||
install.success=正常にインストールされました
|
||||
|
||||
java.add=Javaの追加
|
||||
java.add.failed=このJavaは無効であるか、現在のプラットフォームと互換性がない。
|
||||
java.disable=無効化
|
||||
java.disable.confirm=本当にこのJavaを無効にしますか?
|
||||
java.disabled.management=無効なJava
|
||||
java.disabled.management.remove=このJavaをリストから削除する
|
||||
java.disabled.management.restore=有効化
|
||||
java.download=Javaをダウンロード
|
||||
java.download.load_list.failed=バージョンリストの読み込みに失敗しました
|
||||
java.download.more=その他のJavaディストリビューション
|
||||
java.download.prompt=ダウンロードしたいJavaのバージョンを選択してください:
|
||||
java.download.distribution=配布の種類
|
||||
java.download.version=バージョン
|
||||
java.download.packageType=パッケージの種類
|
||||
java.management=Javaの管理
|
||||
java.info.architecture=アーキテクチャ
|
||||
java.info.vendor=発行元
|
||||
java.info.version=バージョン
|
||||
java.info.disco.distribution=配布の種類
|
||||
java.install=Javaのインストール
|
||||
java.install.archive=場所
|
||||
java.install.failed.exists=この名前はすでに使用されている
|
||||
java.install.failed.invalid=このアーカイブは有効なJavaインストール・パッケージではないため、インストールできません。
|
||||
java.install.failed.unsupported_platform=このJavaは現在のプラットフォームと互換性がないため、インストールできません。
|
||||
java.install.name=名前
|
||||
java.install.warning.invalid_character=名前に不正な文字が含まれています
|
||||
java.reveal=Javaディレクトリを表示する
|
||||
java.uninstall=このJavaをアンインストールする
|
||||
java.uninstall.confirm=本当にこのJavaをアンインストールしますか?この操作は元に戻せません!
|
||||
|
||||
lang=日本語
|
||||
lang.default=システム言語を使用する
|
||||
|
||||
@@ -467,8 +502,8 @@ launch.advice.forge2760_liteloader=Forge 2760以降はLiteLoaderと互換性が
|
||||
launch.advice.forge28_2_2_optifine=Forge28.2.2以降のバージョンはOptiFineと互換性がありません。Forgeを28.2.1以前のバージョンにダウングレードすることを検討してください。
|
||||
launch.advice.java8_1_13=Minecraft 1.13以降は、Java8以降でのみ実行できます。
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13は、1.8.0_51より前のJava8でクラッシュする可能性があります。最新バージョンのJava8をインストールしてください。
|
||||
launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。
|
||||
ゲームを高速化するには、launch.advice.newer_java=Java8をお勧めします。多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。
|
||||
launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。ゲームを高速化するには、Java8をお勧めします。
|
||||
launch.advice.newer_java=多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。
|
||||
launch.advice.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMBであるため、ゲームがクラッシュする可能性があります。
|
||||
launch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。32ビットシステムの最大メモリ容量は1024MBです。
|
||||
launch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。
|
||||
@@ -500,13 +535,14 @@ launcher.agreement.hint=このソフトウェアを使用するには、EULAに
|
||||
launcher.background=背景画像
|
||||
launcher.background.classic=クラシック
|
||||
launcher.background.choose=背景画像ファイルを選択してください
|
||||
launcher.background.default=標準(ランチャーと同じディレクトリにある background.png/jpg/gif と bg フォルダから自動的に画像を取得します。)
|
||||
launcher.background.default=標準
|
||||
launcher.background.default.tooltip=ランチャーと同じディレクトリにある background.png/.jpg/.gif/.webp と bg フォルダから自動的に画像を取得します。
|
||||
launcher.background.network=ネットワーク
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=キャッシュ用のディレクトリ
|
||||
launcher.cache_directory.clean=クリアランス
|
||||
launcher.cache_directory.choose=キャッシュするディレクトリを選択します
|
||||
launcher.cache_directory.default=標準(%AppData%/.minecraft または ~/.minecraft)
|
||||
launcher.cache_directory.default=標準(%APPDATA%/.minecraft または ~/.minecraft)
|
||||
launcher.cache_directory.disabled=無効(常にゲームパスを使用する)
|
||||
launcher.cache_directory.invalid=無効なディレクトリ。デフォルト設定の復元。
|
||||
launcher.contact=お問い合わせ
|
||||
@@ -581,8 +617,6 @@ modpack.scan=このmodpackをスキャンしています
|
||||
modpack.task.install=Modpackのインポート
|
||||
modpack.task.install.error=このmodpackファイルは認識できません。CurseおよびMultiMCmodpackのみがサポートされています。
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Cursemodpackに関連するファイルをインストールします
|
||||
modpack.type.curse.tolerable_error=このmodpackのすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。
|
||||
modpack.type.curse.error=このmodpackをインストールできません。再試行してください。
|
||||
modpack.type.curse.not_found=必要なリソースの一部が欠落しているため、ダウンロードできませんでした。最新バージョンまたは他のmodpackを検討してください。
|
||||
modpack.type.manual.warning=このmodpackをインポートする代わりに、おそらくこのmodpackファイルを直接解凍する必要があります。そして、バンドルされたランチャーを使用してゲームを起動します。このmodpackは、ランチャーによってエクスポートされるのではなく、.minecraftディレクトリを圧縮することによって手動で作成されます。HMCLはこのmodpackのインポートを試みることができます、続行しますか?
|
||||
@@ -632,7 +666,8 @@ mods.add.success=mods %s が正常に追加されました。
|
||||
mods.category=Category
|
||||
mods.check_updates=更新を確認
|
||||
mods.check_updates.current_version=Current
|
||||
mods.check_updates.failed=一部のファイルのダウンロードに失敗しました
|
||||
mods.check_updates.failed_check=更新のチェックに失敗しました
|
||||
mods.check_updates.failed_download=一部のファイルのダウンロードに失敗しました
|
||||
mods.check_updates.file=ファイル
|
||||
mods.check_updates.source=Source
|
||||
mods.check_updates.target_version=Target
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,24 +20,28 @@
|
||||
|
||||
about=О лаунчере
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=Авторские права © 2024 huangyuhui.
|
||||
about.copyright.statement=Авторские права © 2025 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili: @huanghongxun
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
about.claim.statement=Кликните на ссылку, чтобы увидеть весь текст.
|
||||
about.dependency=Зависимости
|
||||
about.legal=Юридическое подтверждение
|
||||
about.thanks_to=Отдельная благодарность
|
||||
about.thanks_to.bangbang93.statement=За предоставление API скачивания BMCLAPI. Рассмотрите возможность пожертвования.
|
||||
about.thanks_to.bangbang93.statement=За предоставление зеркала загрузки BMCLAPI, пожалуйста, подумайте о пожертвовании!
|
||||
about.thanks_to.burningtnt.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.contributors=Всем участникам на GitHub
|
||||
about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, Hello Minecraft! Launcher не смог бы добраться так далеко.
|
||||
about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, HMCL не смог бы добраться так далеко.
|
||||
about.thanks_to.gamerteam.statement=За предоставление фонового изображения.
|
||||
about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.glavo.statement=Отвечает за поддержание HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.zkitefly.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.mcmod=mcmod.cn
|
||||
about.thanks_to.mcmod.statement=За предоставление перевода на китайский и вики для различных модов.
|
||||
about.thanks_to.zkitefly.statement=Отвечает за поддержание документации HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Китайский форум Minecraft)
|
||||
about.thanks_to.mcbbs.statement=За предоставление зеркала загрузки mcbbs.net для пользователей материкового Китая. (Больше недоступно)
|
||||
about.thanks_to.mcmod=MCMod (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=За предоставление упрощенного китайского перевода и вики для различных модов.
|
||||
about.thanks_to.red_lnn.statement=За предоставление фонового изображения.
|
||||
about.thanks_to.shulkersakura.statement=За предоставление логотипа для HMCL.
|
||||
about.thanks_to.users=Членам группы пользователей HMCL
|
||||
about.thanks_to.users.statement=Спасибо за пожертвования, отчёты об ошибках и так далее.
|
||||
about.thanks_to.yushijinhun.statement=За предоставление поддержки, связанной с authlib-injector.
|
||||
@@ -156,8 +160,11 @@ button.retry=Повторить снова
|
||||
button.save=Сохранить
|
||||
button.save_as=Сохранить как
|
||||
button.select_all=Выбрать все
|
||||
button.view=Просмотреть
|
||||
button.yes=Да
|
||||
|
||||
chat=Групповой чат
|
||||
|
||||
color.recent=Рекомендуемые
|
||||
color.custom=Пользовательский цвет
|
||||
|
||||
@@ -369,13 +376,10 @@ game.crash.reason.fabric_version_0_12=Fabric 0.12 (и выше) несовмес
|
||||
game.crash.reason.mixin_apply_mod_failed=Текущая игра не может продолжать работать из-за невозможности применения мода %1$s с помощью Mixin.\nВы можете попробовать удалить или обновить этот мод, чтобы решить проблему.
|
||||
game.crash.reason.fabric_warnings=Предупреждения от Fabric:\n%1$s
|
||||
game.crash.reason.modmixin_failure=Текущая игра не может продолжать работать из-за некоторых сбоев внедрения модов.\nОбычно это означает, что мод содержит ошибку или несовместим с текущей средой.\nВы можете проверить журнал на наличие неправильного мода.
|
||||
game.crash.reason.file_or_content_verification_failed=В текущей игре возникла проблема, поскольку не удалось проверить некоторые файлы или содержимое.\nПожалуйста, попробуйте удалить эту версию (включая моды) и загрузить заново, или попробуйте использовать прокси при повторной загрузке и т. д.
|
||||
game.crash.reason.mod_repeat_installation=В текущей игре неоднократно установлено несколько одинаковых модов, и каждый мод может появиться только один раз. Пожалуйста, удалите повторяющиеся моды перед запуском игры.
|
||||
game.crash.reason.need_jdk11=Текущая игра не может продолжать работать из-за неподходящей версии виртуальной машины Java.\nВам необходимо загрузить и установить Java 11, а также установить версию Java, начинающуюся с 11, в глобальных (конкретных) настройках игры.
|
||||
game.crash.reason.forge_error=Возможно, Forge предоставил сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.\n%1$s
|
||||
game.crash.reason.mod_resolution0=Текущая игра не может продолжать работать из-за некоторых проблем с модом.\nВы можете проверить журнал на наличие неправильного мода.
|
||||
game.crash.reason.mod_profile_causes_game_crash=Текущая игра не может продолжать работать из-за проблемы с профилем мода.\nВы можете проверить журнал на наличие неисправного мода и его конфигурационного файла.
|
||||
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge мог выдать сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.
|
||||
game.crash.reason.java_version_is_too_high=Текущая игра аварийно завершилась из-за того, что версия Java слишком высока и не может продолжать работать.\nПеред запуском игры переключитесь на более раннюю версию Java на вкладке "Путь к Java" в глобальных настройках игры или в настройках игры.\nЕсли нет, вы можете загрузить его с <a href="https://www.java.com/download/">java.com (Java8)</a> или <a href="https://bell- sw.com/pages/downloads">BellSoft Liberica Full JRE (Java17)</a> и другие платформы, чтобы загрузить и установить ее (перезапустите программу запуска после установки).
|
||||
game.crash.reason.mod_name=Текущая игра не может продолжать работать из - за проблем с именем файла Mod. Имя файла\nMod должно быть использовано только в английском языке в полном углу с большими буквами (Aa ~ Zz), цифрами (0 ~ 9), горизонтальными линиями (-), подчеркнутыми (_) и точками (.) .\nПожалуйста, добавьте символ соответствия в папку Mod для всех несовместимых имен файлов Mod.
|
||||
game.crash.reason.incomplete_forge_installation=Текущая игра не может быть продолжена из-за незавершенной установки Forge / NeoForge.\nУдалите и переустановите Forge / NeoForge в настройках версии - Автоматическая установка.
|
||||
@@ -420,7 +424,6 @@ game.crash.reason.stacktrace=Неизвестно. Вы можете просм
|
||||
game.crash.reason.too_old_java=Невозможно запустить игру из-за того, что версия виртуальной машины Java слишком низкая.\nYou need to switch to a newer version (%1$s) of Java virtual machine in the game settings and restart the game. If not, you can download it online.
|
||||
game.crash.reason.unknown=Неизвестно. Вы можете просмотреть подробности, кликнув на кнопку «Журналы».
|
||||
game.crash.reason.unsatisfied_link_error=Невозможно запустить игру из-за того, что отсутствуют необходимые встроенные библиотеки.\nОтсутствующая нативная библиотека: %1$s.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре.
|
||||
game.crash.reason.failed_to_load_a_library=Не удалось запустить игру, так как не удалось загрузить встроенную библиотеку.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре.
|
||||
game.crash.title=Игра вылетела
|
||||
game.directory=Путь игры
|
||||
game.version=Версия игры
|
||||
@@ -475,6 +478,36 @@ install.new_game.malformed=Недопустимое имя
|
||||
install.select=Выберите операцию
|
||||
install.success=Успешно установлено.
|
||||
|
||||
java.add=Добавить Java
|
||||
java.add.failed=Этот Java недопустим или несовместим с текущей платформой.
|
||||
java.disable=Отключить Java
|
||||
java.disable.confirm=Вы уверены, что хотите отключить эту Java?
|
||||
java.disabled.management=Отключенная Java
|
||||
java.disabled.management.remove=Удалить эту Java из списка
|
||||
java.disabled.management.restore=Включить эту Java
|
||||
java.download=Скачать Java
|
||||
java.download.load_list.failed=Не удалось загрузить список версий
|
||||
java.download.more=Больше дистрибутивов Java
|
||||
java.download.prompt=Выберите версию Java, которую вы хотите загрузить:
|
||||
java.download.distribution=Дистрибуция
|
||||
java.download.version=Версия
|
||||
java.download.packageType=Тип упаковки
|
||||
java.management=Управление Java
|
||||
java.info.architecture=Архитектура
|
||||
java.info.vendor=Поставщик
|
||||
java.info.version=Версия
|
||||
java.info.disco.distribution=Дистрибуция
|
||||
java.install=Установите Java
|
||||
java.install.archive=Расположение
|
||||
java.install.failed.exists=Это имя уже используется
|
||||
java.install.failed.invalid=Этот архив не является допустимым установочным пакетом Java, поэтому его установка невозможна.
|
||||
java.install.failed.unsupported_platform=Эта Java несовместима с текущей платформой, поэтому ее невозможно установить.
|
||||
java.install.name=Название
|
||||
java.install.warning.invalid_character=Неправильный символ в названии
|
||||
java.reveal=Откройте каталог Java
|
||||
java.uninstall=Удалить Java
|
||||
java.uninstall.confirm=Вы уверены, что хотите удалить эту Java? Это действие нельзя отменить!
|
||||
|
||||
lang=Русский
|
||||
lang.default=Использовать язык системы
|
||||
|
||||
@@ -524,13 +557,14 @@ launcher.agreement.hint=Нужно принять пользовательско
|
||||
launcher.background=Фоновое изображение
|
||||
launcher.background.choose=Выберите фоновое изображение
|
||||
launcher.background.classic=Классическое
|
||||
launcher.background.default=По умолчанию (или background.png/jpg/gif, или папку с изображениями)
|
||||
launcher.background.default=По умолчанию
|
||||
launcher.background.default.tooltip=или background.png/.jpg/.gif/.webp, или папку с изображениями
|
||||
launcher.background.network=По ссылке
|
||||
launcher.background.translucent=Полупрозрачный
|
||||
launcher.cache_directory=Папка с кэшем
|
||||
launcher.cache_directory.clean=Очистить кэш
|
||||
launcher.cache_directory.choose=Выберите папку для кэша
|
||||
launcher.cache_directory.default=По умолчанию (%AppData%/.minecraft или ~/.minecraft)
|
||||
launcher.cache_directory.default=По умолчанию (%APPDATA%/.minecraft или ~/.minecraft)
|
||||
launcher.cache_directory.disabled=Отключено
|
||||
launcher.cache_directory.invalid=Не удаётся создать папку для кэша, возвращаем к значению по умолчанию.
|
||||
launcher.contact=Связаться с нами
|
||||
@@ -606,8 +640,6 @@ modpack.scan=Разбор индекса модпака
|
||||
modpack.task.install=Установить модпак
|
||||
modpack.task.install.error=Не удаётся идентифицировать этот модпак. Поддерживаются только модпаки Curse, Modrinth, MultiMC и MCBBS.
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Установите файлы, связанные с модпаком Curse
|
||||
modpack.type.curse.tolerable_error=Мы не можем завершить скачивание всех файлов этого модпака. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью.
|
||||
modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.
|
||||
modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.
|
||||
modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?
|
||||
@@ -665,7 +697,8 @@ mods.add.success=%s был успешно установлен.
|
||||
mods.category=Категория
|
||||
mods.check_updates=Проверить обновления
|
||||
mods.check_updates.current_version=Текущая версия
|
||||
mods.check_updates.failed=Не удаётся скачать некоторые файлы.
|
||||
mods.check_updates.failed_check=Не удалось проверить обновления.
|
||||
mods.check_updates.failed_download=Не удаётся скачать некоторые файлы.
|
||||
mods.check_updates.file=Файл
|
||||
mods.check_updates.source=Источник
|
||||
mods.check_updates.target_version=Целевая версия
|
||||
@@ -712,7 +745,7 @@ world.import.failed=Не удаётся импортировать этот ми
|
||||
world.import.invalid=Не удаётся разобрать сохранение.
|
||||
world.info.title=Сведения о мире %s
|
||||
world.info.basic=Основные сведения
|
||||
world.info.allow_cheats=Разрешить читы
|
||||
world.info.allow_cheats=Разрешить команды/читы
|
||||
world.info.dimension.the_nether=Нижний мир
|
||||
world.info.dimension.the_end=Край
|
||||
world.info.difficulty=Сложность
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=關於
|
||||
about.copyright=著作權
|
||||
about.copyright.statement=著作權所有 © 2024 huangyuhui
|
||||
about.copyright.statement=著作權所有 © 2025 huangyuhui
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=使用者協議
|
||||
@@ -65,7 +65,7 @@ account.failed.injector_download_failure=無法下載 authlib-injector,請檢
|
||||
account.failed.invalid_credentials=你的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試。
|
||||
account.failed.invalid_password=密碼無效
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登入。
|
||||
account.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移,你需要使用 Microsoft 方式登入遷移後的帳戶。
|
||||
account.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移,你需要使用遷移後的 Microsoft 帳戶登入。
|
||||
account.failed.no_character=該帳戶沒有角色。
|
||||
account.failed.server_disconnected=無法訪問登入伺服器,帳戶資訊重新整理失敗。\n\
|
||||
你可以選取「再次重新整理帳戶」重新嘗試。\n\
|
||||
@@ -98,11 +98,11 @@ account.copy_uuid=複製該帳戶的 UUID
|
||||
account.methods=登入方式
|
||||
account.methods.authlib_injector=authlib-injector 登入
|
||||
account.methods.microsoft=Microsoft 帳戶
|
||||
account.methods.microsoft.birth=如何修改帳戶出生日期
|
||||
account.methods.microsoft.deauthorize=解除帳戶授權
|
||||
account.methods.microsoft.birth=如何變更帳戶出生日期
|
||||
account.methods.microsoft.deauthorize=移除應用存取權
|
||||
account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權,接下來啟動器還需要完成其餘登入步驟。你現在可以關閉本頁面了。
|
||||
account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登入。
|
||||
account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登入。
|
||||
account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。
|
||||
account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。
|
||||
account.methods.microsoft.error.missing_xbox_account=你的 Microsoft 帳戶尚未關聯 Xbox 帳戶,你必須先建立 Xbox 帳戶,才能登入遊戲。
|
||||
account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft: Java 版購買記錄。\n若已購買,則可能未建立遊戲檔案,請點擊上方連結建立。
|
||||
@@ -112,17 +112,17 @@ account.methods.microsoft.logging_in=登入中……
|
||||
account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱
|
||||
account.methods.microsoft.hint=請點擊「登入」按鈕,稍後複製此處顯示的代碼,在打開的登入網頁中完成登入過程。\n\
|
||||
\n\
|
||||
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方連結解除授權,然後等待令牌過期。\n\
|
||||
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\
|
||||
\n\
|
||||
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
account.methods.microsoft.manual=你的代碼為 <b>%1$s</b>,請點擊此處複製。\n\
|
||||
\n\
|
||||
點擊「登入」按鈕後,你應該在打開的登入網頁中完成登入過程。如果網頁沒有打開,你可以自行在瀏覽器中訪問 %2$s\n\
|
||||
\n\
|
||||
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方連結解除授權,然後等待令牌過期。\n\
|
||||
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\
|
||||
\n\
|
||||
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
account.methods.microsoft.profile=帳戶設定頁
|
||||
account.methods.microsoft.profile=編輯帳戶配置檔
|
||||
account.methods.microsoft.purchase=購買 Minecraft
|
||||
account.methods.forgot_password=忘記密碼
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,請下載官方版本進行登入。
|
||||
@@ -328,7 +328,7 @@ download.existing=檔案已存在,無法儲存。你可以將檔案儲存至
|
||||
download.external_link=打開下載網站
|
||||
download.failed=下載失敗:%1$s\n錯誤碼:%2$d
|
||||
download.failed.empty=沒有可供安裝的版本,按一下此處返回。
|
||||
download.failed.no_code=下載失敗:%s
|
||||
download.failed.no_code=下載失敗
|
||||
download.failed.refresh=載入版本清單失敗,按一下此處重試。
|
||||
download.game=新遊戲
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
@@ -510,6 +510,7 @@ install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換]
|
||||
install.modpack=安裝模組包
|
||||
install.name.invalid=名稱中包含非 ASCII 字元(如 Emoji 表情或中文字元)。\n建議修改名稱,名稱建議僅包含英文字母、數字和底線,以防啟動遊戲時出現問題。是否繼續安裝?
|
||||
install.new_game=安裝新實例
|
||||
install.new_game.already_exists=此實例已經存在,請重新命名
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
@@ -525,6 +526,7 @@ java.disabled.management=管理已禁用的 Java
|
||||
java.disabled.management.remove=從清單中移除此 Java
|
||||
java.disabled.management.restore=重新啟用此 Java
|
||||
java.download=下載 Java
|
||||
java.download.banshanjdk-8=下載 Banshan JDK 8
|
||||
java.download.load_list.failed=載入版本清單失敗
|
||||
java.download.more=更多發行版
|
||||
java.download.prompt=請選擇你要下載的 Java 版本:
|
||||
@@ -602,13 +604,14 @@ launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使
|
||||
launcher.background=背景圖片
|
||||
launcher.background.choose=選取背景圖片
|
||||
launcher.background.classic=經典
|
||||
launcher.background.default=預設 (自動尋找啟動器同目錄下的「background.png/.jpg/.gif」及「bg」目錄內的圖片)
|
||||
launcher.background.default=預設
|
||||
launcher.background.default.tooltip=自動尋找啟動器同目錄下的「background.png/.jpg/.gif/.webp」及「bg」目錄內的圖片
|
||||
launcher.background.network=網路
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=檔案下載快取目錄
|
||||
launcher.cache_directory.clean=清理
|
||||
launcher.cache_directory.choose=選取檔案下載快取目錄
|
||||
launcher.cache_directory.default=預設 ("%AppData%/.minecraft" 或 "~/.minecraft")
|
||||
launcher.cache_directory.default=預設 ("%APPDATA%/.minecraft" 或 "~/.minecraft")
|
||||
launcher.cache_directory.disabled=停用
|
||||
launcher.cache_directory.invalid=無法建立自訂的快取目錄,還原至預設設定
|
||||
launcher.contact=聯絡我們
|
||||
@@ -790,7 +793,8 @@ mods.channel.release=Release
|
||||
mods.check_updates=檢查模組更新
|
||||
mods.check_updates.current_version=目前版本
|
||||
mods.check_updates.empty=沒有需要更新的模組
|
||||
mods.check_updates.failed=部分檔案下載失敗
|
||||
mods.check_updates.failed_check=檢查更新失敗
|
||||
mods.check_updates.failed_download=部分檔案下載失敗
|
||||
mods.check_updates.file=檔案
|
||||
mods.check_updates.source=來源
|
||||
mods.check_updates.target_version=目標版本
|
||||
@@ -855,7 +859,7 @@ world.import.failed=無法匯入此世界: %s
|
||||
world.import.invalid=無法識別的存檔壓縮檔
|
||||
world.info.title=世界 [%s] - 世界資訊
|
||||
world.info.basic=基本資訊
|
||||
world.info.allow_cheats=允許指令
|
||||
world.info.allow_cheats=允許指令(作弊)
|
||||
world.info.dimension.the_nether=地獄
|
||||
world.info.dimension.the_end=末地
|
||||
world.info.difficulty=難易度
|
||||
@@ -1147,6 +1151,10 @@ version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或
|
||||
version.settings=遊戲設定
|
||||
version.update=更新模組包
|
||||
|
||||
wiki.tooltip=Minecraft Wiki 頁面
|
||||
wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-tw
|
||||
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-tw
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
wizard.finish=完成
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=关于
|
||||
about.copyright=版权
|
||||
about.copyright.statement=版权所有 © 2024 huangyuhui
|
||||
about.copyright.statement=版权所有 © 2025 huangyuhui
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=用户协议
|
||||
@@ -65,7 +65,7 @@ account.failed.injector_download_failure=无法下载 authlib-injector,可能
|
||||
account.failed.invalid_credentials=你的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试。
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移,你需要使用微软账户登录迁移后的账户。
|
||||
account.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移,你需要使用迁移后的微软账户登录。
|
||||
account.failed.no_character=该账户没有角色
|
||||
account.failed.server_disconnected=无法访问登录服务器,账户信息刷新失败。\n\
|
||||
你可以选择“再次刷新账户”重新尝试。\n\
|
||||
@@ -99,11 +99,11 @@ account.copy_uuid=复制该账户的 UUID
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=外置登录 (authlib-injector)
|
||||
account.methods.microsoft=微软账户
|
||||
account.methods.microsoft.birth=如何修改账户出生日期
|
||||
account.methods.microsoft.birth=如何更改账户出生日期
|
||||
account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成其余登录步骤。你现在可以关闭本页面了。
|
||||
account.methods.microsoft.deauthorize=解除账户授权
|
||||
account.methods.microsoft.error.add_family=请点击上方“账户设置页”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.add_family_probably=请点击上方“账户设置页”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.add_family=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.add_family_probably=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。
|
||||
account.methods.microsoft.error.missing_xbox_account=请点击上方“创建档案”关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案,请点击上方“创建档案”以创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
@@ -116,8 +116,8 @@ account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\
|
||||
\ 2. 在弹出的网页中输入 HMCL 显示的代码,并点击“允许访问”;\n\
|
||||
\ 3. 按照网站的提示登录;\n\
|
||||
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
|
||||
\ 5. 在网站提示“大功告成”后,只需等待账户完成添加即可。\n\
|
||||
若网站提示“出现错误”或账户添加失败时,请重新按照以上步骤重新添加。\n\
|
||||
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
|
||||
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
|
||||
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
|
||||
@@ -125,12 +125,12 @@ account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
|
||||
\ 2. 在弹出的网页中输入 <b>%1$s</b> (已自动复制),并点击“允许访问”;\n\
|
||||
\ 3. 按照网站的提示登录;\n\
|
||||
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
|
||||
\ 5. 在网站提示“大功告成”后,只需等待账户完成添加即可。\n\
|
||||
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
|
||||
若网站未能显示,请手动在浏览器中打开:%2$s\n\
|
||||
若网站提示“出现错误”或账户添加失败时,请重新按照以上步骤重新添加。\n\
|
||||
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
|
||||
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.profile=账户设置页
|
||||
account.methods.microsoft.profile=编辑账户个人信息
|
||||
account.methods.microsoft.purchase=购买 Minecraft
|
||||
account.methods.forgot_password=忘记密码
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,请下载官方版本来登录微软账户。
|
||||
@@ -337,7 +337,7 @@ download.existing=文件已存在,无法保存。你可以将文件保存至
|
||||
download.external_link=打开下载网站
|
||||
download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。
|
||||
download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.failed.no_code=下载失败: %s
|
||||
download.failed.no_code=下载失败
|
||||
download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.game=新游戏
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com)
|
||||
@@ -434,7 +434,7 @@ game.crash.reason.jdk_9=当前游戏由于 Java 版本过高,无法继续运
|
||||
game.crash.reason.jvm_32bit=当前游戏由于内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。<a href="https://bell-sw.com/pages/downloads/#downloads">下载 Java</a>\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以在“(全局/版本特定) 游戏设置 → 游戏内存”中关闭“自动分配内存”,并且把内存限制调节为 1024 MB 或以下。
|
||||
game.crash.reason.loading_crashed_forge=当前游戏由于模组“%1$s (%2$s)”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.loading_crashed_fabric=当前游戏由于模组“%1$s”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
|
||||
game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将虚拟内存设置调整为“自动管理所有驱动器分页文件大小”,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
|
||||
game.crash.reason.mac_jdk_8u261=当前游戏由于你所使用的 Forge/OptiFine 与 Java 冲突而崩溃。\n请尝试更新 Forge/OptiFine,或使用 Java 8u251 及更早版本启动。
|
||||
game.crash.reason.mod=当前游戏由于“%1$s”的问题,无法继续运行。\n你可以更新或删除已经安装的“%1$s”再试。
|
||||
game.crash.reason.mod_resolution=当前游戏由于模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s
|
||||
@@ -521,6 +521,7 @@ install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.installer.external_version=%s (由外部安装的版本,无法卸载或更换)
|
||||
install.modpack=安装整合包
|
||||
install.name.invalid=名称中包含非 ASCII 字符(如 Emoji 表情或中文字符)。\n建议修改名称,名称建议仅包含英文字母、数字和下划线,以防启动游戏时出现问题。是否继续安装?
|
||||
install.new_game=安装新游戏
|
||||
install.new_game.already_exists=此版本已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏版本
|
||||
@@ -536,6 +537,7 @@ java.disabled.management=管理已禁用的 Java
|
||||
java.disabled.management.remove=从列表中移除此 Java
|
||||
java.disabled.management.restore=重新启用此 Java
|
||||
java.download=下载 Java
|
||||
java.download.banshanjdk-8=下载 Banshan JDK 8
|
||||
java.download.load_list.failed=加载版本列表失败
|
||||
java.download.more=更多发行版
|
||||
java.download.prompt=请选择你要下载的 Java 版本:
|
||||
@@ -561,7 +563,7 @@ java.uninstall.confirm=你确定要卸载此 Java 吗?此操作无法撤销!
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice==%s 是否继续启动?
|
||||
launch.advice=%s 是否继续启动?
|
||||
launch.advice.multi=检测到以下问题:\n\n%s\n\n这些问题可能导致游戏无法正常启动或影响游戏体验,是否继续启动?\n你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本。\n或者你可以在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。
|
||||
@@ -613,13 +615,14 @@ launcher.agreement.hint=同意本软件的用户协议与免责声明以使用
|
||||
launcher.background=背景图片
|
||||
launcher.background.choose=选择背景图片
|
||||
launcher.background.classic=经典
|
||||
launcher.background.default=默认 (自动检索启动器同文件夹下的“background.png/.jpg/.gif”及“bg”文件夹内的图片)
|
||||
launcher.background.default=默认
|
||||
launcher.background.default.tooltip=自动检索启动器同文件夹下的“background.png/.jpg/.gif/.webp”及“bg”文件夹内的图片
|
||||
launcher.background.network=网络
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=文件下载缓存文件夹
|
||||
launcher.cache_directory.clean=清理缓存
|
||||
launcher.cache_directory.choose=选择文件下载缓存文件夹
|
||||
launcher.cache_directory.default=默认 ("%AppData%/.minecraft" 或 "~/.minecraft")
|
||||
launcher.cache_directory.default=默认 ("%APPDATA%/.minecraft" 或 "~/.minecraft")
|
||||
launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径)
|
||||
launcher.cache_directory.invalid=无法创建自定义的缓存文件夹,恢复默认设置
|
||||
launcher.contact=联系我们
|
||||
@@ -801,7 +804,8 @@ mods.channel.release=稳定版本
|
||||
mods.check_updates=检查模组更新
|
||||
mods.check_updates.current_version=当前版本
|
||||
mods.check_updates.empty=没有需要更新的模组
|
||||
mods.check_updates.failed=部分文件下载失败
|
||||
mods.check_updates.failed_check=检查更新失败
|
||||
mods.check_updates.failed_download=部分文件下载失败
|
||||
mods.check_updates.file=文件
|
||||
mods.check_updates.source=来源
|
||||
mods.check_updates.target_version=目标版本
|
||||
@@ -867,7 +871,7 @@ world.import.failed=无法导入此世界:%s
|
||||
world.import.invalid=无法识别该存档压缩包
|
||||
world.info.title=世界 [%s] - 世界信息
|
||||
world.info.basic=基本信息
|
||||
world.info.allow_cheats=允许作弊
|
||||
world.info.allow_cheats=允许命令(作弊)
|
||||
world.info.dimension.the_nether=下界
|
||||
world.info.dimension.the_end=末地
|
||||
world.info.difficulty=难度
|
||||
@@ -944,17 +948,17 @@ settings.advanced.modify=编辑高级设置
|
||||
settings.advanced.title=高级设置 - %s
|
||||
settings.advanced.custom_commands=自定义命令
|
||||
settings.advanced.custom_commands.hint=自定义命令被调用时将包含如下的环境变量:\n\
|
||||
\ · $INST_NAME:版本名称;\n\
|
||||
\ · $INST_ID:版本名称;\n\
|
||||
\ · $INST_DIR:当前版本运行路径;\n\
|
||||
\ · $INST_MC_DIR:当前游戏文件夹路径;\n\
|
||||
\ · $INST_JAVA:游戏运行使用的 Java 路径;\n\
|
||||
\ · $INST_FORGE:若安装了 Forge,将会存在本环境变量;\n\
|
||||
\ · $INST_NEOFORGE:若安装了 NeoForge,将会存在本环境变量;\n\
|
||||
\ · $INST_LITELOADER:若安装了 LiteLoader,将会存在本环境变量;\n\
|
||||
\ · $INST_OPTIFINE:若安装了 OptiFine,将会存在本环境变量;\n\
|
||||
\ · $INST_FABRIC:若安装了 Fabric,将会存在本环境变量;\n\
|
||||
\ · $INST_QUILT:若安装了 Quilt,将会存在本环境变量。
|
||||
\ · $INST_NAME: 版本名称;\n\
|
||||
\ · $INST_ID: 版本名称;\n\
|
||||
\ · $INST_DIR: 当前版本运行路径;\n\
|
||||
\ · $INST_MC_DIR: 当前游戏文件夹路径;\n\
|
||||
\ · $INST_JAVA: 游戏运行使用的 Java 路径;\n\
|
||||
\ · $INST_FORGE: 若安装了 Forge,将会存在本环境变量;\n\
|
||||
\ · $INST_NEOFORGE: 若安装了 NeoForge,将会存在本环境变量;\n\
|
||||
\ · $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量;\n\
|
||||
\ · $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量;\n\
|
||||
\ · $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量;\n\
|
||||
\ · $INST_QUILT: 若安装了 Quilt,将会存在本环境变量。
|
||||
settings.advanced.dont_check_game_completeness=不检查游戏完整性
|
||||
settings.advanced.dont_check_jvm_validity=不检查 JVM 与游戏的兼容性
|
||||
settings.advanced.dont_patch_natives=不尝试自动替换本地库
|
||||
@@ -1158,6 +1162,10 @@ version.manage.rename.fail=重命名版本失败,可能文件被占用或者
|
||||
version.settings=游戏设置
|
||||
version.update=更新整合包
|
||||
|
||||
wiki.tooltip=Minecraft Wiki 页面
|
||||
wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-cn
|
||||
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-cn
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失败
|
||||
wizard.finish=完成
|
||||
|
||||
@@ -3986,38 +3986,27 @@
|
||||
"com.mojang:text2speech:1.13.9:natives-linux": null
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"software-renderer-loader": {
|
||||
"name": "org.glavo:llvmpipe-loader:1.0",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar",
|
||||
"sha1": "ff255415e5c4b2a18970da0a8e552b557ca013ae",
|
||||
"size": 12964773
|
||||
}
|
||||
}
|
||||
},
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:0.2.0:x64",
|
||||
"name": "org.glavo:mesa-loader-windows:0.3.0:x64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar",
|
||||
"sha1": "adb4a16ecb95c8944a016829b05d70f934ea1a29",
|
||||
"size": 22823052
|
||||
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
|
||||
"sha1": "629fca32417d6ec489cef8b2cbd0827131ec6801",
|
||||
"size": 27174940
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"windows-x86": {
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:0.2.0:x86",
|
||||
"name": "org.glavo:mesa-loader-windows:0.3.0:x86",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar",
|
||||
"sha1": "93c0b9b7382d984e713d3aa299ceb337fde2897b",
|
||||
"size": 18273286
|
||||
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
|
||||
"sha1": "d25e0cdf5c5eb182acc9b93f700e0e6d8de36283",
|
||||
"size": 22528549
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4398,7 +4387,18 @@
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
"com.mojang:text2speech:1.12.4:natives": null,
|
||||
"com.mojang:text2speech:1.13.9:natives-windows": null
|
||||
"com.mojang:text2speech:1.13.9:natives-windows": null,
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:0.3.0:arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
|
||||
"sha1": "1986490c6fbe950e64018c2fb62c8ecf77a247ed",
|
||||
"size": 24082103
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"osx-arm64": {
|
||||
"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3:natives": {
|
||||
@@ -5274,7 +5274,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5296,7 +5296,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5318,7 +5318,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-openal:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5340,7 +5340,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5362,7 +5362,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5384,7 +5384,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-stb:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5406,7 +5406,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5428,7 +5428,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5450,7 +5450,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5472,7 +5472,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-openal:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5494,7 +5494,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5516,7 +5516,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5538,7 +5538,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-stb:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5560,7 +5560,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5571,6 +5571,182 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956",
|
||||
"size": 800127
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "610d14530e637564d97d74af7cb98a737e70b77b",
|
||||
"size": 96209
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4",
|
||||
"size": 46430
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac",
|
||||
"size": 157297
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a",
|
||||
"size": 113103
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79",
|
||||
"size": 597486
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af",
|
||||
"size": 931960
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "579071d2a3714f5662522f7d3edf58e941580587",
|
||||
"size": 81028
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"sha1": "7e46ecdec85db8738053cfde1414352cd62dab74",
|
||||
"size": 147044
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9",
|
||||
"size": 104415
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"sha1": "5821735d5ef23f6da8542887344e57eb181b7cac",
|
||||
"size": 143112
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "f5551338a1e2035ff747053f0e985dc93db1235c",
|
||||
"size": 226093
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a",
|
||||
"size": 15928
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5",
|
||||
"size": 40104
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
|
||||
"sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600",
|
||||
"size": 453489
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "67d6775292771087cb3d5ba1239bf9bf42fb3bd7",
|
||||
"size": 1176759
|
||||
}
|
||||
}
|
||||
},
|
||||
"net.java.jinput:jinput-platform:2.0.5:natives": null,
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
|
||||
@@ -82,25 +82,25 @@
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "e1744403a6691aaa6b65a7e9b4ff9aad98831bb1"
|
||||
"sha1": "73e422d8426aaa23e8c712b9f4d9bebf70d3bfb9"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "dbe733470094abee56560067d0fea3ad24991d2b"
|
||||
"sha1": "6ab6f3e23421fcfa04e692d9d26a8f55a830c114"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "0d43a776d6c9e2622e3581ed04501e2cadcd6e65"
|
||||
"sha1": "b7786b1b63e741c0e234829825fae5fef9d96c31"
|
||||
}
|
||||
],
|
||||
"osx-arm64": [
|
||||
@@ -108,25 +108,25 @@
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "d4dc7d2864c07b56a27e39fa5691856ddccf0ba4"
|
||||
"sha1": "1d0d887c492330ed527b0614d115a4f32d2d05a4"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "d6a49f19e90db56c134757d1bbab4935de075d45"
|
||||
"sha1": "64db28799e61e0f8f51e471c732599b2a6961803"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "ad564497edc49f7a6135c3b52d360962e19a2539"
|
||||
"sha1": "3a14bd5f3ebe45d344c1f2bade0fe074e6ea2a83"
|
||||
}
|
||||
],
|
||||
"linux-x86_64": [
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
package org.jackhuang.hmcl.ui.skin;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
@@ -8,7 +8,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.Stage;
|
||||
import moe.mickey.minecraft.skin.fx.test.Test;
|
||||
import org.jackhuang.hmcl.ui.skin.test.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
package moe.mickey.minecraft.skin.fx.test;
|
||||
package org.jackhuang.hmcl.ui.skin.test;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import moe.mickey.minecraft.skin.fx.FunctionHelper;
|
||||
import moe.mickey.minecraft.skin.fx.SkinCanvas;
|
||||
import moe.mickey.minecraft.skin.fx.SkinCanvasSupport;
|
||||
import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning;
|
||||
import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
|
||||
import org.jackhuang.hmcl.ui.skin.FunctionHelper;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
|
||||
import org.jackhuang.hmcl.ui.skin.SkinCanvasSupport;
|
||||
import org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning;
|
||||
import org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -6,14 +6,14 @@ dependencies {
|
||||
api("org.glavo:simple-png-javafx:0.3.0")
|
||||
api("com.google.code.gson:gson:2.11.0")
|
||||
api("com.moandjiezana.toml:toml4j:0.7.2")
|
||||
api("org.tukaani:xz:1.9")
|
||||
api("org.tukaani:xz:1.10")
|
||||
api("org.hildan.fxgson:fx-gson:5.0.0")
|
||||
api("org.jenkins-ci:constant-pool-scanner:1.2")
|
||||
api("com.github.steveice10:opennbt:1.5")
|
||||
api("org.nanohttpd:nanohttpd:2.3.1")
|
||||
api("org.apache.commons:commons-compress:1.25.0")
|
||||
api("org.jsoup:jsoup:1.18.1")
|
||||
compileOnlyApi("org.jetbrains:annotations:24.1.0")
|
||||
compileOnlyApi("org.jetbrains:annotations:26.0.1")
|
||||
|
||||
if (JavaVersion.current().isJava8) {
|
||||
org.gradle.internal.jvm.Jvm.current().toolsJar?.let {
|
||||
|
||||
@@ -109,7 +109,7 @@ public class OAuth {
|
||||
options.callback.openBrowser(deviceTokenResponse.verificationURI);
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
int interval = deviceTokenResponse.interval;
|
||||
long interval = TimeUnit.MILLISECONDS.convert(deviceTokenResponse.interval, TimeUnit.SECONDS);
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(Math.max(interval, 1));
|
||||
@@ -138,7 +138,7 @@ public class OAuth {
|
||||
}
|
||||
|
||||
if ("slow_down".equals(tokenResponse.error)) {
|
||||
interval += 5;
|
||||
interval += 5000;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
Version resolvedVersion = version.resolve(provider);
|
||||
String mainClass = resolvedVersion.getMainClass();
|
||||
return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass)
|
||||
|| mainClass.startsWith("net.minecraftforge")
|
||||
|| mainClass.startsWith("net.fabricmc")
|
||||
|| mainClass.startsWith("org.quiltmc")
|
||||
|| mainClass.startsWith("cpw.mods"));
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class GameJavaVersion {
|
||||
public static final GameJavaVersion LATEST = JAVA_21;
|
||||
|
||||
public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {
|
||||
if (gameVersion.compareTo("1.21") >= 0)
|
||||
if (gameVersion.compareTo("1.20.5") >= 0)
|
||||
return JAVA_21;
|
||||
if (gameVersion.compareTo("1.18") >= 0)
|
||||
return JAVA_17;
|
||||
@@ -59,21 +59,6 @@ public final class GameJavaVersion {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSupportedPlatform(Platform platform) {
|
||||
OperatingSystem os = platform.getOperatingSystem();
|
||||
Architecture arch = platform.getArchitecture();
|
||||
switch (arch) {
|
||||
case X86:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX;
|
||||
case X86_64:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX;
|
||||
case ARM64:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.OSX;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<GameJavaVersion> getSupportedVersions(Platform platform) {
|
||||
OperatingSystem operatingSystem = platform.getOperatingSystem();
|
||||
Architecture architecture = platform.getArchitecture();
|
||||
|
||||
@@ -62,7 +62,14 @@ public enum JavaVersionConstraint {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_17(false, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) {
|
||||
MODDED_JAVA_17(false, GameVersionNumber.between("1.18", "1.20.4"), VersionNumber.between("17", "17.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_21(false, GameVersionNumber.atLeast("1.20.5"), VersionNumber.between("21", "21.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
|
||||
@@ -18,25 +18,13 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.gson.JsonMap;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -229,7 +217,42 @@ public class Version implements Comparable<Version>, Validation {
|
||||
|
||||
public AssetIndexInfo getAssetIndex() {
|
||||
String assetsId = assets == null ? "legacy" : assets;
|
||||
return assetIndex == null ? new AssetIndexInfo(assetsId, Constants.DEFAULT_INDEX_URL + assetsId + ".json") : assetIndex;
|
||||
|
||||
if (assetIndex == null) {
|
||||
String hash;
|
||||
switch (assetsId) {
|
||||
case "1.8":
|
||||
hash = "f6ad102bcaa53b1a58358f16e376d548d44933ec";
|
||||
break;
|
||||
case "14w31a":
|
||||
hash = "10a2a0e75b03cfb5a7196abbdf43b54f7fa61deb";
|
||||
break;
|
||||
case "14w25a":
|
||||
hash = "32ff354a3be1c4dd83027111e6d79ee4d701d2c0";
|
||||
break;
|
||||
case "1.7.4":
|
||||
hash = "545510a60f526b9aa8a38f9c0bc7a74235d21675";
|
||||
break;
|
||||
case "1.7.10":
|
||||
hash = "1863782e33ce7b584fc45b037325a1964e095d3e";
|
||||
break;
|
||||
case "1.7.3":
|
||||
hash = "f6cf726f4747128d13887010c2cbc44ba83504d9";
|
||||
break;
|
||||
case "pre-1.6":
|
||||
hash = "3d8e55480977e32acd9844e545177e69a52f594b";
|
||||
break;
|
||||
case "legacy":
|
||||
default:
|
||||
assetsId = "legacy";
|
||||
hash = "770572e819335b6c0a053f8378ad88eda189fc14";
|
||||
}
|
||||
|
||||
String url = Constants.DEFAULT_INDEX_URL + hash + "/" + assetsId + ".json";
|
||||
return new AssetIndexInfo(assetsId, url);
|
||||
} else {
|
||||
return assetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean appliesToCurrentEnvironment() {
|
||||
|
||||
@@ -496,6 +496,13 @@ public class DefaultLauncher extends Launcher {
|
||||
break;
|
||||
case ZINK:
|
||||
env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||
/**
|
||||
* The amdgpu DDX is missing support for modifiers, causing Zink to fail.
|
||||
* Disable DRI3 to workaround this issue.
|
||||
*
|
||||
* Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093
|
||||
*/
|
||||
env.put("LIBGL_KOPPER_DRI2", "1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,12 +160,17 @@ public final class ForgeNewModMetadata {
|
||||
|
||||
private static ModLoaderType analyzeLoader(Toml toml, String modID, int loaderACC, ModLoaderType defaultLoader) throws IOException {
|
||||
List<HashMap<String, Object>> dependencies = toml.getList("dependencies." + modID);
|
||||
if (dependencies != null) {
|
||||
for (HashMap<String, Object> dependency : dependencies) {
|
||||
switch ((String) dependency.get("modId")) {
|
||||
case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE);
|
||||
case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED);
|
||||
}
|
||||
if (dependencies == null) {
|
||||
dependencies = toml.getList("dependencies"); // ??? I have no idea why some of the Forge mods use [[dependencies]]
|
||||
if (dependencies == null) {
|
||||
return defaultLoader;
|
||||
}
|
||||
}
|
||||
|
||||
for (HashMap<String, Object> dependency : dependencies) {
|
||||
switch ((String) dependency.get("modId")) {
|
||||
case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE);
|
||||
case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod.modrinth;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -27,6 +28,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -41,6 +43,7 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final DefaultGameRepository repository;
|
||||
private final ModManager modManager;
|
||||
private final String version;
|
||||
private ModrinthManifest manifest;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
@@ -69,6 +72,7 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version, ModrinthManifest manifest) {
|
||||
this.dependency = dependencyManager;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.modManager = repository.getModManager(version);
|
||||
this.version = version;
|
||||
this.manifest = manifest;
|
||||
|
||||
@@ -99,18 +103,28 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
if (manifest == null)
|
||||
return;
|
||||
|
||||
Path runDirectory = repository.getRunDirectory(version).toPath();
|
||||
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize();
|
||||
Path modsDirectory = runDirectory.resolve("mods");
|
||||
|
||||
for (ModrinthManifest.File file : manifest.getFiles()) {
|
||||
if (file.getEnv() != null && file.getEnv().getOrDefault("client", "required").equals("unsupported"))
|
||||
continue;
|
||||
Path filePath = runDirectory.resolve(file.getPath());
|
||||
if (!Files.exists(filePath) && !file.getDownloads().isEmpty()) {
|
||||
FileDownloadTask task = new FileDownloadTask(file.getDownloads().get(0), filePath.toFile());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
if (file.getDownloads().isEmpty())
|
||||
continue;
|
||||
|
||||
Path filePath = runDirectory.resolve(file.getPath()).toAbsolutePath().normalize();
|
||||
if (!filePath.startsWith(runDirectory))
|
||||
throw new IOException("Unsecure path: " + file.getPath());
|
||||
|
||||
if (Files.exists(filePath))
|
||||
continue;
|
||||
if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath)))
|
||||
continue;
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(file.getDownloads(), filePath.toFile());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
|
||||
+50
-24
@@ -58,43 +58,63 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
|
||||
private final boolean overrideConsole; // OverrideConsole
|
||||
private final boolean overrideCommands; // OverrideCommands
|
||||
private final boolean overrideWindow; // OverrideWindow
|
||||
private final String iconKey;
|
||||
|
||||
private final MultiMCManifest mmcPack;
|
||||
|
||||
MultiMCInstanceConfiguration(String defaultName, InputStream contentStream, MultiMCManifest mmcPack) throws IOException {
|
||||
// instance.cfg is in .ini format. As it's like the .properties format, we use Properties to parse thie file.
|
||||
// Maybe this will provide some problems (and it does, such as https://github.com/HMCL-dev/HMCL/issues/2991), but this workaround works, doesn't it?
|
||||
Properties p = new Properties();
|
||||
p.load(new InputStreamReader(contentStream, StandardCharsets.UTF_8));
|
||||
|
||||
this.mmcPack = mmcPack;
|
||||
|
||||
instanceType = p.getProperty("InstanceType");
|
||||
autoCloseConsole = Boolean.parseBoolean(p.getProperty("AutoCloseConsole"));
|
||||
instanceType = readValue(p, "InstanceType");
|
||||
autoCloseConsole = Boolean.parseBoolean(readValue(p, "AutoCloseConsole"));
|
||||
gameVersion = mmcPack != null ? mmcPack.getComponents().stream().filter(e -> "net.minecraft".equals(e.getUid())).findAny()
|
||||
.orElseThrow(() -> new IOException("Malformed mmc-pack.json")).getVersion() : p.getProperty("IntendedVersion");
|
||||
javaPath = p.getProperty("JavaPath");
|
||||
jvmArgs = p.getProperty("JvmArgs");
|
||||
fullscreen = Boolean.parseBoolean(p.getProperty("LaunchMaximized"));
|
||||
maxMemory = Lang.toIntOrNull(p.getProperty("MaxMemAlloc"));
|
||||
minMemory = Lang.toIntOrNull(p.getProperty("MinMemAlloc"));
|
||||
height = Lang.toIntOrNull(p.getProperty("MinecraftWinHeight"));
|
||||
width = Lang.toIntOrNull(p.getProperty("MinecraftWinWidth"));
|
||||
overrideCommands = Boolean.parseBoolean(p.getProperty("OverrideCommands"));
|
||||
overrideConsole = Boolean.parseBoolean(p.getProperty("OverrideConsole"));
|
||||
overrideJavaArgs = Boolean.parseBoolean(p.getProperty("OverrideJavaArgs"));
|
||||
overrideJavaLocation = Boolean.parseBoolean(p.getProperty("OverrideJavaLocation"));
|
||||
overrideMemory = Boolean.parseBoolean(p.getProperty("OverrideMemory"));
|
||||
overrideWindow = Boolean.parseBoolean(p.getProperty("OverrideWindow"));
|
||||
permGen = Lang.toIntOrNull(p.getProperty("PermGen"));
|
||||
postExitCommand = p.getProperty("PostExitCommand");
|
||||
preLaunchCommand = p.getProperty("PreLaunchCommand");
|
||||
showConsole = Boolean.parseBoolean(p.getProperty("ShowConsole"));
|
||||
showConsoleOnError = Boolean.parseBoolean(p.getProperty("ShowConsoleOnError"));
|
||||
wrapperCommand = p.getProperty("WrapperCommand");
|
||||
.orElseThrow(() -> new IOException("Malformed mmc-pack.json")).getVersion() : readValue(p, "IntendedVersion");
|
||||
javaPath = readValue(p, "JavaPath");
|
||||
jvmArgs = readValue(p, "JvmArgs");
|
||||
fullscreen = Boolean.parseBoolean(readValue(p, "LaunchMaximized"));
|
||||
maxMemory = Lang.toIntOrNull(readValue(p, "MaxMemAlloc"));
|
||||
minMemory = Lang.toIntOrNull(readValue(p, "MinMemAlloc"));
|
||||
height = Lang.toIntOrNull(readValue(p, "MinecraftWinHeight"));
|
||||
width = Lang.toIntOrNull(readValue(p, "MinecraftWinWidth"));
|
||||
overrideCommands = Boolean.parseBoolean(readValue(p, "OverrideCommands"));
|
||||
overrideConsole = Boolean.parseBoolean(readValue(p, "OverrideConsole"));
|
||||
overrideJavaArgs = Boolean.parseBoolean(readValue(p, "OverrideJavaArgs"));
|
||||
overrideJavaLocation = Boolean.parseBoolean(readValue(p, "OverrideJavaLocation"));
|
||||
overrideMemory = Boolean.parseBoolean(readValue(p, "OverrideMemory"));
|
||||
overrideWindow = Boolean.parseBoolean(readValue(p, "OverrideWindow"));
|
||||
permGen = Lang.toIntOrNull(readValue(p, "PermGen"));
|
||||
postExitCommand = readValue(p, "PostExitCommand");
|
||||
preLaunchCommand = readValue(p, "PreLaunchCommand");
|
||||
showConsole = Boolean.parseBoolean(readValue(p, "ShowConsole"));
|
||||
showConsoleOnError = Boolean.parseBoolean(readValue(p, "ShowConsoleOnError"));
|
||||
wrapperCommand = readValue(p, "WrapperCommand");
|
||||
name = defaultName;
|
||||
notes = Optional.ofNullable(p.getProperty("notes")).orElse("");
|
||||
notes = Optional.ofNullable(readValue(p, "notes")).orElse("");
|
||||
iconKey = readValue(p, "iconKey");
|
||||
}
|
||||
|
||||
public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow) {
|
||||
/**
|
||||
* A workaround for <a href="https://github.com/HMCL-dev/HMCL/issues/2991">...</a>
|
||||
*/
|
||||
private String readValue(Properties p, String key) {
|
||||
String value = p.getProperty(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int l = value.length();
|
||||
if (l >= 2 && value.charAt(0) == '"' && value.charAt(l - 1) == ':') {
|
||||
return value.substring(0, l - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow, String iconKey) {
|
||||
this.instanceType = instanceType;
|
||||
this.name = name;
|
||||
this.gameVersion = gameVersion;
|
||||
@@ -120,6 +140,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
|
||||
this.overrideCommands = overrideCommands;
|
||||
this.overrideWindow = overrideWindow;
|
||||
this.mmcPack = null;
|
||||
this.iconKey = iconKey;
|
||||
}
|
||||
|
||||
public String getInstanceType() {
|
||||
@@ -292,6 +313,10 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
|
||||
return overrideWindow;
|
||||
}
|
||||
|
||||
public String getIconKey() {
|
||||
return iconKey;
|
||||
}
|
||||
|
||||
public Properties toProperties() {
|
||||
Properties p = new Properties();
|
||||
if (instanceType != null) p.setProperty("InstanceType", instanceType);
|
||||
@@ -318,6 +343,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
|
||||
if (wrapperCommand != null) p.setProperty("WrapperCommand", wrapperCommand);
|
||||
if (name != null) p.setProperty("name", name);
|
||||
if (notes != null) p.setProperty("notes", notes);
|
||||
if (iconKey != null) p.setProperty("iconKey", iconKey);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,14 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
Path jarmods = root.resolve("jarmods");
|
||||
if (Files.exists(jarmods))
|
||||
FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods"));
|
||||
|
||||
String iconKey = this.manifest.getIconKey();
|
||||
if (iconKey != null) {
|
||||
Path iconFile = root.resolve(iconKey + ".png");
|
||||
if (Files.exists(iconFile)) {
|
||||
FileUtils.copyFile(iconFile, repository.getVersionRoot(name).toPath().resolve("icon.png"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.add(repository.saveAsync(version));
|
||||
|
||||
@@ -29,5 +29,5 @@ public final class Constants {
|
||||
|
||||
public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/";
|
||||
public static final String DEFAULT_VERSION_DOWNLOAD_URL = "https://bmclapi2.bangbang93.com/versions/";
|
||||
public static final String DEFAULT_INDEX_URL = "https://launchermeta.mojang.com/mc-staging/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/";
|
||||
public static final String DEFAULT_INDEX_URL = "https://launchermeta.mojang.com/v1/packages/";
|
||||
}
|
||||
@@ -389,7 +389,7 @@ public final class Lang {
|
||||
|
||||
public static synchronized Timer getTimer() {
|
||||
if (timer == null) {
|
||||
timer = new Timer();
|
||||
timer = new Timer(true);
|
||||
}
|
||||
return timer;
|
||||
}
|
||||
|
||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user