Compare commits

..
Author SHA1 Message Date
Glavo 0a230ef6b5 优化 Release Note (#3472) 2024-11-23 19:48:46 +08:00
Burning_TNT c12ae9fdf8 Fix #3460 无法识别奇异的 Forge (Like) 模组 Metadata (#3470) 2024-11-21 21:22:20 +08:00
Glavo 773ef4282c Bump action-gh-release to v2 (#3469) 2024-11-17 22:09:29 +08:00
Burning_TNTandYELANDAOKONG 9edfe29659 实现 #2969: 启动前日志列出mods目录的jar文件 (#3125)
* Support #2969: 启动前日志列出mods目录的jar文件

* Support #2969: 启动前日志列出mods目录jar文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 日志列出mods目录时不再过滤文件后缀

* Support #2969: 启动时日志列出mods目录(代码优化)

* Support #2969: 启动时日志列出mods目录(不再列出Zip内容)

* Support #2969

* Fix: checkstyle

---------

Co-authored-by: YELANDAOKONG <yelandaokong@yldk.xyz>
2024-11-16 23:29:53 +08:00
Burning_TNT 5591d92e25 Fix #2991: 导入 PrismLauncher 格式整合包时 $.jvmArgs 参数未正确移除两侧引号 (#3030) 2024-11-15 22:49:30 +08:00
Glavo 65bdb29e71 在 macOS 上使用 JavaFX 19.0.2.1 (#3461) 2024-11-15 18:02:59 +08:00
Glavo 355df211db 在游戏下载列表中添加 Minecraft Wiki 链接 (#3435) 2024-11-14 17:49:27 +08:00
3gf8jv4dv fa66549f53 Revise English (#3453)
- Adjust some translation styles again
- Fix some missed mistakes
2024-11-14 09:59:15 +08:00
3gf8jv4dv 6e48dbd6c6 Fix dialog title when modpack download fails (#3451)
* Fix dialog title when modpack download fails

* Update
2024-11-14 09:58:14 +08:00
Zkitefly e3c7278759 Update check-update.yml (#3452) 2024-11-14 09:42:43 +08:00
3gf8jv4dv dd1829d8ca Check word 其它 → 其他 when checking translation (#3455)
There is some controversy over the use of this word.
In order to ensure consistency in formal texts, we
should unify them to more tried-and-true and commonly
used usages.

By checking at build time, it lets the translator
or proofreader correct the word on his/her own without
any special attention from the reviewer.
2024-11-14 09:27:45 +08:00
3gf8jv4dv 60ac16a3ea Revise Chinese (#3454)
* Revise Chinese

- Simplified Chinese only
- Minor revision

* oops...
2024-11-13 22:14:55 +08:00
Glavo b400a9a3ef 使最小化按钮居中 (#3450)
* 修复最小化按钮未居中的问题

* update
2024-11-12 14:24:15 +08:00
3gf8jv4dv c7cc2d0733 Fix missing i18n when checking for updates fails (#3444)
* Fix missing i18n when checking for updates fails

* Revise translation
2024-11-11 19:20:28 +08:00
Glavo 861223e909 清理 GameJavaVersion (#3441)
* 清理 GameJavaVersion

* update
2024-11-11 02:30:41 +08:00
Glavo 70ecbc734e Fix #3438: 修复 Minecraft 1.20.2+Forge 默认图标错误的问题 (#3439) 2024-11-10 23:19:14 +08:00
ZkiteflyandGlavo 2ca7e7c830 Fix #3200: 启动时不应补全 Modrinth 整合包中已禁用的 mod (#3308)
* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* update

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-11-10 21:27:36 +08:00
Wulian 9a0db56ac3 修复 Copyright © 显示为“漏”的bug (#3437)
Fixes #3436

Specify UTF-8 charset when reading `deps.json` file in `AboutPage.java`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/HMCL-dev/HMCL/issues/3436?shareId=XXXX-XXXX-XXXX-XXXX).
2024-11-10 19:02:59 +08:00
Glavo c1ae5875ba 将 VERSION_ROOT 更新至 3.6 (#3431) 2024-11-10 13:01:50 +08:00
Glavo 1d0ae427bf Update JavaVersionConstraint: 1.20.5 requires Java 21 (#3434) 2024-11-09 19:39:09 +08:00
3gf8jv4dv 3dde2d19e2 Fix extra "=" in zh_CN launch.advice translation (#3430)
Closes #3429
2024-11-08 05:59:02 +08:00
3gf8jv4dv d5ec6d7b85 Revise i18n translation before releases (#3428)
- Fix some English grammar and text style
- Revise `Allow Cheats` to `Allow Commands/Cheats`
  - Due to the change from `Allow Cheats` to `Allow Commands` by Mojang in 24w04a.
  - https://minecraft.wiki/w/Java_Edition_24w04a
2024-11-07 21:37:59 +08:00
Glavo 13856b71a0 更新 HMCLauncher.exe (#3427) 2024-11-05 03:43:18 +08:00
Glavo c6658771ca 修复 HMCLauncher 无法正常识别 Windows on Arm 平台的问题 (#3425)
* update

* update

* update
2024-11-05 00:20:42 +08:00
Glavo 406f36584d 设置 FontComboBox 的最小宽度 (#3424) 2024-11-04 18:55:04 +08:00
Glavo 898750d058 修复对话框按钮自行移动的问题 (#3423)
* 修复对话框按钮自行移动的问题

* update

* update

* update
2024-11-04 18:41:14 +08:00
Glavo 09e9e934d3 Fix #3412: 修复点击前置模组时,模组下载对话框不会关闭的问题 (#3422) 2024-11-04 06:00:45 +08:00
Glavo 3d170267b7 Fix #3416: 修复打开游戏设置页时崩溃的问题 (#3421) 2024-11-04 05:34:28 +08:00
3gf8jv4dv 3ca7e3cf88 Fix incorrect i18n key and line breaks in es & ja (#3404)
* Fix incorrect i18n key and line breaks in es & ja

* fix
2024-11-03 17:03:41 +08:00
3gf8jv4dv 1378d1445a README & PLATFORM docs post-fixes (#3417) 2024-11-02 17:36:41 +08:00
3gf8jv4dv 16d304dbb4 en_US Post-fix (#3413)
* en_US Post-fix

Some minor fixes.

* fix `game.crash.feedback`

* oops...

* oops...

* Bunch of grammar fixes :/

* Another bunch fixes

* Minor fixes

* fix

* fix

* fix
2024-11-02 17:36:20 +08:00
3gf8jv4dv aff5db7759 Change the terminology %AppData% to %APPDATA% (#3420) 2024-11-02 02:27:54 +08:00
Glavo 0697aa9755 重命名包 moe.mickey.minecraft.skin.fx (#3419) 2024-11-02 01:05:52 +08:00
Glavo 995d3bd4b9 不再生成 bss 文件 (#3418) 2024-11-01 20:59:33 +08:00
Glavo 3bf5204e79 优化 Theme (#3414) 2024-11-01 02:12:16 +08:00
3gf8jv4dv 0b1ce6e94a Adjust language selection order (#3411)
Sort alphabetically by language code.
2024-10-31 23:30:44 +08:00
3gf8jv4dv a66ccd7385 Adjust Microsoft login related i18n (#3393)
* Adjust Microsoft login related i18n

- Adjust English and Chinese
- Fix typos and grammatical errors

* fix
2024-10-30 02:26:55 +08:00
3gf8jv4dv 7a3f125802 Adjust custom command hint (zh_CN only) (#3394)
I forgot to change the zh_CN part.
A half-width colon is better.
2024-10-30 02:22:34 +08:00
3gf8jv4dv 4a8dcce3d4 Adjust about page i18n (#3398)
* Adjust about page i18n

- Adjust all languages
- Add missing translations

* fix

* fix
2024-10-30 02:21:54 +08:00
3gf8jv4dv adcc3eb43f Adjust buttons, chat & crash i18n (#3401)
* Adjust buttons, chat & crash i18n

- Add missing translations

* fix

* fix

* fix

* fix ja

* fix es & ja
2024-10-30 02:20:39 +08:00
3gf8jv4dv 8bbd1a7cf4 Remove unused i18n keys in es, ja & ru (#3405) 2024-10-30 02:19:45 +08:00
Zkitefly 467ff26278 模组管理添加右键查看模组详情 (#3232)
* Update ModListPageSkin.java

* Update ModListPageSkin.java

* Update ModListPageSkin.java

* Update ModListPageSkin.java

* Update ModListPageSkin.java
2024-10-30 02:19:16 +08:00
Glavo d6a267afa4 优化 FXUtils.installTooltip (#3407)
* update

* Fix checkstyle

* update

* update

* update

* update

* update
2024-10-28 01:52:38 +08:00
Glavo 743e934436 优化“复制”图标 (#3406) 2024-10-28 00:29:29 +08:00
3gf8jv4dv f89ac2231a Adjust Java management i18n (#3402)
- Add missing languages.
2024-10-27 22:07:25 +08:00
3gf8jv4dv 1877a18ebb Fix en language missing a line break (#3403) 2024-10-27 22:07:03 +08:00
Glavo c99f85d9d5 缩小复制图标 (#3397)
* update

* update
2024-10-27 00:21:31 +08:00
Glavo 228942c60a 优化删除账号按钮图标 (#3396) 2024-10-26 23:49:43 +08:00
Glavo fecd567eab 简化 ContainerAnimations (#3395) 2024-10-26 23:34:41 +08:00
3gf8jv4dv 602840519b Change forgot password link (#3392) 2024-10-26 23:32:12 +08:00
959adf1075 更改主页 UI (#3129)
* Change root page UI

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

* Shorten I18N

* update

---------

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-26 21:02:34 +08:00
Glavo 422f681fe6 Bump XZ to 1.10 (#3391) 2024-10-26 20:54:48 +08:00
Glavo 474d298fd3 Bump Gradle to 8.10.2 (#3390) 2024-10-26 13:52:49 +08:00
3gf8jv4dv 4052136217 Adjust sponsor hint i18n (#3387)
- Adjust for all languages.
- For BMCLAPI: Make it clear that this is a service
  for users in Chinese Mainland.
- For HMCL: Since the page that was opened was not
  Afdian but the GitHub repo page, I made adjustments
  based on the actual situation.
- Fix some typos.
2024-10-26 02:51:19 +08:00
3gf8jv4dv 3193e02867 Adjust feedback page i18n (#3389)
- Adjust for all languages.
- Add missing translations to some languages.
2024-10-26 02:49:58 +08:00
Glavo c7122b3fff 优化首页公告 (#3378)
* update

* 添加公告隐藏动画

* update

* update
2024-10-26 02:45:58 +08:00
3gf8jv4dv 8775194d26 Adjust i18n for Linux/FreeBSD-specific settings in Advanced Settings (#3388)
I checked the code and it looked like this was a
Linux/FreeBSD-specific feature, so I adjusted the
translation for that.
2024-10-26 01:14:28 +08:00
3gf8jv4dv b3f27cc96e Adjust homepage hint typesetting (#3382)
- Adjustments made for all languages.
- Remove outdated hidden hint links.
2024-10-25 22:30:12 +08:00
3gf8jv4dv 3313a1ed27 For language selection, remove all region descriptions (#3383)
Currently, HMCL does not provide variants for
languages ​​(e.g. US, UK, etc.). For Chinese, there
are already descriptions of Simplified (简体) and
Traditional (繁體) to distinguish them, so keep them.
2024-10-25 22:05:19 +08:00
3gf8jv4dv da66622994 Adjust Microsoft login hint (zh_CN only) (#3384) 2024-10-25 22:04:48 +08:00
3gf8jv4dv 446cdc32d7 Adjust language file header comments (#3385)
- Update copyright year
- Unify typesetting style
2024-10-25 22:03:52 +08:00
3gf8jv4dv b08d2b0dc0 Remove some unnecessary tooltips (#3386)
* Remove some unnecessary tooltips

These texts are basically fixed, and the problem of
too long texts can be solved by revising the translation
for different languages.

Currently, for both Chinese and English, the i18n
keys involved in this PR will not be partially obscured
due to long text, so their tooltips are removed.

* fix checkstyle
2024-10-25 16:11:16 +08:00
Glavo e051c212d1 支持使用鼠标返回键返回上级 (#3381)
* 支持使用鼠标返回键返回上级

* fix checkstyle
2024-10-25 12:36:07 +08:00
Glavo 7645a600ef 修复部分控件响应任意鼠标按键点击事件的问题 (#3380)
* update

* update

* update

* update

* update

* update

* update

* update

* update
2024-10-25 12:24:57 +08:00
Glavo 9e56b5cd15 清理对 setOnMouseClicked 的误用 (#3379) 2024-10-25 04:08:40 +08:00
3gf8jv4dvandGlavo dcfcd1d8b2 Revise zh_TW part 3 (#3358)
* Revise zh_TW part 3

* Update

* Update

* fix typo

* fix

* fix

* oops...

* fix

* fix main page hint

* Update HMCL/src/main/resources/assets/lang/I18N_zh.properties

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-25 02:25:26 +08:00
3gf8jv4dv b2dc4b45c2 Revise zh_TW part 2 (#3357)
* Revise zh_TW part 2

* Update

* fix typo

* fix

* fix

* fix

* fix

When choosing a download source, the commas in the BMCLAPI item are changed to half-width.
2024-10-25 01:47:22 +08:00
0314921728 Revise en_US (#3367)
* Revise en_US

* fix

* fix

* Revise

Co-authored-by: Zkitefly <2573874409@qq.com>

* fix some English grammar

* another English grammar fixes

* fix fix fix...

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: Zkitefly <2573874409@qq.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-25 01:28:02 +08:00
Glavo 7d339677e4 调整公告中超链接的风格 (#3377)
* 调整 MessageDialogPane 中超链接的风格

* Fix checkstyle

* fix
2024-10-24 21:50:05 +08:00
3gf8jv4dv c9d259d7f1 zh_CN post-fix (#3376)
- When choosing a download source, the commas in the BMCLAPI item are changed to half-width.
- Remove extra line breaks in the home page hint.
2024-10-24 21:32:37 +08:00
Glavo eae2670855 支持在启动器内上传微软账号皮肤 (#3375)
* update

* Fix clearCache
2024-10-24 20:03:21 +08:00
3gf8jv4dvandGlavo e857f19b18 Revise zh_CN part 4 (#3349)
* Revise zh_CN part 4

* Update

* fix some typo

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-24 14:25:37 +08:00
Glavo a11816884c 创建 @FXThread 注解 (#3374) 2024-10-24 01:01:31 +08:00
3gf8jv4dv f686cb0ceb Update versions.txt (#3373) 2024-10-23 23:00:02 +08:00
Glavo 5aac4b82fc 优化 Java 下载功能 (#3371)
* update

* update

* update

* update
2024-10-23 21:49:35 +08:00
Glavo 99c3ef8a57 优化 Java 下载功能 (#3369)
* update

* update

* update
2024-10-20 22:26:38 +08:00
3gf8jv4dv 0a17aad9d6 Revise readme (#3362) 2024-10-20 21:36:01 +08:00
lijiaxuanand3gf8jv4dv 965995ad20 略微修改了一下英文版的readme.md (#3354)
* Updated English version of readme.md

修改了英文版的readme.md,并未增加或删除内容,只是让一些句子更加自然了一些

* Update README.md

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2024-10-20 21:35:27 +08:00
Glavo a5067cb1dd Bump JavaFX to 17.0.13 (#3364) 2024-10-20 21:33:55 +08:00
3gf8jv4dv 1f9c0736aa Capitalize choose player dialog title text (#3368) 2024-10-20 21:33:35 +08:00
Glavo a27671560d 支持下载 OpenJ9 (#3363)
* 支持下载 OpenJ9

* update
2024-10-19 22:59:40 +08:00
Glavo 5c767deaf3 优化 WorldInfoPage (#3361)
* update

* update

* update
2024-10-19 22:25:47 +08:00
3gf8jv4dv c157d4e0d1 Revise zh_TW part 4 (#3359) 2024-10-19 21:48:30 +08:00
3gf8jv4dv 655f42734a Revise zh_TW part 1 (#3356)
* Revise zh_TW part 1

* Update

* Update

* Update
2024-10-19 21:41:54 +08:00
3gf8jv4dv 55b01ab101 Revise zh_CN part 3 (#3348)
* Revise zh_CN part 3

* Update
2024-10-19 21:35:57 +08:00
3gf8jv4dv c6ed42d3e6 Revise zh_CN part 2 (#3346)
* Revise zh_CN part 2

* Update

* Oops...
2024-10-19 21:35:22 +08:00
0562051989 Fix mod update channel i18n (#3331)
* Fix mod update channel i18n

* Update

Co-authored-by: Burning_TNT <pangyl08@163.com>

* update

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-19 21:34:11 +08:00
3gf8jv4dvandBurning_TNT f68e056e47 Revise zh_CN part 1 (#3343)
* Revise zh_CN part 1

* Update

Co-authored-by: Burning_TNT <pangyl08@163.com>

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
2024-10-19 21:28:03 +08:00
Burning_TNTandGlavo 457490276c Update CallerFinder (#3350)
* Update CallerFinder

* Revert "Fix #2687 (#2688)"

This reverts commit 207b4d0033.

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-18 02:17:47 +08:00
Glavo 49ea9e5da0 fix checkstyle (#3353) 2024-10-17 12:48:17 +08:00
Glavo 9f3fb7aff6 修复无法安装 zip 格式的 Java 压缩包的问题 (#3352) 2024-10-17 04:20:09 +08:00
Glavo 1033389c38 Revert "Bump commons-compress to 1.27.1 (#3344)" (#3351)
This reverts commit fab32a6d51.
2024-10-16 22:15:13 +08:00
Glavo 58e32c4459 当整合包没有描述时,隐藏查看整合包描述按钮 (#3347) 2024-10-15 19:56:32 +08:00
Glavo 936285847c Bump Gson to 2.11.0 (#3345) 2024-10-15 19:10:02 +08:00
Glavo fab32a6d51 Bump commons-compress to 1.27.1 (#3344) 2024-10-15 19:05:22 +08:00
Glavo 5b345affec 优化 Java 下载功能 (#3342)
* update

* update
2024-10-15 18:47:57 +08:00
3gf8jv4dv 32c3b694b3 Revise readme and platform docs [skip ci] (#3054)
* Revise [skip ci]

Squash previous commits.

* Revise [skip ci]

Unify badge style.

* Revise [skip ci]

Change the badge style of QQ group

* Revise [skip ci]

- Add Chinese variants (Traditional, Taiwan)
- Compilation requirements for Java bump to 11+
- Various revisions

* Revise [skip ci]

Fix a link

* Revise [skip ci]

Revert a change

* Revise [skip ci]

* Revise [skip ci]

* Revise [skip ci]
2024-10-15 18:47:38 +08:00
3gf8jv4dv 4dce13855a Fix some i18n key mistake for es, ja & ru (#3341) 2024-10-15 18:26:12 +08:00
Burning_TNTandGlavo 1ce9858893 Support #2992: 检查模组更新界面加入全选/取消全选复选框 (#3032)
Support #2992

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-15 04:06:40 +08:00
Glavo 71c9632d61 GitHub Action 使用 Java 8 构建 HMCL (#3340)
* update

* update
2024-10-15 03:13:35 +08:00
3gf8jv4dv cce17edaa6 Capitalize text above the sidebar line (#3336) 2024-10-15 01:24:44 +08:00
3gf8jv4dvandGlavo deeef8f67a Download source and thread setting titles i18n key (#3338)
* Change the download source and thread setting titles to the appropriate i18n key

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-15 01:19:11 +08:00
3gf8jv4dv 25c76a748f Fix alignment of host and port in proxy settings (#3332) 2024-10-15 01:06:21 +08:00
3gf8jv4dv 2109df8145 Fix some link (#3334)
* Fix some link

* Revise
2024-10-15 00:58:40 +08:00
3gf8jv4dv ae01a49cc4 Fix some error message wrap (#3333)
* Fix some error message wrap

* Fix checkstyle
2024-10-15 00:57:23 +08:00
142 changed files with 3414 additions and 2805 deletions

No files matched your search

+13 -8
View File
@@ -54,12 +54,14 @@ jobs:
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 +71,7 @@ jobs:
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
name: ${{ env.HMCL_TAG_NAME }}
tag_name: ${{ env.HMCL_TAG_NAME }}
prerelease: ture
stable-check-update:
if: ${{ github.repository_owner == 'HMCL-dev' }}
needs: dev-check-update
@@ -118,12 +121,14 @@ jobs:
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: |
+2 -2
View File
@@ -14,11 +14,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up JDK 11
- name: Set up JDK 8
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '11'
java-version: 8
java-package: 'jdk+fx'
- name: Build with Gradle
run: ./gradlew build --no-daemon
+1 -19
View File
@@ -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") ?: ""
@@ -167,24 +167,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)
}
@@ -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(),
@@ -244,9 +252,9 @@ public final class LauncherHelper {
} else if (ex instanceof PermissionException) {
message = i18n("launch.failed.executable_permission");
} else if (ex instanceof ProcessCreationException) {
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
message = i18n("launch.failed.creating_process") + "\n" + ex.getLocalizedMessage();
} else if (ex instanceof NotDecompressingNativesException) {
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
message = i18n("launch.failed.decompressing_natives") + "\n" + ex.getLocalizedMessage();
} else if (ex instanceof LibraryDownloadException) {
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n";
if (ex.getCause() instanceof ResponseCodeException) {
@@ -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;
@@ -881,8 +892,8 @@ public final class LauncherHelper {
}
private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download";
private static final String OPENJDK_DOWNLOAD_LINK = "https://docs.microsoft.com/java/openjdk/download";
private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download/";
private static final String OPENJDK_DOWNLOAD_LINK = "https://learn.microsoft.com/java/openjdk/download";
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
@@ -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;
@@ -391,9 +391,6 @@ public final class Controllers {
Controllers.getSettingsPage().showFeedback();
Controllers.navigate(Controllers.getSettingsPage());
break;
case "hmcl://hide-announcement":
Controllers.getRootPage().getMainPage().hideAnnouncementPane();
break;
}
} else {
FXUtils.openLink(href);
@@ -53,7 +53,7 @@ public class CrashWindow extends Stage {
Button btnContact = new Button();
btnContact.setText(i18n("launcher.contact"));
btnContact.setOnMouseClicked(event -> FXUtils.openLink(Metadata.CONTACT_URL));
btnContact.setOnAction(event -> FXUtils.openLink(Metadata.CONTACT_URL));
HBox box = new HBox();
box.setStyle("-fx-padding: 8px;");
box.getChildren().add(btnContact);
@@ -23,13 +23,12 @@ import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.beans.value.WritableValue;
import javafx.beans.value.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
@@ -54,8 +53,8 @@ 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.ui.construct.JFXHyperlink;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -74,8 +73,6 @@ 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.nio.file.Files;
@@ -87,6 +84,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Lang.thread;
@@ -98,6 +97,20 @@ 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";
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
@@ -300,8 +313,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) {
@@ -309,39 +326,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);
@@ -657,6 +648,61 @@ public final class FXUtils {
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
}
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
int itemCount = children.length;
int childSelectedCount = 0;
for (BooleanProperty child : children) {
if (child.get())
childSelectedCount++;
}
allEnabled.set(childSelectedCount == itemCount);
class Listener implements InvalidationListener {
private int childSelectedCount;
private boolean updating = false;
public Listener(int childSelectedCount) {
this.childSelectedCount = childSelectedCount;
}
@Override
public void invalidated(Observable observable) {
if (updating)
return;
updating = true;
try {
boolean value = ((BooleanProperty) observable).get();
if (observable == allEnabled) {
for (BooleanProperty child : children) {
child.setValue(value);
}
childSelectedCount = value ? itemCount : 0;
} else {
if (value)
childSelectedCount++;
else
childSelectedCount--;
allEnabled.set(childSelectedCount == itemCount);
}
} finally {
updating = false;
}
}
}
InvalidationListener listener = new Listener(childSelectedCount);
WeakInvalidationListener weakListener = new WeakInvalidationListener(listener);
allEnabled.addListener(listener);
for (BooleanProperty child : children) {
child.addListener(weakListener);
}
}
public static void setIcon(Stage stage) {
String icon;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
@@ -904,6 +950,15 @@ public final class FXUtils {
});
}
public static void onClicked(Node node, Runnable action) {
node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 1) {
action.run();
e.consume();
}
});
}
public static void copyText(String text) {
ClipboardContent content = new ClipboardContent();
content.putString(text);
@@ -925,7 +980,7 @@ public final class FXUtils {
Element r = doc.getDocumentElement();
NodeList children = r.getChildNodes();
List<javafx.scene.Node> texts = new ArrayList<>();
List<Node> texts = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
org.w3c.dom.Node node = children.item(i);
@@ -933,8 +988,8 @@ public final class FXUtils {
Element element = (Element) node;
if ("a".equals(element.getTagName())) {
String href = element.getAttribute("href");
JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent());
hyperlink.setOnAction(e -> {
Text text = new Text(element.getTextContent());
onClicked(text, () -> {
String link = href;
try {
link = new URI(href).toASCIIString();
@@ -942,7 +997,10 @@ public final class FXUtils {
}
hyperlinkAction.accept(link);
});
texts.add(hyperlink);
text.setCursor(Cursor.HAND);
text.setFill(Color.web("#0070E0"));
text.setUnderline(true);
texts.add(text);
} else if ("b".equals(element.getTagName())) {
Text text = new Text(element.getTextContent());
text.getStyleClass().add("bold");
@@ -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");
@@ -417,14 +416,14 @@ public class GameCrashWindow extends Stage {
HBox toolBar = new HBox();
{
JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs"));
exportGameCrashInfoButton.setOnMouseClicked(e -> exportGameCrashInfo());
exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo());
JFXButton logButton = FXUtils.newRaisedButton(i18n("logwindow.title"));
logButton.setOnMouseClicked(e -> showLogWindow());
logButton.setOnAction(e -> showLogWindow());
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));
@@ -123,7 +123,7 @@ public final class HTMLRenderer {
if (hyperlink != null) {
URI target = resolveLink(hyperlink);
if (target != null) {
text.setOnMouseClicked(event -> onClickHyperlink.accept(target));
FXUtils.onClicked(text, () -> onClickHyperlink.accept(target));
text.setCursor(Cursor.HAND);
}
text.getStyleClass().add("html-hyperlink");
@@ -192,7 +192,7 @@ public final class HTMLRenderer {
if (hyperlink != null) {
URI target = resolveLink(hyperlink);
if (target != null) {
imageView.setOnMouseClicked(event -> onClickHyperlink.accept(target));
FXUtils.onClicked(imageView, () -> onClickHyperlink.accept(target));
imageView.setCursor(Cursor.HAND);
}
}
@@ -23,7 +23,6 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
@@ -33,7 +32,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.setting.Theme;
@@ -60,8 +59,8 @@ public class InstallerItem extends Control {
private final ObjectProperty<InstalledState> versionProperty = new SimpleObjectProperty<>(this, "version", null);
private final ObjectProperty<State> resolvedStateProperty = new SimpleObjectProperty<>(this, "resolvedState", InstallableState.INSTANCE);
private final ObjectProperty<EventHandler<? super MouseEvent>> installActionProperty = new SimpleObjectProperty<>(this, "installAction");
private final ObjectProperty<EventHandler<? super MouseEvent>> removeActionProperty = new SimpleObjectProperty<>(this, "removeAction");
private final ObjectProperty<Runnable> onInstall = new SimpleObjectProperty<>(this, "onInstall");
private final ObjectProperty<Runnable> onRemove = new SimpleObjectProperty<>(this, "onRemove");
public interface State {
}
@@ -170,12 +169,28 @@ public class InstallerItem extends Control {
return resolvedStateProperty;
}
public ObjectProperty<EventHandler<? super MouseEvent>> installActionProperty() {
return installActionProperty;
public ObjectProperty<Runnable> onInstallProperty() {
return onInstall;
}
public ObjectProperty<EventHandler<? super MouseEvent>> removeActionProperty() {
return removeActionProperty;
public Runnable getOnInstall() {
return onInstall.get();
}
public void setOnInstall(Runnable onInstall) {
this.onInstall.set(onInstall);
}
public ObjectProperty<Runnable> onRemoveProperty() {
return onRemove;
}
public Runnable getOnRemove() {
return onRemove.get();
}
public void setOnRemove(Runnable onRemove) {
this.onRemove.set(onRemove);
}
@Override
@@ -371,7 +386,11 @@ public class InstallerItem extends Control {
removeButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> control.resolvedStateProperty.get() instanceof InstalledState, control.resolvedStateProperty));
}
removeButton.managedProperty().bind(removeButton.visibleProperty());
removeButton.onMouseClickedProperty().bind(control.removeActionProperty);
removeButton.setOnAction(e -> {
Runnable onRemove = control.getOnRemove();
if (onRemove != null)
onRemove.run();
});
buttonsContainer.getChildren().add(removeButton);
JFXButton installButton = new JFXButton();
@@ -383,7 +402,7 @@ public class InstallerItem extends Control {
));
installButton.getStyleClass().add("toggle-icon4");
installButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> {
if (control.installActionProperty.get() == null) {
if (control.getOnInstall() == null) {
return false;
}
@@ -396,18 +415,27 @@ public class InstallerItem extends Control {
}
return false;
}, control.resolvedStateProperty, control.installActionProperty));
}, control.resolvedStateProperty, control.onInstall));
installButton.managedProperty().bind(installButton.visibleProperty());
installButton.onMouseClickedProperty().bind(control.installActionProperty);
installButton.setOnAction(e -> {
Runnable onInstall = control.getOnInstall();
if (onInstall != null)
onInstall.run();
});
buttonsContainer.getChildren().add(installButton);
FXUtils.onChangeAndOperate(installButton.visibleProperty(), clickable -> {
if (clickable) {
container.onMouseClickedProperty().bind(control.installActionProperty);
container.setOnMouseClicked(event -> {
Runnable onInstall = control.getOnInstall();
if (onInstall != null && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
onInstall.run();
event.consume();
}
});
pane.setCursor(Cursor.HAND);
} else {
container.onMouseClickedProperty().unbind();
container.onMouseClickedProperty().set(null);
container.setOnMouseClicked(null);
pane.setCursor(Cursor.DEFAULT);
}
});
@@ -70,7 +70,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
btnAdd.getStyleClass().add("jfx-button-raised-round");
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
btnAdd.setGraphic(SVG.PLUS.createIcon(Theme.whiteFill(), -1, -1));
btnAdd.setOnMouseClicked(e -> skinnable.add());
btnAdd.setOnAction(e -> skinnable.add());
JFXButton btnRefresh = new JFXButton();
FXUtils.setLimitWidth(btnRefresh, 40);
@@ -78,7 +78,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
btnRefresh.getStyleClass().add("jfx-button-raised-round");
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1, -1));
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
btnRefresh.setOnAction(e -> skinnable.refresh());
vBox.getChildren().setAll(btnAdd);
@@ -34,6 +34,7 @@ import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import org.jackhuang.hmcl.game.GameDumpGenerator;
@@ -322,6 +323,9 @@ public final class LogWindow extends Stage {
setGraphic(null);
setOnMouseClicked(event -> {
if (event.getButton() != MouseButton.PRIMARY)
return;
if (!event.isControlDown()) {
for (ListCell<Log> logListCell : selected) {
if (logListCell != this) {
@@ -340,6 +344,8 @@ public final class LogWindow extends Stage {
if (getItem() != null) {
getItem().setSelected(true);
}
event.consume();
});
}
@@ -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"),
@@ -91,7 +91,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
ret.textFillProperty().bind(Theme.foregroundFillBinding());
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
ret.setText(text);
ret.setOnMouseClicked(e -> onClick.run());
ret.setOnAction(e -> onClick.run());
return ret;
}
@@ -100,7 +100,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
ret.getStyleClass().add("jfx-tool-bar-button");
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1, -1)));
ret.setText(text);
ret.setOnMouseClicked(e -> onClick.run());
ret.setOnAction(e -> onClick.run());
return ret;
}
@@ -110,7 +110,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
ret.textFillProperty().bind(Theme.foregroundFillBinding());
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
FXUtils.installFastTooltip(ret, tooltip);
ret.setOnMouseClicked(e -> onClick.run());
ret.setOnAction(e -> onClick.run());
return ret;
}
@@ -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);
}
}
}
@@ -32,17 +32,14 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.util.skin.InvalidSkinException;
import org.jackhuang.hmcl.util.skin.NormalizedSkin;
@@ -128,7 +125,7 @@ public class AccountListItem extends RadioButton {
.orElse(emptySet());
return uploadableTextures.contains(TextureType.SKIN);
}, profile);
} else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) {
} else if (account instanceof OfflineAccount || account.canUploadSkin()) {
return createBooleanBinding(() -> true);
} else {
return createBooleanBinding(() -> false);
@@ -144,11 +141,7 @@ public class AccountListItem extends RadioButton {
Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account));
return null;
}
if (account instanceof MicrosoftAccount) {
FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editskin");
return null;
}
if (!(account instanceof YggdrasilAccount)) {
if (!account.canUploadSkin()) {
return null;
}
@@ -174,7 +167,7 @@ public class AccountListItem extends RadioButton {
NormalizedSkin skin = new NormalizedSkin(skinImg);
String model = skin.isSlim() ? "slim" : "";
LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]");
((YggdrasilAccount) account).uploadSkin(model, selectedFile.toPath());
account.uploadSkin(skin.isSlim(), selectedFile.toPath());
})
.thenComposeAsync(refreshAsync())
.whenComplete(Schedulers.javafx(), e -> {
@@ -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);
@@ -94,7 +93,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
JFXButton btnMove = new JFXButton();
SpinnerPane spinnerMove = new SpinnerPane();
spinnerMove.getStyleClass().add("small-spinner-pane");
btnMove.setOnMouseClicked(e -> {
btnMove.setOnAction(e -> {
Account account = skinnable.getAccount();
Accounts.getAccounts().remove(account);
if (account.isPortable()) {
@@ -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);
@@ -129,7 +128,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
JFXButton btnRefresh = new JFXButton();
SpinnerPane spinnerRefresh = new SpinnerPane();
spinnerRefresh.getStyleClass().setAll("small-spinner-pane");
btnRefresh.setOnMouseClicked(e -> {
btnRefresh.setOnAction(e -> {
spinnerRefresh.showSpinner();
skinnable.refreshAsync()
.whenComplete(Schedulers.javafx(), ex -> {
@@ -143,13 +142,13 @@ 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);
JFXButton btnUpload = new JFXButton();
SpinnerPane spinnerUpload = new SpinnerPane();
btnUpload.setOnMouseClicked(e -> {
btnUpload.setOnAction(e -> {
Task<?> uploadTask = skinnable.uploadSkin();
if (uploadTask != null) {
spinnerUpload.showSpinner();
@@ -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,18 +169,19 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
JFXButton btnCopyUUID = new JFXButton();
SpinnerPane spinnerCopyUUID = new SpinnerPane();
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
btnCopyUUID.setOnMouseClicked(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
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);
JFXButton btnRemove = new JFXButton();
btnRemove.setOnMouseClicked(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
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);
@@ -44,6 +44,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.net.URI;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -95,7 +96,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
VBox boxMethods = new VBox();
{
boxMethods.getStyleClass().add("advanced-list-box-content");
boxMethods.getChildren().add(new ClassTitle(i18n("account.create")));
boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT)));
FXUtils.setLimitWidth(boxMethods, 200);
AdvancedListItem offlineItem = new AdvancedListItem();
@@ -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() {
@@ -65,6 +65,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@@ -296,7 +297,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
}
});
hintPane.setOnMouseClicked(e -> {
FXUtils.onClicked(hintPane, () -> {
if (deviceCode.get() != null) {
FXUtils.copyText(deviceCode.get().getUserCode());
}
@@ -316,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);
@@ -509,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);
@@ -637,7 +638,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
cancel.setOnAction(e -> latch.countDown());
listBox.startCategory(i18n("account.choose"));
listBox.startCategory(i18n("account.choose").toUpperCase(Locale.ROOT));
setCenter(listBox);
@@ -657,7 +658,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
TexturesLoader.bindAvatar(portraitCanvas, service, profile.getId());
IconedItem accountItem = new IconedItem(portraitCanvas, profile.getName());
accountItem.setOnMouseClicked(e -> {
FXUtils.onClicked(accountItem, () -> {
selectedProfile = profile;
latch.countDown();
});
@@ -58,7 +58,7 @@ public class OAuthAccountLoginDialog extends DialogPane {
);
}
});
hintPane.setOnMouseClicked(e -> {
FXUtils.onClicked(hintPane, () -> {
if (deviceCode.get() != null) {
FXUtils.copyText(deviceCode.get().getUserCode());
}
@@ -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;
}
}
@@ -25,7 +25,6 @@ import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Pair;
@@ -42,7 +41,7 @@ public class AdvancedListItem extends Control {
public AdvancedListItem() {
getStyleClass().add("advanced-list-item");
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent()));
FXUtils.onClicked(this, () -> fireEvent(new ActionEvent()));
}
public Node getLeftGraphic() {
@@ -17,21 +17,36 @@
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
public class AnnouncementCard extends VBox {
public final class AnnouncementCard extends VBox {
public AnnouncementCard(String title, String content) {
TextFlow tf = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
public AnnouncementCard(String title, String content, Runnable onClose) {
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
body.setLineSpacing(4);
Label label = new Label(title);
label.getStyleClass().add("title");
getChildren().setAll(label, tf);
setSpacing(14);
BorderPane titleBar = new BorderPane();
titleBar.getStyleClass().add("title");
titleBar.setLeft(new Label(title));
if (onClose != null) {
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
hideNode.setOnMouseClicked(e -> onClose.run());
hideNode.setCursor(Cursor.HAND);
titleBar.setRight(hideNode);
}
getChildren().setAll(titleBar, body);
setSpacing(16);
getStyleClass().addAll("card", "announcement");
}
}
@@ -25,9 +25,6 @@ import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -139,7 +136,7 @@ final class ComponentListCell extends StackPane {
container.getChildren().setAll(content);
groupNode.getChildren().add(container);
EventHandler<Event> onExpand = e -> {
Runnable onExpand = () -> {
if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {
expandAnimation.stop();
}
@@ -182,8 +179,8 @@ final class ComponentListCell extends StackPane {
});
};
headerRippler.setOnMouseClicked(onExpand);
expandButton.setOnAction((EventHandler<ActionEvent>) (Object) onExpand);
FXUtils.onClicked(headerRippler, onExpand);
expandButton.setOnAction(e -> onExpand.run());
expandedProperty().addListener((a, b, newValue) -> expandIcon.setRotate(newValue ? 180 : 0));
@@ -60,7 +60,7 @@ public class FileItem extends BorderPane {
JFXButton right = new JFXButton();
right.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 15, 15));
right.getStyleClass().add("toggle-icon4");
right.setOnMouseClicked(e -> onExplore());
right.setOnAction(e -> onExplore());
FXUtils.installFastTooltip(right, i18n("button.edit"));
setRight(right);
@@ -21,6 +21,7 @@ import static javafx.collections.FXCollections.emptyObservableList;
import static javafx.collections.FXCollections.observableList;
import static javafx.collections.FXCollections.singletonObservableList;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import com.jfoenix.controls.JFXComboBox;
@@ -34,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>() {
@@ -51,7 +54,7 @@ public class FontComboBox extends JFXComboBox<String> {
itemsProperty().bind(BindingMapping.of(valueProperty())
.map(value -> value == null ? emptyObservableList() : singletonObservableList(value)));
setOnMouseClicked(e -> {
FXUtils.onClicked(this, () -> {
if (loaded)
return;
itemsProperty().unbind();
@@ -30,9 +30,9 @@ public class IconedMenuItem extends IconedItem {
getStyleClass().setAll("iconed-menu-item");
if (popup == null) {
setOnMouseClicked(e -> action.run());
FXUtils.onClicked(this, action);
} else {
setOnMouseClicked(e -> {
FXUtils.onClicked(this, () -> {
action.run();
popup.hide();
});
@@ -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;
}
}
@@ -23,12 +23,12 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
@@ -44,8 +44,8 @@ public final class ImagePickerItem extends BorderPane {
private final ImageView imageView;
private final StringProperty title = new SimpleStringProperty(this, "title");
private final ObjectProperty<EventHandler<? super MouseEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
private final ObjectProperty<EventHandler<? super MouseEvent>> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked");
private final ObjectProperty<EventHandler<ActionEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
private final ObjectProperty<EventHandler<ActionEvent>> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked");
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
public ImagePickerItem() {
@@ -55,12 +55,12 @@ public final class ImagePickerItem extends BorderPane {
JFXButton selectButton = new JFXButton();
selectButton.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 20, 20));
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
selectButton.onActionProperty().bind(onSelectButtonClicked);
selectButton.getStyleClass().add("toggle-icon4");
JFXButton deleteButton = new JFXButton();
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20));
deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked);
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
deleteButton.getStyleClass().add("toggle-icon4");
FXUtils.installFastTooltip(selectButton, i18n("button.edit"));
@@ -93,27 +93,27 @@ public final class ImagePickerItem extends BorderPane {
this.title.set(title);
}
public EventHandler<? super MouseEvent> getOnSelectButtonClicked() {
public EventHandler<ActionEvent> getOnSelectButtonClicked() {
return onSelectButtonClicked.get();
}
public ObjectProperty<EventHandler<? super MouseEvent>> onSelectButtonClickedProperty() {
public ObjectProperty<EventHandler<ActionEvent>> onSelectButtonClickedProperty() {
return onSelectButtonClicked;
}
public void setOnSelectButtonClicked(EventHandler<? super MouseEvent> onSelectButtonClicked) {
public void setOnSelectButtonClicked(EventHandler<ActionEvent> onSelectButtonClicked) {
this.onSelectButtonClicked.set(onSelectButtonClicked);
}
public EventHandler<? super MouseEvent> getOnDeleteButtonClicked() {
public EventHandler<ActionEvent> getOnDeleteButtonClicked() {
return onDeleteButtonClicked.get();
}
public ObjectProperty<EventHandler<? super MouseEvent>> onDeleteButtonClickedProperty() {
public ObjectProperty<EventHandler<ActionEvent>> onDeleteButtonClickedProperty() {
return onDeleteButtonClicked;
}
public void setOnDeleteButtonClicked(EventHandler<? super MouseEvent> onDeleteButtonClicked) {
public void setOnDeleteButtonClicked(EventHandler<ActionEvent> onDeleteButtonClicked) {
this.onDeleteButtonClicked.set(onDeleteButtonClicked);
}
@@ -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);
@@ -61,9 +61,7 @@ public class OptionToggleButton extends StackPane {
toggleButton.setSize(8);
FXUtils.setLimitHeight(toggleButton, 30);
container.setOnMouseClicked(e -> {
toggleButton.setSelected(!toggleButton.isSelected());
});
FXUtils.onClicked(container, () -> toggleButton.setSelected(!toggleButton.isSelected()));
FXUtils.onChangeAndOperate(subtitleProperty(), subtitle -> {
if (StringUtils.isNotBlank(subtitle)) {
@@ -32,7 +32,6 @@ import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
@DefaultProperty("content")
public class SpinnerPane extends Control {
@@ -101,18 +100,19 @@ public class SpinnerPane extends Control {
return onFailedActionProperty().get();
}
private ObjectProperty<EventHandler<Event>> onFailedAction = new SimpleObjectProperty<EventHandler<Event>>(this, "onFailedAction") {
private final ObjectProperty<EventHandler<Event>> onFailedAction = new SimpleObjectProperty<EventHandler<Event>>(this, "onFailedAction") {
@Override
protected void invalidated() {
setEventHandler(FAILED_ACTION, get());
}
};
@Override
protected Skin createDefaultSkin() {
protected SkinBase<SpinnerPane> createDefaultSkin() {
return new Skin(this);
}
private static class Skin extends SkinBase<SpinnerPane> {
private static final class Skin extends SkinBase<SpinnerPane> {
private final JFXSpinner spinner = new JFXSpinner();
private final StackPane contentPane = new StackPane();
private final StackPane topPane = new StackPane();
@@ -122,20 +122,18 @@ public class SpinnerPane extends Control {
@SuppressWarnings("FieldCanBeLocal") // prevent from gc.
private final InvalidationListener observer;
protected Skin(SpinnerPane control) {
Skin(SpinnerPane control) {
super(control);
topPane.getChildren().setAll(spinner);
topPane.getStyleClass().add("notice-pane");
failedPane.getStyleClass().add("notice-pane");
failedPane.getChildren().setAll(failedReasonLabel);
failedPane.onMouseClickedProperty().bind(
BindingMapping.of(control.onFailedAction)
.map(actionHandler -> (e -> {
if (actionHandler != null) {
actionHandler.handle(new Event(FAILED_ACTION));
}
})));
FXUtils.onClicked(failedPane, () -> {
EventHandler<Event> action = control.getOnFailedAction();
if (action != null)
action.handle(new Event(FAILED_ACTION));
});
FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> {
if (newValue == null) {
@@ -148,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());
}
@@ -30,7 +30,6 @@ import javafx.geometry.Insets;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
@@ -594,11 +593,9 @@ public class TabHeader extends Control implements TabControl, PageAware {
FXUtils.onChangeAndOperate(tab.selectedProperty(), selected -> inner.pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, selected));
this.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY) {
this.setOpacity(1);
getSkinnable().getSelectionModel().select(tab);
}
FXUtils.onClicked(this, () -> {
this.setOpacity(1);
getSkinnable().getSelectionModel().select(tab);
});
}
}
@@ -30,6 +30,8 @@ import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
@@ -144,6 +146,18 @@ public class DecoratorController {
// press ESC to go back
onEscPressed(navigator, this::back);
try {
// For JavaFX 12+
MouseButton button = MouseButton.valueOf("BACK");
navigator.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == button) {
back();
e.consume();
}
});
} catch (IllegalArgumentException ignored) {
}
}
public Decorator getDecorator() {
@@ -443,7 +457,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;
@@ -44,13 +42,8 @@ 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;
@@ -69,8 +62,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
primaryStage = control.getPrimaryStage();
minus.fillProperty().bind(Theme.foregroundFillBinding());
Decorator skinnable = getSkinnable();
root = new StackPane();
root.getStyleClass().add("window");
@@ -174,11 +165,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 +193,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());
@@ -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 + " - ";
@@ -59,7 +59,7 @@ class AdditionalInstallersPage extends InstallersPage {
for (InstallerItem library : group.getLibraries()) {
String libraryId = library.getLibraryId();
if (libraryId.equals("game")) continue;
library.removeActionProperty().set(e -> {
library.setOnRemove(() -> {
controller.getSettings().put(libraryId, new UpdateInstallerWizardProvider.RemoveVersionAction(libraryId));
reload();
});
@@ -55,6 +55,7 @@ import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Locale;
import java.util.concurrent.CancellationException;
import java.util.function.Supplier;
@@ -96,12 +97,12 @@ 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);
});
{
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game"))
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("game"));
item.setLeftGraphic(wrap(SVG.GAMEPAD));
@@ -114,7 +115,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
settingsItem.setOnAction(e -> tab.select(modpackTab));
})
.startCategory(i18n("download.content"))
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("mods"));
item.setLeftGraphic(wrap(SVG.PUZZLE));
@@ -69,7 +69,7 @@ public class InstallersPage extends Control implements WizardPage {
for (InstallerItem library : group.getLibraries()) {
String libraryId = library.getLibraryId();
if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue;
library.installActionProperty().set(e -> {
library.setOnInstall(() -> {
if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) {
Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING);
}
@@ -77,7 +77,7 @@ public class InstallersPage extends Control implements WizardPage {
if (!(library.resolvedStateProperty().get() instanceof InstallerItem.IncompatibleState))
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false)));
});
library.removeActionProperty().set(e -> {
library.setOnRemove(() -> {
controller.getSettings().remove(libraryId);
reload();
});
@@ -222,7 +222,7 @@ public class InstallersPage extends Control implements WizardPage {
installButton.disableProperty().bind(control.installable.not());
installButton.setPrefWidth(100);
installButton.setPrefHeight(40);
installButton.setOnMouseClicked(e -> control.onInstall());
installButton.setOnAction(e -> control.onInstall());
BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT);
root.setBottom(installButton);
}
@@ -36,6 +36,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -79,6 +80,8 @@ public final class LocalModpackPage extends ModpackPage {
});
}
btnDescription.setVisible(false);
File selectedFile;
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
if (filePath.isPresent()) {
@@ -134,6 +137,8 @@ public final class LocalModpackPage extends ModpackPage {
// trim: https://github.com/HMCL-dev/HMCL/issues/962
txtModpackName.setText(manifest.getName().trim());
}
btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription()));
}
}).start();
}
@@ -24,6 +24,7 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
protected final Label lblAuthor;
protected final JFXTextField txtModpackName;
protected final JFXButton btnInstall;
protected final JFXButton btnDescription;
protected ModpackPage(WizardController controller) {
this.controller = controller;
@@ -77,7 +78,7 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
BorderPane descriptionPane = new BorderPane();
{
JFXButton btnDescription = new JFXButton(i18n("modpack.description"));
btnDescription = new JFXButton(i18n("modpack.description"));
btnDescription.getStyleClass().add("jfx-button-border");
btnDescription.setOnAction(e -> onDescribe());
descriptionPane.setLeft(btnDescription);
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.IOException;
import java.util.Map;
@@ -69,6 +70,8 @@ public final class RemoteModpackPage extends ModpackPage {
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)),
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId));
}
btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription()));
}
@Override
@@ -95,7 +95,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
HintPane hintPane = new HintPane();
hintPane.setText(i18n("sponsor.bmclapi"));
hintPane.getStyleClass().add("sponsor-pane");
hintPane.setOnMouseClicked(e -> onSponsor());
FXUtils.onClicked(hintPane, this::onSponsor);
BorderPane.setMargin(hintPane, new Insets(10, 10, 0, 10));
this.setTop(hintPane);
@@ -146,7 +146,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
failedPane.getStyleClass().add("notice-pane");
{
Label label = new Label(i18n("download.failed.refresh"));
label.setOnMouseClicked(e -> onRefresh());
FXUtils.onClicked(label, this::onRefresh);
failedPane.getChildren().setAll(label);
}
@@ -155,7 +155,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
emptyPane.getStyleClass().add("notice-pane");
{
Label label = new Label(i18n("download.failed.empty"));
label.setOnMouseClicked(e -> onBack());
FXUtils.onClicked(label, this::onBack);
emptyPane.getChildren().setAll(label);
}
@@ -178,9 +178,9 @@ 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));
list.setOnMouseClicked(e -> {
FXUtils.onClicked(list, () -> {
if (list.getSelectionModel().getSelectedIndex() < 0)
return;
navigation.getSettings().put(libraryId, list.getSelectionModel().getSelectedItem());
@@ -210,7 +210,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 +218,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 +227,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);
});
}
@@ -272,8 +272,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 +311,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 +348,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
content.setSubtitle(remoteVersion.getGameVersion());
else
content.getTags().setAll(remoteVersion.getGameVersion());
content.setExternalLink(null);
}
}
}
@@ -21,12 +21,10 @@ import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.auth.Account;
@@ -78,7 +76,6 @@ public final class ModpackInfoPage extends Control implements WizardPage {
private final SimpleStringProperty authlibInjectorServer = new SimpleStringProperty();
private final SimpleStringProperty launchArguments = new SimpleStringProperty("");
private final SimpleStringProperty javaArguments = new SimpleStringProperty("");
private final ObjectProperty<EventHandler<? super MouseEvent>> next = new SimpleObjectProperty<>();
private final SimpleStringProperty mcbbsThreadId = new SimpleStringProperty("");
public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepository, String version) {
@@ -97,8 +94,6 @@ public final class ModpackInfoPage extends Control implements WizardPage {
javaArguments.set(versionSetting.getJavaArgs());
canIncludeLauncher = JarUtils.thisJarPath() != null;
next.set(e -> onNext());
}
private void onNext() {
@@ -178,9 +173,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) {
Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server"));
hyperlink.setOnMouseClicked(e -> {
FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html");
});
hyperlink.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html"));
borderPane.setTop(hyperlink);
} else {
HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO);
@@ -378,7 +371,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
borderPane.setBottom(hbox);
JFXButton nextButton = FXUtils.newRaisedButton(i18n("wizard.next"));
nextButton.onMouseClickedProperty().bind(skinnable.next);
nextButton.setOnAction(e -> skinnable.onNext());
nextButton.setPrefWidth(100);
nextButton.setPrefHeight(40);
nextButton.disableProperty().bind(
@@ -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) {
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.main;
import com.jfoenix.controls.*;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
@@ -100,7 +99,7 @@ public class DownloadSettingsPage extends StackPane {
downloadSource.getChildren().setAll(chooseWrapper, versionListSourcePane, downloadSourcePane);
}
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.version_list_source")), downloadSource);
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.download_source")), downloadSource);
}
{
@@ -204,7 +203,6 @@ public class DownloadSettingsPage extends StackPane {
Label host = new Label(i18n("settings.launcher.proxy.host"));
GridPane.setRowIndex(host, 1);
GridPane.setColumnIndex(host, 0);
GridPane.setHalignment(host, HPos.RIGHT);
gridPane.getChildren().add(host);
}
@@ -220,7 +218,6 @@ public class DownloadSettingsPage extends StackPane {
Label port = new Label(i18n("settings.launcher.proxy.port"));
GridPane.setRowIndex(port, 2);
GridPane.setColumnIndex(port, 0);
GridPane.setHalignment(port, HPos.RIGHT);
gridPane.getChildren().add(port);
}
@@ -21,7 +21,6 @@ import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
@@ -46,7 +45,6 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.DialogPane;
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -56,6 +54,7 @@ import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Lang.resolveException;
@@ -118,6 +117,8 @@ public final class JavaDownloadDialog extends StackPane {
button.setUserData(version);
vbox.getChildren().add(button);
toggleGroup.getToggles().add(button);
if (JavaManager.REPOSITORY.isInstalled(platform, version))
button.setDisable(true);
}
setBody(vbox);
@@ -166,19 +167,6 @@ public final class JavaDownloadDialog extends StackPane {
}
private final class DownloadDiscoJava extends JFXDialogLayout {
private boolean isLTS(int major) {
if (major <= 8) {
return true;
}
if (major < 21) {
return major == 11 || major == 17;
}
return major % 4 == 1;
}
private final JFXComboBox<DiscoJavaDistribution> distributionBox;
private final JFXComboBox<DiscoJavaRemoteVersion> remoteVersionBox;
private final JFXComboBox<JavaPackageType> packageTypeBox;
@@ -189,11 +177,8 @@ public final class JavaDownloadDialog extends StackPane {
private final DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider();
private final ObjectProperty<DiscoJavaVersionList> currentDiscoJavaVersionList = new SimpleObjectProperty<>();
private final Map<Pair<DiscoJavaDistribution, JavaPackageType>, DiscoJavaVersionList> javaVersionLists = new HashMap<>();
private boolean changingDistribution = false;
private final Map<DiscoJavaDistribution, DiscoJavaVersionList> javaVersionLists = new HashMap<>();
private final ObjectProperty<DiscoJavaVersionList> currentJavaVersionList = new SimpleObjectProperty<>();
DownloadDiscoJava() {
assert !distributions.isEmpty();
@@ -204,7 +189,7 @@ public final class JavaDownloadDialog extends StackPane {
this.remoteVersionBox = new JFXComboBox<>();
this.remoteVersionBox.setConverter(FXUtils.stringConverter(JavaRemoteVersion::getDistributionVersion));
this.packageTypeBox = new JFXComboBox<>();
this.packageTypeBox = new JFXComboBox<>(FXCollections.observableArrayList());
this.downloadButton = new JFXButton(i18n("download"));
downloadButton.setOnAction(e -> onDownload());
@@ -227,66 +212,91 @@ public final class JavaDownloadDialog extends StackPane {
body.addRow(2, new Label(i18n("java.download.packageType")), packageTypeBox);
distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));
ChangeListener<DiscoJavaVersionList.Status> updateStatusListener = (observable, oldValue, newValue) -> updateStatus(newValue);
this.currentDiscoJavaVersionList.addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.status.removeListener(updateStatusListener);
FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {
ObservableList<DiscoJavaRemoteVersion> versions;
if (packageType == null
|| currentJavaVersionList.get() == null
|| (versions = currentJavaVersionList.get().versions.get(packageType)) == null) {
remoteVersionBox.setItems(null);
return;
}
remoteVersionBox.setItems(versions);
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.isLTS()) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
remoteVersionBox.getSelectionModel().selectFirst();
});
Consumer<DiscoJavaVersionList> updateListStatus = list -> {
remoteVersionBox.setItems(null);
packageTypeBox.getItems().clear();
remoteVersionBox.setDisable(true);
packageTypeBox.setDisable(true);
warningLabel.setText(null);
if (list == null || (list.versions != null && list.versions.isEmpty()))
downloadButtonPane.getChildren().setAll(downloadButton);
else if (list.status == DiscoJavaVersionList.Status.LOADING)
downloadButtonPane.getChildren().setAll(new JFXSpinner());
else {
downloadButtonPane.getChildren().setAll(downloadButton);
if (list.status == DiscoJavaVersionList.Status.SUCCESS) {
packageTypeBox.getItems().setAll(list.versions.keySet());
packageTypeBox.getSelectionModel().selectFirst();
remoteVersionBox.setDisable(false);
packageTypeBox.setDisable(false);
} else
warningLabel.setText(i18n("java.download.load_list.failed"));
}
};
currentJavaVersionList.addListener((observable, oldValue, newValue) -> {
if (oldValue != null)
oldValue.listener = null;
if (newValue != null) {
newValue.status.addListener(updateStatusListener);
updateStatus(newValue.status.get());
updateListStatus.accept(newValue);
if (newValue.status == DiscoJavaVersionList.Status.LOADING)
newValue.listener = updateListStatus;
} else {
updateStatus(null);
currentJavaVersionList.set(null);
updateListStatus.accept(null);
}
});
packageTypeBox.getSelectionModel().selectedItemProperty().addListener(ignored -> updateVersions());
FXUtils.onChangeAndOperate(distributionBox.getSelectionModel().selectedItemProperty(), distribution -> {
if (distribution != null) {
changingDistribution = true;
packageTypeBox.setItems(FXCollections.observableList(new ArrayList<>(distribution.getSupportedPackageTypes())));
packageTypeBox.getSelectionModel().select(0);
changingDistribution = false;
updateVersions();
packageTypeBox.setDisable(false);
remoteVersionBox.setDisable(false);
} else {
packageTypeBox.setItems(null);
updateVersions();
remoteVersionBox.setItems(null);
packageTypeBox.setDisable(true);
remoteVersionBox.setDisable(true);
}
});
FXUtils.onChange(distributionBox.getSelectionModel().selectedItemProperty(),
it -> currentJavaVersionList.set(getJavaVersionList(it)));
setHeading(new Label(i18n("java.download")));
setBody(body);
setActions(warningLabel, downloadButtonPane, cancelButton);
}
private void updateStatus(DiscoJavaVersionList.Status status) {
if (status == DiscoJavaVersionList.Status.LOADING) {
downloadButtonPane.getChildren().setAll(new JFXSpinner());
remoteVersionBox.setDisable(true);
warningLabel.setText(null);
} else {
downloadButtonPane.getChildren().setAll(downloadButton);
if (status == DiscoJavaVersionList.Status.SUCCESS || status == null) {
remoteVersionBox.setDisable(false);
warningLabel.setText(null);
} else if (status == DiscoJavaVersionList.Status.FAILED) {
remoteVersionBox.setDisable(true);
warningLabel.setText(i18n("java.download.load_list.failed"));
}
}
}
private void onDownload() {
fireEvent(new DialogCloseEvent());
DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();
DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem();
JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();
if (version == null)
return;
@@ -312,8 +322,15 @@ public final class JavaDownloadDialog extends StackPane {
getIntegrityCheck = Task.completed(new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), fileInfo.getChecksum()));
else if (StringUtils.isNotBlank(fileInfo.getChecksumUri()))
getIntegrityCheck = new GetTask(downloadProvider.injectURLWithCandidates(fileInfo.getChecksumUri()))
.thenApplyAsync(checksum ->
new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum.trim()));
.thenApplyAsync(checksum -> {
checksum = checksum.trim();
int idx = checksum.indexOf(' ');
if (idx > 0)
checksum = checksum.substring(0, idx);
return new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum);
});
else
throw new IOException("Unable to get checksum for file");
@@ -336,7 +353,7 @@ public final class JavaDownloadDialog extends StackPane {
if (idx > 0) {
javaVersion = javaVersion.substring(0, idx);
}
String defaultName = distribution.getApiParameter() + "-" + javaVersion;
String defaultName = distribution.getApiParameter() + "-" + javaVersion + "-" + packageType.name().toLowerCase(Locale.ROOT);
Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller ->
new JavaInstallPage(controller::onFinish, info, version, updateInfo, defaultName, result)));
} else {
@@ -350,65 +367,39 @@ public final class JavaDownloadDialog extends StackPane {
}
private void updateVersions() {
if (changingDistribution) return;
private DiscoJavaVersionList getJavaVersionList(DiscoJavaDistribution distribution) {
if (distribution == null)
return null;
return javaVersionLists.computeIfAbsent(distribution, it -> {
DiscoJavaVersionList versionList = new DiscoJavaVersionList(it);
new DiscoFetchJavaListTask(downloadProvider, it, platform).setExecutor(Schedulers.io()).thenApplyAsync(versions -> {
EnumMap<JavaPackageType, ObservableList<DiscoJavaRemoteVersion>> result = new EnumMap<>(JavaPackageType.class);
if (versions.isEmpty())
return result;
DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();
if (distribution == null) {
this.currentDiscoJavaVersionList.set(null);
return;
}
for (Map.Entry<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>> entry : versions.entrySet())
for (DiscoJavaRemoteVersion version : entry.getValue().values())
if (version.isLTS()
|| version.getJdkVersion() == entry.getValue().lastKey() // latest version
|| version.getJdkVersion() == 16)
result.computeIfAbsent(entry.getKey(), ignored -> FXCollections.observableArrayList())
.add(version);
JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();
DiscoJavaVersionList list = javaVersionLists.computeIfAbsent(Pair.pair(distribution, packageType), pair -> {
DiscoJavaVersionList res = new DiscoJavaVersionList();
new DiscoFetchJavaListTask(downloadProvider, distribution, platform, packageType).setExecutor(Schedulers.io()).thenApplyAsync(versions -> {
if (versions.isEmpty()) return Collections.<DiscoJavaRemoteVersion>emptyList();
int lastLTS = -1;
for (int v : versions.keySet()) {
if (isLTS(v)) {
lastLTS = v;
}
}
ArrayList<DiscoJavaRemoteVersion> remoteVersions = new ArrayList<>();
for (Map.Entry<Integer, DiscoJavaRemoteVersion> entry : versions.entrySet()) {
int v = entry.getKey();
if (v >= lastLTS || isLTS(v) || v == 16) {
remoteVersions.add(entry.getValue());
}
}
Collections.reverse(remoteVersions);
return remoteVersions;
for (List<DiscoJavaRemoteVersion> l : result.values())
Collections.reverse(l);
return result;
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
res.status.set(DiscoJavaVersionList.Status.SUCCESS);
res.versions.setAll(result);
selectLTS(res);
versionList.status = DiscoJavaVersionList.Status.SUCCESS;
versionList.versions = result;
} else {
LOG.warning("Failed to load java list", exception);
res.status.set(DiscoJavaVersionList.Status.FAILED);
versionList.status = DiscoJavaVersionList.Status.FAILED;
}
versionList.invalidate();
})).start();
return res;
return versionList;
});
this.currentDiscoJavaVersionList.set(list);
this.remoteVersionBox.setItems(list.versions);
selectLTS(list);
}
private void selectLTS(DiscoJavaVersionList list) {
if (remoteVersionBox.getItems() == list.versions) {
for (int i = 0; i < list.versions.size(); i++) {
JavaRemoteVersion item = list.versions.get(i);
if (item.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
remoteVersionBox.getSelectionModel().select(i);
break;
}
}
}
}
}
@@ -417,7 +408,19 @@ public final class JavaDownloadDialog extends StackPane {
LOADING, SUCCESS, FAILED
}
final ObservableList<DiscoJavaRemoteVersion> versions = FXCollections.observableArrayList();
final ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.LOADING);
final DiscoJavaDistribution distribution;
Status status = Status.LOADING;
EnumMap<JavaPackageType, ObservableList<DiscoJavaRemoteVersion>> versions;
Consumer<DiscoJavaVersionList> listener;
DiscoJavaVersionList(DiscoJavaDistribution distribution) {
this.distribution = distribution;
}
void invalidate() {
if (listener != null)
listener.accept(this);
}
}
}
@@ -50,7 +50,6 @@ import org.jackhuang.hmcl.util.tree.ArchiveFileTree;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import org.jackhuang.hmcl.util.tree.TarFileTree;
import java.io.*;
import java.nio.file.Files;
@@ -151,7 +150,7 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
private void onInstallArchive(Path file) {
Task.supplyAsync(() -> {
try (ArchiveFileTree<?, ?> tree = TarFileTree.open(file)) {
try (ArchiveFileTree<?, ?> tree = ArchiveFileTree.open(file)) {
JavaInfo info = JavaInfo.fromArchive(tree);
if (!JavaManager.isCompatible(info.getPlatform()))
@@ -33,8 +33,9 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
@@ -65,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);
});
{
@@ -74,7 +75,6 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
settingsItem.setTitle(i18n("settings.type.global.manage"));
settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
runInFX(() -> FXUtils.installFastTooltip(settingsItem, i18n("settings.type.global.manage")));
settingsItem.setOnAction(e -> tab.select(gameTab));
})
.addNavigationDrawerItem(javaItem -> {
@@ -83,7 +83,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
javaItem.setOnAction(e -> tab.select(javaManagementTab));
})
.startCategory(i18n("launcher"))
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("settings.launcher.general"));
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
@@ -102,7 +102,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
downloadItem.setOnAction(e -> tab.select(downloadTab));
})
.startCategory(i18n("help"))
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(helpItem -> {
helpItem.setTitle(i18n("help"));
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
@@ -32,7 +32,6 @@ import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
@@ -46,6 +45,8 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.AnnouncementCard;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
@@ -65,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 {
@@ -83,7 +83,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
private final ObservableList<Node> versionNodes;
private Profile profile;
private VBox announcementPane;
private TransitionPane announcementPane;
private final StackPane updatePane;
private final JFXButton menuButton;
@@ -102,13 +102,23 @@ public final class MainPage extends StackPane implements DecoratorPage {
setPadding(new Insets(20));
if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) {
announcementPane = new VBox(16);
AnnouncementCard announcementCard = null;
if (Metadata.isNightly()) {
announcementPane.getChildren().add(new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint")));
announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null);
} else if (Metadata.isDev()) {
announcementPane.getChildren().add(new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint")));
announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane);
}
if (announcementCard != null) {
VBox announcementBox = new VBox(16);
announcementBox.getChildren().add(announcementCard);
announcementPane = new TransitionPane();
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
getChildren().add(announcementPane);
}
getChildren().add(announcementPane);
}
updatePane = new StackPane();
@@ -117,7 +127,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
FXUtils.setLimitWidth(updatePane, 230);
FXUtils.setLimitHeight(updatePane, 55);
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
updatePane.setOnMouseClicked(e -> onUpgrade());
FXUtils.onClicked(updatePane, this::onUpgrade);
FXUtils.onChange(showUpdateProperty(), this::showUpdate);
{
@@ -144,7 +154,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
StackPane.setMargin(closeUpdateButton, new Insets(5));
closeUpdateButton.setOnMouseClicked(e -> closeUpdateBubble());
closeUpdateButton.setOnAction(e -> closeUpdateBubble());
updatePane.getChildren().setAll(hBox, closeUpdateButton);
}
@@ -206,14 +216,14 @@ public final class MainPage extends StackPane implements DecoratorPage {
menuButton.setPrefWidth(230);
//menuButton.setButtonType(JFXButton.ButtonType.RAISED);
menuButton.setStyle("-fx-font-size: 15px;");
menuButton.setOnMouseClicked(e -> onMenu());
menuButton.setOnAction(e -> onMenu());
menuButton.setClip(new Rectangle(211, -100, 100, 200));
StackPane graphic = new StackPane();
Node svg = SVG.TRIANGLE.createIcon(Theme.foregroundFillBinding(), 10, 10);
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);
@@ -224,10 +234,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
menu.setMaxHeight(365);
menu.setMaxWidth(545);
menu.setAlwaysShowingVBar(true);
menu.setOnMouseClicked(e -> popup.hide());
FXUtils.onClicked(menu, popup::hide);
versionNodes = MappedObservableList.create(versions, version -> {
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId()));
FXUtils.onClicked(node, () -> profile.setSelectedVersion(version.getId()));
return node;
});
Bindings.bindContent(menu.getContent(), versionNodes);
@@ -290,10 +300,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
public void hideAnnouncementPane() {
if (announcementPane != null) {
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
Pane parent = (Pane) announcementPane.getParent();
if (parent != null)
parent.getChildren().remove(announcementPane);
announcementPane = null;
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
}
}
@@ -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();
@@ -73,7 +73,7 @@ public abstract class SettingsView extends StackPane {
{
StackPane sponsorPane = new StackPane();
sponsorPane.setCursor(Cursor.HAND);
sponsorPane.setOnMouseClicked(e -> onSponsor());
FXUtils.onClicked(sponsorPane, this::onSponsor);
sponsorPane.setPadding(new Insets(8, 0, 8, 0));
GridPane gridPane = new GridPane();
@@ -121,7 +121,7 @@ public abstract class SettingsView extends StackPane {
{
btnUpdate = new JFXButton();
btnUpdate.setOnMouseClicked(e -> onUpdate());
btnUpdate.setOnAction(e -> onUpdate());
btnUpdate.getStyleClass().add("toggle-icon4");
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20, 20));
@@ -161,7 +161,7 @@ public abstract class SettingsView extends StackPane {
{
JFXButton cleanButton = new JFXButton(i18n("launcher.cache_directory.clean"));
cleanButton.setOnMouseClicked(e -> clearCacheDirectory());
cleanButton.setOnAction(e -> clearCacheDirectory());
cleanButton.getStyleClass().add("jfx-button-border");
fileCommonLocationSublist.setHeaderRight(cleanButton);
@@ -193,11 +193,11 @@ public abstract class SettingsView extends StackPane {
debugPane.setLeft(left);
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
openLogFolderButton.setOnMouseClicked(e -> openLogFolder());
openLogFolderButton.setOnAction(e -> openLogFolder());
openLogFolderButton.getStyleClass().add("jfx-button-border");
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
logButton.setOnMouseClicked(e -> onExportLogs());
logButton.setOnAction(e -> onExportLogs());
logButton.getStyleClass().add("jfx-button-border");
HBox buttonBox = new HBox();
@@ -22,7 +22,6 @@ import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.setting.Theme;
@@ -47,9 +46,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
skinnable.pseudoClassStateChanged(SELECTED, active);
});
getSkinnable().addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
getSkinnable().setSelected(true);
});
FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true));
Node left = VersionPage.wrap(SVG.FOLDER_OUTLINE);
root.setLeft(left);
@@ -64,7 +61,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnRemove = new JFXButton();
btnRemove.setOnMouseClicked(e -> skinnable.remove());
btnRemove.setOnAction(e -> skinnable.remove());
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
@@ -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,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.animation.AnimationTimer;
@@ -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,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,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.scene.Group;
import javafx.scene.Node;
@@ -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,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.geometry.Point2D;
import javafx.scene.Group;
@@ -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;
@@ -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 {
@@ -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 {
@@ -64,7 +64,6 @@ 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.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
@@ -302,7 +301,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())) {
@@ -488,7 +487,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
JFXListView<RemoteMod> listView = new JFXListView<>();
spinnerPane.setContent(listView);
Bindings.bindContent(listView.getItems(), getSkinnable().items);
listView.setOnMouseClicked(e -> {
FXUtils.onClicked(listView, () -> {
if (listView.getSelectionModel().getSelectedIndex() < 0)
return;
RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem();
@@ -62,7 +62,6 @@ 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.util.i18n.I18n.i18n;
public class DownloadPage extends Control implements DecoratorPage {
@@ -241,14 +240,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 +345,10 @@ public class DownloadPage extends Control implements DecoratorPage {
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
RipplerContainer container = new RipplerContainer(pane);
container.setOnMouseClicked(e -> 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) {
@@ -388,15 +390,15 @@ public class DownloadPage extends Control implements DecoratorPage {
switch (dataItem.getVersionType()) {
case Alpha:
content.getTags().add(i18n("version.game.snapshot"));
content.getTags().add(i18n("mods.channel.alpha"));
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
break;
case Beta:
content.getTags().add(i18n("version.game.snapshot"));
content.getTags().add(i18n("mods.channel.beta"));
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
break;
case Release:
content.getTags().add(i18n("version.game.release"));
content.getTags().add(i18n("mods.channel.release"));
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
break;
}
@@ -428,7 +430,7 @@ public class DownloadPage extends Control implements DecoratorPage {
}
RipplerContainer container = new RipplerContainer(pane);
container.setOnMouseClicked(e -> Controllers.dialog(new ModVersion(dataItem, selfPage)));
FXUtils.onClicked(container, () -> Controllers.dialog(new ModVersion(dataItem, selfPage)));
getChildren().setAll(container);
// Workaround for https://github.com/HMCL-dev/HMCL/issues/2129
@@ -443,7 +445,7 @@ public class DownloadPage extends Control implements DecoratorPage {
VBox box = new VBox(8);
box.setPadding(new Insets(8));
ModItem modItem = new ModItem(version, selfPage);
modItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
FXUtils.onClicked(modItem, () -> fireEvent(new DialogCloseEvent()));
box.getChildren().setAll(modItem);
SpinnerPane spinnerPane = new SpinnerPane();
ScrollPane scrollPane = new ScrollPane();
@@ -505,7 +507,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);
dependencyModItem.setOnMouseClicked(e -> 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> {
@@ -79,33 +78,33 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
right.setAlignment(Pos.CENTER_RIGHT);
if (skinnable.canUpdate()) {
JFXButton btnUpgrade = new JFXButton();
btnUpgrade.setOnMouseClicked(e -> skinnable.update());
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);
}
{
JFXButton btnLaunch = new JFXButton();
btnLaunch.setOnMouseClicked(e -> skinnable.launch());
btnLaunch.setOnAction(e -> skinnable.launch());
btnLaunch.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
runInFX(() -> FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test")));
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
right.getChildren().add(btnLaunch);
}
{
JFXButton btnManage = new JFXButton();
btnManage.setOnMouseClicked(e -> {
btnManage.setOnAction(e -> {
currentSkinnable = skinnable;
popup.get().show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());
});
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);
}
@@ -91,13 +91,11 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
installNewGameItem.setTitle(i18n("install.new_game"));
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
installNewGameItem.setOnAction(e -> Versions.addNewGame());
runInFX(() -> FXUtils.installFastTooltip(installNewGameItem, i18n("install.new_game")));
})
.addNavigationDrawerItem(installModpackItem -> {
installModpackItem.setTitle(i18n("install.modpack"));
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK));
installModpackItem.setOnAction(e -> Versions.importModpack());
runInFX(() -> FXUtils.installFastTooltip(installModpackItem, i18n("install.modpack")));
})
.addNavigationDrawerItem(refreshItem -> {
refreshItem.setTitle(i18n("button.refresh"));
@@ -108,7 +106,6 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
globalManageItem.setTitle(i18n("settings.type.global.manage"));
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE));
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
runInFX(() -> FXUtils.installFastTooltip(globalManageItem, i18n("settings.type.global.manage")));
});
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
setLeft(pane, bottomLeftCornerList);
@@ -97,11 +97,11 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
item.versionProperty().set(null);
}
item.installActionProperty().set(e -> {
item.setOnInstall(() -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
});
item.removeActionProperty().set(e -> profile.getDependency().removeLibraryAsync(version, libraryId)
item.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId)
.thenComposeAsync(profile.getRepository()::saveAsync)
.withComposeAsync(profile.getRepository().refreshVersionsAsync())
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
@@ -121,7 +121,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
InstallerItem installerItem = new InstallerItem(libraryId, InstallerItem.Style.LIST_ITEM);
installerItem.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false));
installerItem.removeActionProperty().set(e -> profile.getDependency().removeLibraryAsync(version, libraryId)
installerItem.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId)
.thenComposeAsync(profile.getRepository()::saveAsync)
.withComposeAsync(profile.getRepository().refreshVersionsAsync())
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
@@ -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 {
@@ -186,6 +186,14 @@ 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));
}
});
center.setContent(listView);
root.getContent().add(center);
}
@@ -204,7 +212,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();
}
@@ -539,7 +547,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
}
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
restoreButton.setVisible(!dataItem.getModInfo().getMod().getOldFiles().isEmpty());
restoreButton.setOnMouseClicked(e -> {
restoreButton.setOnAction(e -> {
menu.get().getContent().setAll(dataItem.getModInfo().getMod().getOldFiles().stream()
.map(localModFile -> new IconedMenuItem(null, localModFile.getVersion(),
() -> getSkinnable().rollback(dataItem.getModInfo(), localModFile),
@@ -549,12 +557,8 @@ class ModListPageSkin extends SkinBase<ModListPage> {
popup.get().show(restoreButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, restoreButton.getHeight());
});
revealButton.setOnMouseClicked(e -> {
FXUtils.showFileInExplorer(dataItem.getModInfo().getFile());
});
infoButton.setOnMouseClicked(e -> {
Controllers.dialog(new ModInfoDialog(dataItem));
});
revealButton.setOnAction(e -> FXUtils.showFileInExplorer(dataItem.getModInfo().getFile()));
infoButton.setOnAction(e -> Controllers.dialog(new ModInfoDialog(dataItem)));
}
}
}
@@ -24,6 +24,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
@@ -73,6 +74,8 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
getStyleClass().add("gray-background");
TableColumn<ModUpdateObject, Boolean> enabledColumn = new TableColumn<>();
CheckBox allEnabledBox = new CheckBox();
enabledColumn.setGraphic(allEnabledBox);
enabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(enabledColumn));
setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);
enabledColumn.setEditable(true);
@@ -95,6 +98,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty);
objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList()));
FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new));
TableView<ModUpdateObject> table = new TableView<>(objects);
table.setEditable(true);
@@ -135,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);
@@ -170,7 +174,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
csvTable.write(Files.newOutputStream(path));
FXUtils.showFileInExplorer(path);
}).whenComplete(Schedulers.javafx() ,exception -> {
}).whenComplete(Schedulers.javafx(), exception -> {
if (exception == null) {
Controllers.dialog(path.toString(), i18n("message.success"));
} else {
@@ -95,9 +95,7 @@ public class VersionIconDialog extends DialogPane {
RipplerContainer container = new RipplerContainer(shape);
FXUtils.setLimitWidth(container, 36);
FXUtils.setLimitHeight(container, 36);
container.setOnMouseClicked(e -> {
exploreIcon();
});
FXUtils.onClicked(container, this::exploreIcon);
return container;
}
@@ -107,7 +105,7 @@ public class VersionIconDialog extends DialogPane {
RipplerContainer container = new RipplerContainer(imageView);
FXUtils.setLimitWidth(container, 36);
FXUtils.setLimitHeight(container, 36);
container.setOnMouseClicked(e -> {
FXUtils.onClicked(container, () -> {
if (vs != null) {
vs.setVersionIcon(type);
onAccept();
@@ -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));
@@ -244,7 +244,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
versionSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
versionSettingsItem.setActionButtonVisible(false);
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
runInFX(() -> FXUtils.installFastTooltip(versionSettingsItem, i18n("settings.game")));
versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab));
AdvancedListItem installerListItem = new AdvancedListItem();
@@ -253,7 +252,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
installerListItem.setLeftGraphic(wrap(SVG.CUBE));
installerListItem.setActionButtonVisible(false);
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
runInFX(() -> FXUtils.installFastTooltip(installerListItem, i18n("settings.tabs.installers")));
installerListItem.setOnAction(e -> control.tab.select(control.installerListTab));
AdvancedListItem modListItem = new AdvancedListItem();
@@ -262,7 +260,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
modListItem.setLeftGraphic(wrap(SVG.PUZZLE));
modListItem.setActionButtonVisible(false);
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
runInFX(() -> FXUtils.installFastTooltip(modListItem, i18n("mods.manage")));
modListItem.setOnAction(e -> control.tab.select(control.modListTab));
AdvancedListItem worldListItem = new AdvancedListItem();
@@ -271,7 +268,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
worldListItem.setLeftGraphic(wrap(SVG.EARTH));
worldListItem.setActionButtonVisible(false);
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
runInFX(() -> FXUtils.installFastTooltip(worldListItem, i18n("world.manage")));
worldListItem.setOnAction(e -> control.tab.select(control.worldListTab));
AdvancedListBox sideBar = new AdvancedListBox()
@@ -685,7 +685,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"),
@@ -83,7 +83,7 @@ public class WorldExportPageSkin extends SkinBase<WorldExportPage> {
JFXButton btnExport = FXUtils.newRaisedButton(i18n("button.export"));
btnExport.disableProperty().bind(Bindings.createBooleanBinding(() -> txtWorldName.getText().isEmpty() || Files.exists(Paths.get(fileItem.getPath())),
txtWorldName.textProperty().isEmpty(), fileItem.pathProperty()));
btnExport.setOnMouseClicked(e -> skinnable.export());
btnExport.setOnAction(e -> skinnable.export());
HBox bottom = new HBox();
bottom.setAlignment(Pos.CENTER_RIGHT);
bottom.getChildren().setAll(btnExport);
@@ -1,3 +1,20 @@
/*
* 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.versions;
import com.github.steveice10.opennbt.tag.builtin.*;
@@ -10,11 +27,14 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.DoubleValidator;
@@ -32,22 +52,36 @@ 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;
/**
* @author Glavo
*/
public final class WorldInfoPage extends StackPane implements DecoratorPage {
private final World world;
private final CompoundTag levelDat;
private final CompoundTag dataTag;
private CompoundTag levelDat;
private final ObjectProperty<State> stateProperty = new SimpleObjectProperty<>();
private final ObjectProperty<State> stateProperty;
public WorldInfoPage(World world) throws IOException {
public WorldInfoPage(World world) {
this.world = world;
this.levelDat = world.readLevelDat();
this.dataTag = levelDat.get("Data");
this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName())));
this.getChildren().add(new ProgressIndicator());
Task.supplyAsync(world::readLevelDat)
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
this.levelDat = result;
loadWorldInfo();
} else {
LOG.warning("Failed to load level.dat", exception);
this.getChildren().setAll(new Label(i18n("world.info.failed")));
}
})).start();
}
private void loadWorldInfo() {
CompoundTag dataTag = levelDat.get("Data");
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
stateProperty.set(State.fromTitle(i18n("world.info.title", world.getWorldName())));
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
@@ -483,7 +517,6 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
Tag z = listTag.get(2);
if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) {
//noinspection MalformedFormatString
return this == OVERWORLD
? String.format("(%.2f, %.2f, %.2f)", x.getValue(), y.getValue(), z.getValue())
: String.format("%s (%.2f, %.2f, %.2f)", name, x.getValue(), y.getValue(), z.getValue());
@@ -51,7 +51,7 @@ public class WorldListItem extends Control {
subtitle.set(i18n("world.description", world.getFileName(), formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
setOnMouseClicked(event -> showInfo());
FXUtils.onClicked(this, this::showInfo);
}
@Override
@@ -102,10 +102,6 @@ public class WorldListItem extends Control {
}
public void showInfo() {
try {
Controllers.navigate(new WorldInfoPage(world));
} catch (Exception e) {
// TODO
}
Controllers.navigate(new WorldInfoPage(world));
}
}
@@ -76,9 +76,7 @@ public class WorldListItemSkin extends SkinBase<WorldListItem> {
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnManage = new JFXButton();
btnManage.setOnMouseClicked(e -> {
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());
});
btnManage.setOnAction(e -> popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
btnManage.setGraphic(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), -1, -1));
@@ -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);
}
@@ -9,13 +9,13 @@ import java.util.stream.Stream;
* @author Glavo
*/
final class CallerFinder {
private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + ".";
private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);
private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();
private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> frame.getClassName() + "." + frame.getMethodName();
static String getCaller() {
return WALKER.walk(FUNCTION).map(it -> it.getClassName() + "." + it.getMethodName()).orElse(null);
return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null);
}
private CallerFinder() {
Binary file not shown.
@@ -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);
}
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2023 huangyuhui <huanghongxun2008@126.com> and contributors
# 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
@@ -23,25 +23,29 @@ about=Acerca de
about.copyright=Copyright
about.copyright.statement=Copyright © 2024 huangyuhui.
about.author=Autor
about.author.statement=@huanghongxun en bilibili
about.author.statement=bilibili @huanghongxun
about.claim=EULA
about.claim.statement=Haz clic en este enlace para ver el texto completo.
about.claim.statement=Haga clic en este enlace para ver el texto completo.
about.dependency=Bibliotecas de terceros
about.legal=Reconocimiento legal
about.thanks_to=Gracias a
about.thanks_to.bangbang93.statement=Por proporcionar la API de descarga de BMCLAPI, por favor considere hacer una donación.
about.thanks_to.bangbang93.statement=Por proporcionar el espejo de descarga de BMCLAPI, por favor considere hacer una donación!
about.thanks_to.burningtnt.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.contributors=Todos los colaboradores en GitHub
about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, Hello Minecraft! Launcher no habría llegado tan lejos.
about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, HMCL no habría llegado tan lejos.
about.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defecto.
about.thanks_to.glavo.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.glavo.statement=Responsable del mantenimiento de HMCL.
about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.zkitefly.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.mcmod=mcmod.cn
about.thanks_to.mcmod.statement=Por facilitar las traducciones al chino y el wiki de varios mods.
about.thanks_to.zkitefly.statement=Responsable del mantenimiento de la documentación de HMCL.
about.thanks_to.mcbbs=MCBBS (Foro Chino Minecraft)
about.thanks_to.mcbbs.statement=Por proporcionar el espejo de descarga de mcbbs.net para los usuarios de China continental. (Ya no disponible)
about.thanks_to.mcmod=MCMod (mcmod.cn)
about.thanks_to.mcmod.statement=Por proporcionar las traducciones al chino simplificado y la wiki de varios mods.
about.thanks_to.red_lnn.statement=Por proporcionar la imagen de fondo por defecto.
about.thanks_to.shulkersakura.statement=Por proporcionar el logotipo de HMCL.
about.thanks_to.users=Miembros del grupo de usuarios de HMCL
about.thanks_to.users.statement=Gracias por las donaciones, los informes de errores, etc.
about.thanks_to.yushijinhun.statement=Por proporcionar soporte relacionado con authlib-injector.
about.thanks_to.yushijinhun.statement=Por proporcionar el soporte relacionado con authlib-injector.
about.open_source=Código abierto
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
@@ -148,25 +152,30 @@ assets.index.malformed=Los archivos de índice de los activos descargados estaba
button.cancel=Cancelar
button.change_source=Cambiar fuente de descarga
button.clear=Limpiar
button.copy_and_exit=Copiar y salir
button.delete=Borrar
button.edit=Editar
button.install=Instalar
button.export=Exportar
button.no=No
botón.ok=Aceptar
button.ok=Aceptar
button.refresh=Refrescar
button.remove=Eliminar
button.remove.confirm=¿Está seguro de querer eliminarlo definitivamente? Esta acción no se puede deshacer.
button.remove.confirm=¿Estás seguro de que deseas eliminarlo de forma permanente? ¡Esta acción no se puede deshacer!
button.retry=Reintentar
button.save=Guardar
button.save_as=Guardar como
button.select_all=Seleccionar todo
button.view=Vista
button.yes=Sí
chat=Chat de grupo
color.recent=Recomendado
color.custom=Color personalizado
crash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su versión de Java.
crash.user_fault=El launcher se ha bloqueado debido a un entorno Java o de sistema corrupto. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente.
crash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su Java.
crash.user_fault=El lanzador se ha bloqueado debido a que Java o el entorno del sistema están dañados. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente.
curse.category.0=Todos
@@ -316,7 +325,7 @@ download.javafx.prepare=Preparando la descarga
exception.access_denied=HMCL no puede acceder al archivo %s, puede estar bloqueado por otro proceso.\n\
\n\
Para los usuarios de Windows, puede abrir el 'Monitor de Recursos' para comprobar si otro proceso lo está utilizando actualmente. Si es así, puede intentarlo de nuevo después de cerrar ese proceso.\
Para los usuarios de Windows, puede abrir el 'Monitor de Recursos' para comprobar si otro proceso lo está utilizando actualmente. Si es así, puede intentarlo de nuevo después de cerrar ese proceso.\n\
Si no es así, comprueba si tu cuenta tiene suficientes privilegios para acceder a él.
exception.artifact_malformed=No se puede verificar la integridad de los archivos descargados .
exception.ssl_handshake=No se puede establecer una conexión SSL debido a que faltan certificados SSL en la instalación actual de Java. Puede intentar ejecutar HMCL en otra versión de Java y luego volver a intentarlo.
@@ -333,7 +342,7 @@ Por favor, intente cambiar a versiones de Java como Oracle Java 8 que tiene Java
fatal.javafx.missing=No se encontró un entorno JavaFX.\n\
Si está utilizando Java 11 o superior, por favor, descargue Oracle JRE 8 (java.com), o instale BellSoft Liberica Full JRE (bell-sw.com/pages/downloads).\n\
O, si está utilizando distribuciones de OpenJDK, asegúrese de que tiene incluido OpenJFX.
fatal.config_loading_failure=No se pueden cargar los archivos de configuración.\nPara macOS, intente colocar HMCL en un lugar con permiso que no sea "Escritorio", "Descargas" y "Documentos" y vuelva a intentarlo.
fatal.config_loading_failure=No se pueden cargar los archivos de configuración.\nPara macOS, intente colocar HMCL en un lugar con permiso que no sea "Escritorio", "Descargas" y "Documentos" y vuelva a intentarlo.\n\
¡Por favor, asegúrese de que "Hello Minecraft! Launcher" tiene acceso de lectura y escritura a "%s" y a los archivos que contiene.
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher ha sido actualizado. Por favor, reinicie el launcher.
fatal.apply_update_failure=Lo sentimos, pero Hello Minecraft! Launcher no puede actualizarse.\n\
@@ -345,8 +354,13 @@ fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal '\=', por lo
Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline.
feedback=Comentarios
feedback.channel=Canal de comentarios
feedback.discord=Discord
feedback.discord.statement=¡Únete a nuestra comunidad de Discord!
feedback.discord.statement=¡Únase a nuestro servidor Discord!
feedback.github=GitHub Issues
feedback.github.statement=Envíe una propuesta en GitHub.
feedback.qq_group=Grupo QQ de usuarios de HMCL
feedback.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios!
file=Archivo
@@ -374,7 +388,7 @@ game.crash.reason.bootstrap_failed=El juego se ha bloqueado debido al mod %1$s.\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.config=El juego se ha bloqueado porque un mod %1$s no puede analizar su archivo de configuración %2$s.
game.crash.reason.debug_crash=El juego se ha bloqueado porque lo has activado manualmente.
game.crash.reason.debug_crash=El juego se ha bloqueado porque lo has activado manualmente.\n\
\n\
Entonces, probablemente sabes por qué.
game.crash.reason.mixin_apply_mod_failed=El juego se ha bloqueado porque Mixin no puede aplicar el mod %1$s, no se puede continuar.\n\
@@ -431,7 +445,7 @@ game.crash.reason.loading_crashed_fabric=El juego se ha bloqueado debido al mod
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.memory_exceeded=El juego se ha bloqueado debido a que se ha asignado demasiada memoria para un pequeño archivo de paginación.\n\
\n\
Puede intentar desactivar la opción de asignación automática de memoria en la configuración y ajustar el valor hasta que el juego se inicie.
Puede intentar desactivar la opción de asignación automática de memoria en la configuración y ajustar el valor hasta que el juego se inicie.\n\
También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema.
game.crash.reason.mod=El juego se ha bloqueado debido al mod %1$s.\n\
\n\
@@ -470,22 +484,19 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (Cualquier versión)
game.crash.reason.forge_repeat_installation=El juego actual no puede continuar ejecutándose debido a una instalación duplicada de Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">Este es un problema conocido</a>\nSe recomienda cargar los comentarios del registro en GitHub para que podamos encontrar más pistas y arreglar esta pregunta. \nActualmente puede ir a la instalación automática para desinstalar Forge y volver a instalarlo.
game.crash.reason.optifine_repeat_installation=El juego actual no puede seguir ejecutándose debido a la instalación repetida de OptiFine. \nElimine OptiFine de la carpeta Mod o vaya a Gestión de juegos-Instalación automática para desinstalar OptiFine que se instala automáticamente.
game.crash.reason.modmixin_failure=El juego actual no puede continuar ejecutándose debido a algunas fallas en la inyección de mods. \nEsto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \nPuede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.file_or_content_verification_failed=El juego actual tiene un problema porque fallaron algunos archivos o la verificación de contenido. \nIntente eliminar esta versión (incluidas las modificaciones) y vuelva a descargarla, o intente usar un proxy cuando vuelva a descargar, etc.
game.crash.reason.mod_repeat_installation=El juego actual tiene múltiples Mods idénticos instalados repetidamente, y cada Mod solo puede aparecer una vez. Elimina los Mods repetidos y luego inicia el juego.
game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s
game.crash.reason.mod_solution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir ejecutándose debido a un problema con el perfil de mod.\nPuede consultar el registro del mod defectuoso y su archivo de configuración.
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge puede haber dado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.
game.crash.reason.mod_resolution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan.
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática.
game.crash.reason.modlauncher_8=El juego se ha bloqueado porque tu versión actual de Forge no es compatible con tu instalación de Java. Por favor, intente actualizar Forge, o intente utilizar Java 8u312/11.0.13/17.0.1 o versiones anteriores.\n\
\n\
8u312 y anteriores\:\n\
https://oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html
https://oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html\n\
\n\
11.0.13 y anteriores\:\n\
https://oracle.com/java/technologies/javase/jdk11-archive-downloads.html
https://oracle.com/java/technologies/javase/jdk11-archive-downloads.html\n\
\n\
17.0.1 y anteriores\:\n\
https://oracle.com/java/technologies/javase/jdk17-archive-downloads.html
@@ -494,21 +505,21 @@ game.crash.reason.no_class_def_found_error=El juego no puede ejecutarse debido a
A su instancia de juego le falta %1$s, esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\n\
\n\
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
game.crash.reason.no_sethod_error=El juego no puede ejecutarse debido a un código incompleto.\n\
game.crash.reason.no_such_method_error=El juego no puede ejecutarse debido a un código incompleto.\n\
\n\
Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\n\
\n\
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
game.crash.reason.opengl_not_supported=El juego se ha bloqueado porque OpenGL no es compatible con su controlador de gráficos.\n\
\n\
Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.
Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\n\
O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\n\
\n\
Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.
game.crash.reason.openj9=El juego no puede ejecutarse en una máquina virtual OpenJ9. Por favor, cambia a una VM Java Hotspot en la configuración del juego y vuelve a ejecutar el juego. Si no tienes una, puedes descargar una en línea.
game.crash.reason.out_of_memory=El juego se ha bloqueado porque se ha quedado sin memoria.\n\
\n\
Esto puede deberse a que no hay suficiente memoria disponible, o a que hay demasiados mods instalados. Puedes intentar solucionarlo aumentando la memoria asignada en la configuración del juego.
Esto puede deberse a que no hay suficiente memoria disponible, o a que hay demasiados mods instalados. Puedes intentar solucionarlo aumentando la memoria asignada en la configuración del juego.\n\
Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.
game.crash.reason.resolution_too_high=El juego se ha bloqueado porque estás utilizando un resource packs cuya resolución de textura es demasiado alta.\n\
\n\
@@ -517,12 +528,12 @@ game.crash.reason.shaders_mod=El juego actual no puede seguir ejecutándose porq
game.crash.reason.rtss_forest_sodium=El juego actual se bloquea debido a una incompatibilidad entre RivaTuner Statistics Server (RTSS) y Sodium, lo que provoca el fallo del juego. Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
game.crash.reason.stacktrace=El motivo del fallo es desconocido. Puede ver los detalles haciendo clic en el botón "Registros".\n\
\n\
Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo..\n\
Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo.\n\
\n\
%s
game.crash.reason.too_old_java=El juego se ha bloqueado porque estás utilizando una versión histórica de Java VM..\n\
\n\
Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde <a href="https://docs.microsoft.com/java/openjdk/download">here</a>.
Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
game.crash.reason.unknown=No somos capaces de averiguar por qué el juego se estrelló, por favor refiérase a los registros del juego.\n\
\n\
<b>Cuando pidas ayuda a otra persona, por favor, comparte con ella el registro completo del juego y el archivo de informe de fallos relacionado.</b>\n\
@@ -533,11 +544,6 @@ game.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque fa
Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\
Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\
De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.
game.crash.reason.failed_to_load_a_library=Failed to load a library.\n\
\n\
Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\
Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\
De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.
game.crash.title=Juego interrumpido
game.directory=Ruta del juego
game.version=Versión del juego
@@ -592,6 +598,36 @@ install.new_game.malformed=Nombre no válido
install.select=Seleccione una operación
install.success=Instalado con éxito.
java.add=Añadir Java
java.add.failed=Este Java no es válido o es incompatible con la plataforma actual.
java.disable=Deshabilitar este Java
java.disable.confirm=¿Está seguro de que desea desactivar este Java?
java.disabled.management=Java desactivado
java.disabled.management.remove=Eliminar este Java de la lista
java.disabled.management.restore=Activar este Java
java.download=Descargar Java
java.download.load_list.failed=Error al cargar la lista de versiones
java.download.more=Más distribuciones de Java
java.download.prompt=Elija la versión de Java que desea descargar:
java.download.distribution=Distribución
java.download.version=Versión
java.download.packageType=Tipo de paquete
java.management=Gestión Java
java.info.architecture=Arquitectura
java.info.vendor=Proveedor
java.info.version=Versión
java.info.disco.distribution=Distribución
java.install=Instalar Java
java.install.archive=Fuente Path
java.install.failed.exists=Este nombre ya tiene dueño
java.install.failed.invalid=Este archivo no es un paquete de instalación de Java válido, por lo que no se puede instalar.
java.install.failed.unsupported_platform=Este Java no es compatible con la plataforma actual, por lo que no puede instalarse.
java.install.name=Nombre
java.install.warning.invalid_character=Carácter ilegal en el nombre
java.reveal=Revelar el directorio de Java
java.uninstall=Desinstalar Java
java.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer!
lang=Español
lang.default=Usar idioma del sistema
@@ -615,8 +651,6 @@ launch.advice.too_large_memory_for_32bit=Has asignado un tamaño de memoria mayo
launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 o inferior sólo admite Java 8 para la plataforma Linux x86-64, porque las versiones posteriores no pueden cargar las bibliotecas nativas de 32 bits como liblwjgl.so\n\
\n\
Por favor, descárguelo de java.com, o instale OpenJDK 8.
launch.advice.vanilla_x86=Minecraft actualmente no proporciona soporte oficial para arquitecturas distintas de x86 o x86-64.\n\
Puedes intentar descargar la librería nativa de tu plataforma y especificar su ruta, luego vuelve a intentarlo.
launch.advice.vanilla_x86.translation=Minecraft no proporciona actualmente soporte oficial para arquitecturas distintas de x86 y x86-64.\n\
Por favor, instale Java para x86-64 para jugar a Minecraft a través del entorno de traducción Rosetta, o descargue sus bibliotecas nativas correspondientes y especifique su ruta.
launch.failed=No se puede ejecutar
@@ -654,7 +688,7 @@ launcher.background.translucent=Translúcido
launcher.cache_directory=Directorio de la caché
launcher.cache_directory.clean=Borrar caché
launcher.cache_directory.choose=Elegir el directorio de la caché
launcher.cache_directory.default=Por defecto (%AppData%/.minecraft o ~/.minecraft)
launcher.cache_directory.default=Por defecto (%APPDATA%/.minecraft o ~/.minecraft)
launcher.cache_directory.disabled=Desactivado
launcher.cache_directory.invalid=No se ha podido crear el directorio de la caché, volviendo a los valores por defecto.
launcher.contact=Contacta con nosotros
@@ -733,7 +767,6 @@ modpack.scan=Análisis del índice de modpacks
modpack.task.install=Instalar Modpack
modpack.task.install.error=No se ha podido identificar este modpack. Actualmente sólo soportamos los modpacks Curse, Modrinth, MultiMC y MCBBS.
modpack.type.curse=Curse
modpack.type.curse.tolerable_error=No se han podido descargar las dependencias, puedes intentar continuar la descarga ejecutando esta instancia del juego.
modpack.type.curse.error=No se han podido descargar las dependencias de CurseForge, inténtalo de nuevo o utiliza una conexión proxy.
modpack.type.curse.not_found=Algunas dependencias ya no están disponibles, por favor intenta instalar una versión más reciente del modpack.
modpack.type.manual.warning=Este archivo contiene una copia completa de una instancia de Minecraft. La mayoría de las veces, sólo tienes que descomprimirlo y ejecutar el juego usando su launcher incorporado. Pero, HMCL todavía puede importarlo, sin garantía de su utilidad, ¿aún continúa?
@@ -741,7 +774,7 @@ modpack.type.mcbbs=Tipo MCBBS
modpack.type.mcbbs.export=Puede ser importado por ¡Hello Minecraft! Launcher
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
¡modpack.type.multimc.export=Puede ser importado por Hello Minecraft! Launcher y MultiMC
modpack.type.multimc.export=Puede ser importado por Hello Minecraft! Launcher y MultiMC
modpack.type.server=Actualización automática del modpack desde el servidor
modpack.type.server.export=Permite al propietario del servidor actualizar la instancia del juego de forma remota
modpack.type.server.malformed=Manifiesto de modpack inválido, por favor, consulte al creador del modpack del servidor para solucionar este problema.
@@ -758,7 +791,7 @@ modpack.wizard.step.initialization.exported_version=Versión del juego a exporta
modpack.wizard.step.initialization.force_update=Forzar la actualización del modpack a la última versión (necesitarás un servicio de alojamiento de archivos)
modpack.wizard.step.initialization.include_launcher=Incluir el launcher
modpack.wizard.step.initialization.save=Exportar a...
modpack.wizard.step.initialization.warning=Antes de crear un modpack, por favor asegúrate de que el juego se ejecuta, y que es una versión de lanzamiento en lugar de una versión instantánea. El launcher guardará su configuración de descarga.
modpack.wizard.step.initialization.warning=Antes de crear un modpack, por favor asegúrate de que el juego se ejecuta, y que es una versión de lanzamiento en lugar de una versión instantánea. El launcher guardará su configuración de descarga.\n\
\n\
Ten en cuenta que no se te permite añadir mods y resource packs que se digan explícitamente que no se pueden distribuir o poner en un modpack.
modpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente.
@@ -797,7 +830,8 @@ mods.add.success=%s fue instalado con éxito.
mods.category=Categoría
mods.check_updates=Comprobar actualizaciones
mods.check_updates.current_version=Versión actual
mods.check_updates.failed=No se han podido descargar algunos de los archivos.
mods.check_updates.failed_check=No se ha podido comprobar si hay actualizaciones.
mods.check_updates.failed_download=No se han podido descargar algunos de los archivos.
mods.check_updates.file=Archivo
mods.check_updates.source=Fuente
mods.check_updates.target_version=Versión de destino
@@ -937,8 +971,8 @@ settings.advanced.post_exit_command=Comando post-salida
settings.advanced.post_exit_command.prompt=Comandos a ejecutar tras la salida del juego
settings.advanced.server_ip=Dirección del servidor
settings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego
settings.advanced.use_native_glfw=[Sólo Linux] Utilizar GLFW nativo
settings.advanced.use_native_openal=[Sólo Linux] Utilizar OpenAL nativo
settings.advanced.use_native_glfw=[Sólo Linux/FreeBSD] Utilizar GLFW nativo
settings.advanced.use_native_openal=[Sólo Linux/FreeBSD] Utilizar OpenAL nativo
settings.advanced.workaround=Métodos alternativos
settings.advanced.workaround.warning=Éstas opciones están pensadas sólo para usuarios expertos. Jugar con estas opciones puede romper el juego. A menos que sepas lo que estás haciendo, por favor no modifiques estas opciones.
settings.advanced.wrapper_launcher=Wrapper Launcher
@@ -1022,8 +1056,8 @@ settings.type.special.edit=Editar ajustes de la instancia actual
settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones.
sponsor=Donantes
sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI. Haz clic aquí para obtener más información.
sponsor.hmcl=¡Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Usted puede donar a nosotros en Afdian para apoyar nuestro alojamiento de archivos y el desarrollo del proyecto. Haga clic aquí para obtener más información.
sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información.
sponsor.hmcl=Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Haga clic aquí para obtener más información.
system.architecture=Arquitectura
system.operating_system=Sistema operativo
@@ -1033,15 +1067,13 @@ update.accept=Actualizar
update.changelog=Registro de cambios
update.channel.dev=Beta
update.channel.dev.hint=Actualmente está utilizando una versión beta del launcher, que puede incluir algunas características adicionales, pero también es más inestable que las versiones de lanzamiento.\n\
\n\
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello, o bien para decírnoslo en nuestro <a href="https://discord.gg/jVvC7HfM6U">Discord</a> o en la comunidad <a href="https://docs.hmcl.net/groups.html">QQ</a>.\n\
\n\
<a href="hmcl://hide-announcement">Haga clic aquí</a> para ocultar el recordatorio para la versión de prueba actual.
\n\
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello.
update.channel.dev.title=Aviso de versión beta
update.channel.nightly=Nocturna
update.channel.nightly.hint=Estás utilizando una versión nocturna del launcher, que puede incluir algunas características adicionales, pero también es siempre más inestable que las otras versiones.\n\
\n\
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello, o bien para decírnoslo en nuestro <a href="https://discord.gg/jVvC7HfM6U">Discord</a> o en la comunidad <a href="https://docs.hmcl.net/groups.html">QQ</a> community.
\n\
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello.
update.channel.nightly.title=Aviso de versión nocturna
update.channel.stable=Estable
update.checking=Buscando actualizaciones
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2023 huangyuhui <huanghongxun2008@126.com> and contributors
# 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
@@ -17,31 +17,36 @@
#
# 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 © 2024 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=マント
@@ -128,13 +133,14 @@ archive.date=公開日
archive.file.name=名前
archive.version=バージョン
Assets.download=アセットのダウンロード
Assets.download_all=アセットの整合性チェック
Assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。
assets.download=アセットのダウンロード
assets.download_all=アセットの整合性チェック
assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。
button.cancel=キャンセル
button.change_source=ダウンロードソースの変更
button.clear=クリア
button.copy_and_exit=コピーして終了
button.delete=削除
button.edit=編集
button.install=インストール
@@ -143,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=カスタムカラー
crack.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。
crack.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
@@ -313,9 +323,15 @@ fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か
fatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder.
fatal.illegal_char=ユーザーフォルダーのパスに不正な文字'='が含まれています, ログインアカウントやオフラインログインではスキンの変更ができなくなり。
feedback=フィードバック
feedback.channel=フィードバックチャンネル
feedback.discord=Discord
feedback.discord.statement=チャットに参加しよう!
feedback.discord.statement=Discordサーバーに参加してください!
feedback.github=GitHub Issues
feedback.github.statement=GitHubで問題を送信します。
feedback.qq_group=HMCLユーザーQQグループ
feedback.qq_group.statement=ユーザーQQグループに参加してください!
file=ファイル
@@ -343,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 をアンインストールしてから再インストールしてください。
@@ -393,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=ゲームバージョン
@@ -448,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=システム言語を使用する
@@ -460,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ビットネイティブライブラリをロードできません。
@@ -499,7 +541,7 @@ 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=お問い合わせ
@@ -574,8 +616,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のインポートを試みることができます、続行しますか?
@@ -625,7 +665,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
@@ -761,8 +802,8 @@ settings.advanced.post_exit_command=終了後のコマンド
settings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます
settings.advanced.server_ip=サーバーアドレス
settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する
settings.advanced.use_native_glfw=システムGLFWを使用する
settings.advanced.use_native_openal=システムOpenALを使用する
settings.advanced.use_native_glfw=[Linux/FreeBSDのみ]システムGLFWを使用する
settings.advanced.use_native_openal=[Linux/FreeBSDのみ]システムOpenALを使用する
settings.advanced.workaround=デバッグ用オプション
settings.advanced.workaround.warning=デバッグオプションはプロフェッショナルのみ使用可能です。デバッグオプションにより、ゲームが起動しない場合があります。これらのオプションは、ご自分が何をしているのかが分からない限り、変更しないでください。
settings.advanced.wrapper_launcher=パッキングオーダー
@@ -843,8 +884,8 @@ settings.type.special.edit=現在のゲーム設定を構成します
settings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。
sponsor=寄付
sponsor.bmclapi=ダウンロードサービスはBMCLAPIによって提供されます。詳細については、ここをクリックしてください。
sponsor.hmcl=こんにちはマインクラフト! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。私たちはAfdianを使用して、ホスティングとプロジェクト開発の費用を引き続き支払います。詳細については、ここをクリックしてください。
sponsor.bmclapi=中国本土向けのダウンロードサービスはBMCLAPIによって提供されています。詳細については、ここをクリックしてください。
sponsor.hmcl=Hello Minecraft! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。詳細については、ここをクリックしてください。
system.architecture=Arch
system.operating_system=OS
@@ -853,15 +894,14 @@ update=更新
update.accept=更新
update.changelog=変更
update.channel.dev=ベータ
update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\
ベータ版は不安定なはずです!\n\
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット <a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://docs.hmcl.net/groups.html">QQ</a>で問題を報告します。\n\n\
現在のテスト版のヒントを非表示にするには、<a href="hmcl://hide-announcement">ここをクリックしてください</a>。
update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。ベータ版は不安定なはずです!\n\
\n\
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告してください。
update.channel.dev.title=ベータ版のヒント
update.channel.nightly=アルファ
update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\
アルファ版は不安定です!\n\
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット <a href="https://discord.gg/jVvC7HfM6U">Discord</a>で問題を報告します。
update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。アルファ版は不安定です!\n\
\n\
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告してください。
update.channel.nightly.title=アルファ版のヒント
update.channel.stable=リリース
update.checking=更新の確認
@@ -1,44 +1,47 @@
#
# Hello Minecraft! Launcher
# Авторские права (C) 2023 huangyuhui <huanghongxun2008@126.com> и участников
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
#
# Эта программа является бесплатным программным обеспечением:
# вы можете распространять и/или изменять её на условиях GNU
# General Public License, опубликованной Free Software Foundation,
# либо 3й версией лицензии, либо (по вашему выбору) любой версией выше.
# 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.
#
# Эта программа распространяется в надежде, что она окажется полезной,
# но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемых гарантий
# ТОВАРНОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ.
# Подробности смотрите в Стандартной общественной лицензии GNU.
# 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.
#
# Вместе с этой программой вы должны были получить копию GNU
# General Public License. Если нет, смотрите <https://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Соавторы: vanja-san
#
# Contributors: vanja-san
about=О лаунчере
about.copyright=Авторские права
about.copyright.statement=Авторские права © 2024 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.
@@ -157,8 +160,11 @@ button.retry=Повторить снова
button.save=Сохранить
button.save_as=Сохранить как
button.select_all=Выбрать все
button.view=Просмотреть
button.yes=Да
chat=Групповой чат
color.recent=Рекомендуемые
color.custom=Пользовательский цвет
@@ -336,8 +342,13 @@ fatal.samba=Если вы пытаетесь запустить лаунчер
fatal.illegal_char=Недопустимый символ '=' в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\nВы не сможете использовать аккаунт authlib-injector или изменить офлайн скин.
feedback=Обратная связь
feedback.channel=Канал обратной связи
feedback.discord=Discord
feedback.discord.statement=Присоединиться!
feedback.discord.statement=Добро пожаловать присоединиться к нашему Discord.
feedback.github=Проблемы GitHub
feedback.github.statement=Отправьте проблему на GitHub.
feedback.qq_group=Группа QQ пользователя HMCL
feedback.qq_group.statement=Добро пожаловать присоединиться к нашей группе QQ!
file=Файл
@@ -365,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 в настройках версии - Автоматическая установка.
@@ -416,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=Версия игры
@@ -471,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=Использовать язык системы
@@ -526,7 +563,7 @@ 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=Связаться с нами
@@ -602,8 +639,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 может попытаться импортировать этот модпак, продолжать?
@@ -661,7 +696,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=Целевая версия
@@ -708,7 +744,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=Сложность
@@ -831,8 +867,8 @@ settings.advanced.post_exit_command=Команда после выхода
settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры
settings.advanced.server_ip=Адрес сервера
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
settings.advanced.use_native_glfw=Использовать системный GLFW
settings.advanced.use_native_openal=Использовать системный OpenAL
settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW
settings.advanced.use_native_openal=[Только для Linux/FreeBSD] Использовать системный OpenAL
settings.advanced.workaround=Обходные пути
settings.advanced.workaround.warning=Варианты обхода предназначены только для опытных пользователей. Изменение этих параметров может привести к вылету игры. Если не знаете, что делаете, то не изменяйте эти параметры.
settings.advanced.wrapper_launcher=Команда-оболочка
@@ -917,8 +953,8 @@ settings.type.special.edit=Изменить настройки текущего
settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры.
sponsor=Спонсоры
sponsor.bmclapi=Скачивание предоставляется BMCLAPI. Нажмите здесь для получения подробностей.
sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Пожертвуйте нам на Afdian для поддержки нашего файлового хостинга и развития проекта. Для получения подробностей нажмите здесь.
sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей.
sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Для получения подробностей нажмите здесь.
system.architecture=Архитектура
system.operating_system=Операционная система
@@ -927,15 +963,14 @@ update=Обновление
update.accept=Обновить
update.changelog=Изменения
update.channel.dev=Бета
update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования.\n\
Бета-версия может быть нестабильной!\n\
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://docs.hmcl.net/groups.html">QQ</a>, чтобы сообщить о проблемах.\n\n\
<a href="hmcl://hide-announcement">Нажмите здесь</a>, чтобы скрыть подсказку для текущей тестовой версии.
update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования. Бета-версия может быть нестабильной!\n\
\n\
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>.
update.channel.dev.title=Подсказки для бета-версии
update.channel.nightly=Альфа
update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования.\n\
Альфа-версия может быть нестабильной!\n\
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://docs.hmcl.net/groups.html">QQ</a>, чтобы сообщить о проблемах.
update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования. Альфа-версия может быть нестабильной!\n\
\n\
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>.
update.channel.nightly.title=Подсказки для альфа-версии
update.channel.stable=Релиз
update.checking=Проверка наличия обновлений
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -30,25 +30,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "win",
"sha1": "f33d0776b2b027c2ea9267c184f9048ef94c4deb"
"sha1": "ae86377359860449040372d3693bccf046148513"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "win",
"sha1": "951ca77bd50ef4fd44f4b7fb1e38301088fde4e2"
"sha1": "bc0055a059b201d6c0be2293ccf72b6d2874fd1c"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "win",
"sha1": "78ac7de48d0aac48e7d5282efc81bab7f337b587"
"sha1": "bcd765dca36fa9e5fb4c997491a2d908947603f5"
}
],
"windows-arm64": [
@@ -134,25 +134,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "linux",
"sha1": "9e4ca5ce2b87c479d2cc445ff411a48aeae76936"
"sha1": "875f02b498b00c9692f6ed0af3da373b1d07bf03"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "linux",
"sha1": "27c64e4e1569ddf198f28eda84cf8d014ab80693"
"sha1": "103f3bba5a177ba8912a92ef2ee0855de7ba050c"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "linux",
"sha1": "b5b2a8ead40bd43476740aa51697bb1cac23de5c"
"sha1": "940e9e05a974a3e484d5ec241d88a0c77ad013f2"
}
],
"linux-arm32": [
@@ -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;
@@ -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;
+8 -2
View File
@@ -4,9 +4,9 @@ plugins {
dependencies {
api("org.glavo:simple-png-javafx:0.3.0")
api("com.google.code.gson:gson:2.10.1")
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")
@@ -14,4 +14,10 @@ dependencies {
api("org.apache.commons:commons-compress:1.25.0")
api("org.jsoup:jsoup:1.18.1")
compileOnlyApi("org.jetbrains:annotations:24.1.0")
if (JavaVersion.current().isJava8) {
org.gradle.internal.jvm.Jvm.current().toolsJar?.let {
compileOnly(files(it))
}
}
}
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -69,6 +70,14 @@ public abstract class Account implements Observable {
*/
public abstract AuthInfo playOffline() throws AuthenticationException;
public boolean canUploadSkin() {
return false;
}
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
throw new UnsupportedOperationException("Unsupported Operation");
}
public abstract Map<Object, Object> toStorage();
public void clearCache() {
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more