Compare commits

..
108 Commits
Author SHA1 Message Date
3gf8jv4dv 8d3794d828 fix(i18n): split game.version to make different scenarios appropriate (#3709)
- `game.version` indicates the currently used game instance
in the game crash window, not the game version.

- Change the i18n key previously used in ModListPageSkin to
`mods.game.version`, to be consistent with the world section.
2025-03-07 21:06:34 +08:00
3gf8jv4dv d1b59ef553 fix(locale): revise spanish (#3713)
Fix some mistakes. :\
2025-03-07 20:20:04 +08:00
3gf8jv4dv 79f29e143e fix(locale): clean up language typos (#3707) 2025-03-07 19:35:05 +08:00
Glavo e7ffb0b271 优化世界管理页面 (#3711)
* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update
2025-03-07 19:19:04 +08:00
3gf8jv4dv cda2f6fb82 chore(locale): improve spanish translation (#3712)
- Add translation of the `game.crash` part.
- Improve further the translation of the remaining parts.
2025-03-07 17:34:40 +08:00
3gf8jv4dv 114aa4ce94 chore(locale): add missing translations for spanish (#3710)
Several i18n keys were added during the 3.6.12 semester.
This patch adds those translations.
2025-03-07 17:12:34 +08:00
Glavo 0ff194b391 Fix #3485: 修复游戏 Java 路径显示错误的问题 (#3708) 2025-03-06 12:30:13 +08:00
3gf8jv4dvandGlavo 025bde66d6 chore(locale): improve russian translation (game crash part) (#3704)
* chore(locale): improve russian translation (game crash part)

- Re-translate something from English
- Fix and unify the style of text left by different contributors
- Fix some inconsistent/incorrect terminology
- Add missing translations

* Update I18N_ru.properties

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-03-06 12:00:37 +08:00
Glavo 872c1943b7 修复 Javadoc 中的无效链接 (#3703) 2025-03-05 19:00:22 +08:00
Glavo d268d923d7 更新全部 SVG 图标至 Material Symbols (#3687) 2025-03-05 16:39:21 +08:00
3gf8jv4dv d523ccd312 fix(ui&locale): optimize the create offline account dialog (#3695)
- Adjust lblErrorMessage for invalid username to avoid ui truncation
- Improve the wording of warnings
2025-03-05 16:05:02 +08:00
3gf8jv4dv 78a5ade35a fix(locale): fix english translation of game.crash.reason.file_changed (#3699)
My mistake in https://github.com/HMCL-dev/HMCL/pull/3367
2025-03-05 16:02:56 +08:00
Glavo f011a14798 在 DDE 上禁用自绘窗口动画 (#3701) 2025-03-05 16:01:48 +08:00
Glavo f4701c678e Revert "启用高帧率动画 (#3655)" (#3700)
This reverts commit a45b636b1c.
2025-03-05 14:45:02 +08:00
Zkitefly e61fb27c61 更新 PURCHASE_URL (#3675)
* Update YggdrasilService.java

* Update YggdrasilService.java
2025-03-05 02:54:39 +08:00
3gf8jv4dv d2f58a89a2 fix(locale): remove duplicate extensions (#3692)
Oops... Didn't notice this before.

The issue first appeared in https://github.com/HMCL-dev/HMCL/pull/2468
2025-03-03 15:58:53 +08:00
3gf8jv4dvandGlavo 5174e0a67a chore(locale): revise english (#3688)
* chore(locale): revise english

`settings.launcher.launcher_log.reveal`
Considering that HMCL is a cross-platform launcher and not all systems
use the term `Explorer`, this should be changed to a more generic term.

`update.channel.dev.hint` and `update.channel.nightly.hint`
Revise text style.

Misc
Resolve rendering issues on some text editors.

* Update I18N.properties

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-03-03 10:41:03 +08:00
3gf8jv4dv 4225e98d6b chore(locale): improve russian translation (curseforge category) (#3686)
- Add translations
- Some proper nouns (mod name) keep the original name
- Some proper nouns (game content) follow the game and wiki
2025-03-03 02:12:46 +08:00
Glavo 0540f3c9cd 在世界管理界面显示世界图标 (#3689)
* 在世界管理界面显示世界图标

* update

* update
2025-03-03 02:12:07 +08:00
Glavo 4292d47fe8 Bump Gson to 2.12.1 (#3691) 2025-03-02 23:58:07 +08:00
3gf8jv4dv 4e98d80855 chore(locale): improve russian translation (setting, launch, menu, etc) (#3684)
Fully revision, except for the `curse` and `game.crash.reason` parts.

- Re-translate something from English
- Fix some inconsistent terminology
- Change some terminology to make sure to keep up with the game and wiki
- Add some missing translations
- Revise some incorrect/outdated translations
2025-03-02 23:56:55 +08:00
Glavo 578aaafb0a 使用 DDE 桌面时启用窗口动画 (#3682) 2025-03-02 00:28:44 +08:00
3gf8jv4dv 60deea64c8 chore(locale): improve russian translation (mods & nbt) (#3678)
- add `mods.check_updates.empty`
- add nbt translations
2025-03-01 21:05:08 +08:00
3gf8jv4dv 7e1cd4319b chore(locale): improve russian translation (modpack) (#3679)
- Revise some translations based on English
- Change some terminology to make sure to keep up with the game and wiki
- Add some missing translations
2025-03-01 21:04:54 +08:00
Glavo 318da840f9 修复对话框关闭动画缺失的问题 (#3676) 2025-03-01 21:01:51 +08:00
Glavo 0c7046d764 优化 TransitionPane (#3680)
* update

* update

* update

* update

* update

* update

* update

* update

* update

* Fix checkstyle
2025-03-01 20:50:31 +08:00
Glavo bdf4886445 禁止标题栏中返回键获取焦点 (#3677) 2025-03-01 10:20:39 +08:00
Glavo 4a758d045d 使更多标签支持双击复制 (#3673) 2025-03-01 01:47:56 +08:00
Glavo 1059a4b3d4 支持关闭鼠标进入 RipplerContainer 时的渐变动画 (#3670)
* update

* fix checkstyle
2025-02-28 23:56:42 +08:00
Glavo a25eb14da4 优化 TransitionPane (#3668) 2025-02-28 23:41:40 +08:00
Glavo 349b663c83 禁止标题栏按钮获取焦点 (#3666) 2025-02-28 01:54:53 +08:00
Glavo 04781da3a7 清理 FXUtils (#3667) 2025-02-28 01:53:31 +08:00
Glavo 96e1ddff79 Close #3664: 支持双击复制世界信息 (#3665)
* Close #3664: 支持双击复制世界信息

* update

* update
2025-02-27 23:58:20 +08:00
3gf8jv4dv e8502890a4 chore(locale): improve russian translation (game setting / management) (#3661)
- Re-translate something from English
- Fix some inconsistent terminology
- Revise text to fit UI
- Change some terminology to make sure to keep up with the game and wiki
- Add some missing translations
2025-02-27 21:19:31 +08:00
3gf8jv4dv 4d0619a9df chore(locale): improve russian translation (javafx dialog / search) (#3662) 2025-02-27 21:18:57 +08:00
3gf8jv4dv fba14737f9 chore(locale): revise unofficial hint for russian (#3660)
- add `unofficial.hint`
- fix typo in `account.methods.microsoft.snapshot`
2025-02-27 20:56:59 +08:00
3gf8jv4dv 5de98399c5 chore(locale): add wiki translation for russian (#3659) 2025-02-27 20:56:41 +08:00
Glavo 7adfa0e3d1 启动时尝试从 HMCL_DIRECTORY 中加载字体 (#3663)
* 启动时尝试从 HMCL_DIRECTORY 中加载字体

* update

* update
2025-02-27 15:05:51 +08:00
Glavo a45b636b1c 启用高帧率动画 (#3655)
* 启用高帧率动画

* update
2025-02-27 09:51:06 +08:00
Glavo 50c22e1f43 添加最小化和还原动画 (#3657)
* update

* update

* update

* update

* update

* update

* update

* update

* update

* fix checkstyle
2025-02-26 20:39:49 +08:00
Glavo 3b38c22398 修复标题过长时窗口异常的问题 (#3654) 2025-02-26 02:00:27 +08:00
Glavo 618243350e 优化 NBT 查看器 (#3653)
* update

* update

* update
2025-02-26 01:40:36 +08:00
Glavo ff4940e79f 添加关闭启动器时的淡出动画 (#3652)
* 添加关闭启动器时的淡出动画

* update
2025-02-26 01:04:12 +08:00
Glavo 1c163934ee Fix #2048: 当微软账户 Token 过期时应当刷新账户 (#3651) 2025-02-25 20:50:49 +08:00
Glavo c513d00c50 添加启动动画 (#3442)
* update

* update

* update
2025-02-25 18:20:44 +08:00
3gf8jv4dv 2c9549b36b fix(ui): limit width of FileSelector in offline skin pane (#3623)
make the text around the selector not be covered.

resolves https://github.com/HMCL-dev/HMCL/issues/3296
2025-02-25 17:58:31 +08:00
Glavo 3389e0a934 Close #3649: 点击账户卡片空白处时切换账户 (#3650)
* Close #3649: 点击账户卡片空白处时切换账户

* update
2025-02-25 17:36:51 +08:00
3gf8jv4dv 78149449a9 chore(locale): improve russian translation (modrinth category) (#3647)
- Add missing translations
- Fix existing translations
2025-02-25 17:33:07 +08:00
3gf8jv4dv b792b23ad4 chore(locale): improve russian translation (web) (#3648) 2025-02-25 17:32:27 +08:00
3gf8jv4dv 881b0e9127 chore(locale): improve russian translation (account part) (#3642)
- Re-translate some translations based on English
- Fix some inconsistent terminology
  - Such as Account, учетная запись > аккаунт
- Add some missing translations
- Reorder some keys to ensure they match with English
- Adjust terminology related to LittleSkin to ensure consistency with
the terminology used on the site
2025-02-23 10:48:45 +08:00
3gf8jv4dv 77e06c8820 chore(locale): adjust english translation of account.portable (#3643)
Match the style of the account type shown for the account item
on account list page
2025-02-23 10:48:03 +08:00
Glavo bc2405cf12 修复在 macOS 平台无法找到通过 DMG 安装的 Java 的问题 (#3645) 2025-02-23 10:47:25 +08:00
Glavo 72b5428f8b 修复 NeoForge 版本识别错误的问题 (#3638) 2025-02-22 17:28:25 +08:00
Glavo e7e8d45497 禁止整合包复制全局游戏设置 (#3639)
* 禁止整合包复制全局游戏设置

* update
2025-02-22 14:35:54 +08:00
3gf8jv4dv 82dbe18d78 fix(i18n): fix a missing i18n for LittleSkin term (#3637)
The word "LittleSkin" appears twice in OfflineAccountSkinPane, but one
of them is hardcoded. Add i18n support for the hardcoded one.
2025-02-22 08:36:34 +08:00
3gf8jv4dv ecb7ef0867 fix(locale): remove accidental japanese characters from russian (#3636)
This bug first appeared in https://github.com/HMCL-dev/HMCL/pull/2412
2025-02-22 00:50:59 +08:00
Glavo d40742a624 Bump jsoup to 1.18.3 (#3632) 2025-02-19 16:28:46 +08:00
Glavo 6f8002a0cf Fix typo in FXUtils (#3631) 2025-02-19 15:34:08 +08:00
3gf8jv4dv 3d3896c5c4 fix(i18n): add i18n support for steve & alex option in offline skin pane (#3630)
Some Slavic Minecraft communities have translated Steve and Alex,
such as Russian and Ukrainian. They need a translation.

ref:
- https://ru.minecraft.wiki/w/%D0%A1%D0%BA%D0%B8%D0%BD
- https://uk.minecraft.wiki/w/%D0%A1%D0%BA%D1%96%D0%BD
2025-02-19 15:30:34 +08:00
Glavoand3gf8jv4dv a24f8e2ec0 在游戏设置中添加“复制全局游戏设置”选项 (#3628)
* update

* update

* Fix checkstyle

* update

* fix checkstyle

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

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

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

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

* Fix FXUtils.bindEnum

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-02-19 15:29:50 +08:00
Glavo fce08a6923 删除全局游戏设置页面的离线账户更换皮肤提示 (#3629) 2025-02-19 13:38:51 +08:00
Glavo 5fa01c8838 修复在非官方支持的平台上 Minecraft 1.21 与 Sodium 不兼容的问题 (#3622)
* update

* update

* update
2025-02-18 23:58:16 +08:00
Glavo 15a3fb5f59 优化 VersionsPage 搜索框 (#3625)
* update

* update

* update

* update
2025-02-18 23:51:28 +08:00
Glavo d5ab14f476 Fix #3295: 修复离线账户选择 Steve/Alex 皮肤时模型错误的问题 (#3626)
* Fix #3295: 修复离线账户选择 Steve/Alex 皮肤时模型错误的问题

* update
2025-02-18 22:46:52 +08:00
Glavo 554ecfd6d5 Fix #3445: 点击安装整合包时无条件关闭整合包下载对话框 (#3621) 2025-02-17 23:35:58 +08:00
Glavoand3gf8jv4dv c2abe755f4 优化整合包下载对话框 (#3620)
* update

* 优化整合包下载对话框

* update

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

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

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-02-17 22:44:25 +08:00
Glavo c0410c46ff 在日志中记录 PROCESSOR_IDENTIFIER (#3619) 2025-02-16 21:10:58 +08:00
Glavo da93b4c12d 修复无法识别 Intel64 架构的问题 (#3618) 2025-02-16 17:23:14 +08:00
Zkitefly c16d6e8d5f 添加 NeoForge 的 BMCLAPI 代理支持 (#3605) 2025-02-15 16:38:30 +08:00
Glavo 6df73aeb68 修复语言设置中英语选项名称错误的问题 (#3612) 2025-02-15 12:19:09 +08:00
3gf8jv4dv 23d11eca81 fix(locale): adjust time formats for various languages (#3613)
- Change to a standard and commonly used format.
- Rename i18n key as it is used in more than one place.
2025-02-15 12:16:42 +08:00
Glavo 0e307f3b71 简化 AdvancedListBox (#3611) 2025-02-12 21:53:10 +08:00
Glavo 33b791f2b0 更新日期时间格式 (#3610)
* Revert "improv(i18n): refine time formats on ui (#3608)"

This reverts commit 741f65d37c.

* update
2025-02-12 19:37:05 +08:00
3gf8jv4dv de01946ab2 fix(locale): fix a typo in zh-TW language (#3606) 2025-02-12 17:35:03 +08:00
3gf8jv4dv 5526bac70e fix(locale): change Socks to SOCKS (#3607)
ref: https://en.wikipedia.org/wiki/SOCKS
2025-02-12 17:33:31 +08:00
3gf8jv4dv 741f65d37c improv(i18n): refine time formats on ui (#3608)
- unify time formats using DateTimeFormatter, deprecate `world.time`
- change FormatStyle to make the time detail appropriate
- fix incorrect time format in English locale
2025-02-12 17:32:36 +08:00
Haowei Wen ecafc9d6f0 Fix height computation in ComponentListCell 2025-02-11 23:51:50 +08:00
Glavo 8795d522b0 支持临时隐藏预览版提示 (#3603) 2025-02-11 19:00:03 +08:00
Glavo d8e7e0e97e 更新 mod 数据 (#3601)
* 更新 mod 数据

* update

* update

* update
2025-02-10 22:04:48 +08:00
Glavo 8236dfc7e5 Fix #3595: patchNative may throw NPE when gameVersion is null (#3602) 2025-02-10 19:59:25 +08:00
Glavo 8a15b7d274 修复使用自定义字体时未正确设置字重的问题 (#3598) 2025-02-10 00:41:31 +08:00
3gf8jv4dv fce4c13515 fix(locale): fix zh-tw locale for game crash window buttons (#3596) 2025-02-09 09:28:00 +08:00
Glavo 2f36506099 Fix #3508: Java 下载对话框中切换包类型时不应重置版本字段 (#3594)
* Fix #3508: Java 下载对话框中切换包类型时不应重置版本字段

* fix checkstyle
2025-02-08 18:02:46 +08:00
Glavo 5e60eaf595 修复 macOS 平台 HMCLauncher.sh 不会显示 Java 下载页面的问题 (#3549) 2025-01-29 07:35:19 +08:00
Glavo f70fb4ae08 Update README (#3548) 2025-01-29 07:27:17 +08:00
3gf8jv4dv 38adb48051 Fix qq badge in readme (#3544)
ref: https://shields.io/blog/simple-icons-14
2025-01-25 16:38:55 +08:00
Glavo f972c49762 Fix #3539: 安装 MultiMC 整合包后应当设置图标 (#3541) 2025-01-24 09:11:22 +08:00
Glavo 5afeb3d019 优化 HMCLauncher.sh (#3538) 2025-01-23 18:21:29 +08:00
Burning_TNT 38897fbd2a Fix #3063 修复本地模组管理界面水平滚动条显示异常的问题 (#3463) 2025-01-23 13:51:55 +08:00
ZkiteflyandGlavo 0562042218 Update HMCLauncher.sh (#3265)
* Update HMCLauncher.sh

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-01-23 13:17:33 +08:00
Glavo c5e7ee8aef 优化对 Linux RISC-V 64 平台的支持 (#3536) 2025-01-23 10:23:27 +08:00
Glavo 35b288b800 Fix #3529: 修复在 FreeBSD x86-64 平台未正确替换 LWJGL 本地库的问题 (#3531)
* Fix #3529: 修复在 FreeBSD x86-64 平台未正确替换 LWJGL 本地库的问题

* update

* update

* update
2025-01-20 14:03:11 +08:00
Zkitefly a609d32081 更新版权年份至2025 (#3527) 2025-01-16 11:26:31 +08:00
Haowei Wen 72d8b75643 模组列表页中按下 Esc 时取消选中 (#3523)
* 模组列表页中按下 Esc 时取消选中

目前在模组列表页中选中模组后,按下 Esc 是不会有任何反应的,因为 [ListView](https://github.com/openjdk/jfx/blob/5b074c4c2b4f539301813322a6589cec28121553/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ListViewBehavior.java#L108) 会处理这个事件,认为是取消编辑,而不论是否处于编辑模式中。

这个 PR 添加了 `FXUtils.ignoreEvent()` 函数,用于忽略掉对特定事件的处理。然后,我们就可以让 ListView 不处理 Esc 按下事件,然后在模组页添加 Esc 处理逻辑(必须让 ListView 忽略掉这个事件,不然就会被它优先处理)。

理论上所有用了 ListView / JFXListView 的地方都会有这个问题,即当焦点在 ListView 中时,按下 Esc 不会触发正常的事件处理(如返回上一页、关闭对话框)。比如在游戏下载页面中,对某个版本按下右键后,焦点就会转移到其上,此时再按 Esc 就毫无反应。

* 修复 Esc 在版本下载页/模组下载页/数据包页不工作
2025-01-16 11:15:29 +08:00
3gf8jv4dvandyushijinhun 01f9a452f1 Update ISSUE_TEMPLATE [skip ci] (#3446)
* Update ISSUE_TEMPLATE [skip ci]

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:40:49 +08:00
3gf8jv4dvandyushijinhun f2edc8001c Revise Spanish & English (#3465)
* Revise Spanish & English

Overhaul for Spanish. I cannot guarantee it is 100% correct, but I
promise to proofread it. During the overhaul some more mistakes in the
English language were discovered, so let's fix it.

All translations have been revised except for the `game.crash.reason`
series.

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:37:19 +08:00
3gf8jv4dvandyushijinhun d2df515325 Revise README [skip ci] (#3447)
* Revise README [skip ci]

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:32:42 +08:00
3gf8jv4dv 95e39fe87a gha: revise release description [skip ci] (#3474)
unify text style
2025-01-06 03:12:13 +08:00
lijiaxuanand3gf8jv4dv 662bf1a9d4 Update I18N.properties English Translations (#3477)
* Update I18N.properties English Translations

更新了英文语言翻译文件,修复了一些语法错误并且让一些句子更加通顺

* Update I18N.properties

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

* Update I18N.properties

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

* Update I18N.properties

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

* Update I18N.properties

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

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

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

* Update I18N.properties

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

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

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

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

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-01-06 02:15:58 +08:00
Zkiteflyand3gf8jv4dv bfda297838 检查安装时的非ASCII字符名称 (#3244)
* 检查安装时的非ASCII字符名称

* i18n

* 添加导出模组列表信息功能

* Revert "添加导出模组列表信息功能"

This reverts commit 6505d1c284.

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

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

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

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

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

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

---------

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>
2025-01-05 01:31:44 +08:00
ZkiteflyandGlavo d80f61e2e7 update getAssetIndex() (#3252)
* update getAssetIndex()

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-01-04 21:33:48 +08:00
Zkitefly 1f7b156423 Press ESC in search box to exit search (#3519) 2025-01-04 01:38:11 +08:00
Haowei Wen 56d20a5d08 Fix problems with window resizing in Linux 2025-01-04 01:35:36 +08:00
Haowei Wen b5cea83a8a Workaround for JDK-8344372
https://bugs.openjdk.org/browse/JDK-8344372
2025-01-03 20:15:03 +08:00
Haowei Wen 397edaec5d Press ESC in search box to exit search 2025-01-03 17:08:19 +08:00
Zkitefly 39b8de4824 fix 2025-01-03 17:08:19 +08:00
Zkitefly 5197157a97 添加搜索功能 2025-01-03 17:08:19 +08:00
Glavo d61826ba4c 优化 Release Note (#3516) 2025-01-01 05:37:30 +08:00
127 changed files with 9806 additions and 3444 deletions

No files matched your search

+25 -19
View File
@@ -1,6 +1,6 @@
name: Bug 反馈 | Bug Feedback
name: Bug 反馈 | Bug Report
description:
反馈一个 HMCL 错误。| Bug Feedback.
反馈一个 HMCL 错误。| File a bug report for HMCL.
title: "[Bug] "
labels: bug
body:
@@ -8,29 +8,35 @@ body:
attributes:
value: |
提交前请确认:
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
* 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新快照版本。
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
* 你的启动器版本是**最新的预览版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新预览版本。
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
Before submitting, please confirm:
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
* Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version.
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
* The issue is indeed **a bug of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
* Your launcher is the **latest nightly build**. You can click [here](https://zkitefly.github.io/HMCL-Snapshot-Update/en) to download the latest nightly build.
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
- type: input
id: platform
attributes:
label: 平台 | Platform
description: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug.
description: |
请输入你遇到问题的平台。
Please enter the platform on which you encountered the bug.
placeholder: e.g. Windows 11
validations:
required: true
- type: textarea
id: bug-report
attributes:
label: 问题描述 | Problem Description
description: 请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。 Please describe the problem you met in as much detail as possible. Besides, please describe how to trigger this problem again.
label: 问题描述 | Bug Description
description: |
请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。
Please describe the bug you met in as much detail as possible. Additionally, describe the steps to reproduce this bug.
placeholder: |
1. 点击 HMCL 上的某个按钮 | Click a button named ...
2. 向下翻页 | Scroll down
@@ -42,12 +48,12 @@ body:
attributes:
label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
description: |
如果您的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
如果您的启动器没有崩溃,请在遇到问题后不要退出启动器,在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。
如果你的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
如果你的启动器没有崩溃,请在遇到问题后**不要退出启动器**,在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”,并将导出的日志拖到下方的输入栏中。
**请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!**
If your launcher crashes, please fill it in (or drag the file in) below.
If your launcher doesn't crash, please DO NOT EXIT your launcher, click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below.
**Attention: The crash report or the log file is the key to solve the problem. Please update it!**
If your launcher crashes, please fill in (or drag the file in) the following input field with the crash report.
If your launcher does not crash, please DO NOT EXIT your launcher, click "Export Launcher Logs" in the "Settings → General → Debug" of the launcher, and drag the exported log to the following input field.
**ATTENTION: The crash report or log file is the key to resolving the bug. Please upload them!**
validations:
required: true
+4 -4
View File
@@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: QQ 群 | QQ Groups
- name: QQ 群 | QQ Group
url: https://docs.hmcl.net/groups.html
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher.
- name: Discord 频道 | Discord Channel
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ group of Hello Minecraft! Launcher.
- name: Discord 服务器 | Discord Server
url: https://discord.gg/jVvC7HfM6U
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
about: Hello Minecraft! Launcher 的官方 Discord 服务器。| The official Discord server of Hello Minecraft! Launcher.
- name: 其他反馈 | Others
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
+10 -4
View File
@@ -1,22 +1,28 @@
name: 新功能 | Feature Request
description: 为 HMCL 提出新功能 | A new feature for HMCL
description: 为 HMCL 提出新功能。| Suggest a new feature or enhancement for HMCL.
title: "[Feature] "
labels: enhancement
body:
- type: markdown
attributes:
value: 请确认 Issues 列表无重复的项目。| Please make sure that no duplicated issues has already been delivered.
value: |
请确认 Issues 列表无重复的项目。
Please make sure that no duplicate issues have already been submitted.
- type: textarea
id: description
attributes:
label: 描述 | Description
description: 请详细描述你想加入的新功能。| Please describe the new feature detaily.
description: |
请详细描述你想加入的新功能。
Please describe the new feature in detail.
validations:
required: true
- type: textarea
id: reason
attributes:
label: 原因 | Reason
description: 请描述该功能带来的好处及原因。| Please describe why you want to add the feature into HMCL.
description: |
请描述该功能带来的好处及原因。
Please describe why you want to add the feature or enhancement to HMCL.
validations:
required: true
+9 -8
View File
@@ -50,13 +50,14 @@ jobs:
if: ${{ env.continue == 'true' }}
run: |
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
echo "| --- | --- |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.exe]($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
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
@@ -117,13 +118,13 @@ jobs:
echo "" >> RELEASE_NOTE
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
echo "| --- | --- |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.exe]($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
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
+1
View File
@@ -20,6 +20,7 @@ minecraft-exported-crash-info*
/HMCLCore/build/
/HMCLTransformerDiscoveryService/build/
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
/buildSrc/build/
# idea
.idea
+3 -1
View File
@@ -1,2 +1,4 @@
/data.csv
/data.json
/data.json
/mod.json
/modpack.json
+14 -1
View File
@@ -1,3 +1,4 @@
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
@@ -121,7 +122,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
manifest {
attributes(
"Created-By" to "Copyright(c) 2013-2024 huangyuhui.",
"Created-By" to "Copyright(c) 2013-2025 huangyuhui.",
"Main-Class" to "org.jackhuang.hmcl.Main",
"Multi-Release" to "true",
"Implementation-Version" to project.version,
@@ -253,3 +254,15 @@ tasks.create<JavaExec>("run") {
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
}
}
// mcmod data
tasks.register<ParseModDataTask>("parseModData") {
inputFile.set(layout.projectDirectory.file("mod.json"))
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/mod_data.txt"))
}
tasks.register<ParseModDataTask>("parseModPackData") {
inputFile.set(layout.projectDirectory.file("modpack.json"))
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/modpack_data.txt"))
}
-123
View File
@@ -1,123 +0,0 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
import json
import codecs
import re
import sys
from urllib.parse import urlparse, parse_qs
S = ';'
MOD_SEPARATOR = ','
CURSEFORGE_PATTERN1 = re.compile(
r'^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN2 = re.compile(
r'^/projects/(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN3 = re.compile(
r'^/mc-mods/minecraft/(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN4 = re.compile(
r'^/legacy/mc-mods/minecraft/(\d+)-(?P<modid>[\w-]+)'
)
def parseCurseforge(url):
res = urlparse(url)
if res.scheme not in ['http', 'https']:
return ''
for pattern in [CURSEFORGE_PATTERN1, CURSEFORGE_PATTERN2, CURSEFORGE_PATTERN3, CURSEFORGE_PATTERN4]:
match = pattern.match(res.path)
if match:
return match.group('modid')
return ''
MCMOD_PATTERN = re.compile(
r'^http(s)?://www\.mcmod\.cn/(class|modpack)/(?P<modid>\d+)\.html$')
def parseMcmod(url):
match = MCMOD_PATTERN.match(url)
if match:
return match.group('modid')
return ''
skip = [
'Minecraft',
'The Building Game'
]
if __name__ == '__main__':
json_name = sys.argv[1] or 'data.json'
with codecs.open(json_name, mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile:
data = json.load(jsonfile)
for mod in data:
chinese_name = mod['name']['main']
sub_name = mod['name']['sub']
abbr = mod['name']['abbr']
if sub_name in skip:
continue
if S in chinese_name:
print('Error! ' + chinese_name)
exit(1)
if S in sub_name:
print('Error! ' + chinese_name)
exit(1)
black_lists = [
'Wood Converter'
]
curseforge_id = ''
mcmod_id = ''
links = mod['links']['list']
if 'curseforge' in links and links['curseforge'] and sub_name not in black_lists and chinese_name not in black_lists:
for link in links['curseforge']:
curseforge_id = parseCurseforge(link['url'])
if curseforge_id != '':
break
if curseforge_id == '':
print('Error curseforge ' + chinese_name)
exit(1)
if 'mcmod' in links and links['mcmod']:
mcmod_id = parseMcmod(links['mcmod'][0]['url'])
if mcmod_id == '':
print('Error mcmod ' + chinese_name)
exit(1)
mod_id = []
if 'modid' in mod and 'list' in mod['modid']:
for id in mod['modid']['list']:
if MOD_SEPARATOR in id:
print('Error mod id!' + id)
exit(1)
mod_id.append(id)
mod_ids = MOD_SEPARATOR.join([str(id) for id in mod_id])
outfile.write(
f'{curseforge_id}{S}{mcmod_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
print('Success!')
@@ -225,8 +225,11 @@ public final class Launcher extends Application {
LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')'));
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH_NAME);
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH_NAME);
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
}
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
LOG.info("Java Home: " + System.getProperty("java.home"));
@@ -245,7 +248,6 @@ public final class Launcher extends Application {
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
}
launch(Launcher.class, args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
@@ -43,8 +43,8 @@ public class CrashReport {
stackTrace + "\n\n" +
"-- System Details --\n" +
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
" System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName() + "\n" +
" Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName() + "\n" +
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
@@ -359,12 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs = createLocalVersionSetting(id);
if (vs == null)
return null;
VersionIconType versionIcon = vs.getVersionIcon();
if (vs.isUsesGlobal()) {
PropertyUtils.copyProperties(profile.getGlobal(), vs);
vs.setUsesGlobal(false);
}
vs.setVersionIcon(versionIcon); // versionIcon is preserved
return vs;
}
@@ -374,7 +370,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs.setUsesGlobal(true);
}
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
VersionSetting vs = getVersionSetting(version);
LaunchOptions.Builder builder = new LaunchOptions.Builder()
@@ -417,7 +413,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
.setUseNativeGLFW(vs.isUseNativeGLFW())
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
.setJavaAgents(javaAgents);
.setJavaAgents(javaAgents)
.setJavaArguments(javaArguments);
if (config().hasProxy()) {
builder.setProxy(ProxyManager.getProxy());
if (config().hasProxyAuth()) {
@@ -131,13 +131,14 @@ public final class LauncherHelper {
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
CountDownLatch launchingLatch = new CountDownLatch(1);
List<String> javaAgents = new ArrayList<>(0);
List<String> javaArguments = new ArrayList<>(0);
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
TaskExecutor executor = checkGameState(profile, setting, version.get())
.thenComposeAsync(java -> {
javaVersionRef.set(Objects.requireNonNull(java));
version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), java, setting));
version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));
if (setting.isNotCheckGame())
return null;
return Task.allOf(
@@ -155,7 +156,7 @@ public final class LauncherHelper {
Task.composeAsync(() -> {
Renderer renderer = setting.getRenderer();
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
Library lib = NativePatcher.getMesaLoader(java, renderer);
Library lib = NativePatcher.getMesaLoader(java);
if (lib == null)
return null;
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
@@ -182,7 +183,8 @@ public final class LauncherHelper {
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
LaunchOptions launchOptions = repository.getLaunchOptions(
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));
@@ -376,8 +376,8 @@ public final class JavaManager {
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
break;
case OSX:
tryAddJavaHome(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines/Contents/Home"));
tryAddJavaHome(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines/Contents/Home"));
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
// Homebrew
@@ -631,6 +631,20 @@ public final class JavaManager {
}
}
private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
if (!Files.isDirectory(directory)) {
return;
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
for (Path subDir : stream) {
tryAddJavaHome(javaRuntimes, subDir.resolve("Contents/Home"));
}
} catch (IOException e) {
LOG.warning("Failed to find Java in " + directory, e);
}
}
// ==== Windows Registry Support ====
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, String location) {
for (String java : querySubFolders(location)) {
@@ -24,7 +24,10 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Lazy;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
@@ -56,29 +59,41 @@ public class Theme {
Color.web("#B71C1C") // red
};
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static Optional<Font> font;
private static final Lazy<Font> FONT = new Lazy<>(() -> {
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
private static Optional<Font> tryLoadFont() {
//noinspection OptionalAssignedToNull
if (font != null) {
return font;
}
for (String fileName : fileNames) {
Path path = Paths.get(fileName).toAbsolutePath();
if (Files.isRegularFile(path)) {
try {
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
if (font != null) {
return font;
}
} catch (MalformedURLException ignored) {
}
Path path = Paths.get("font.ttf");
if (!Files.isRegularFile(path)) {
path = Paths.get("font.otf");
}
if (Files.isRegularFile(path)) {
try {
return font = Optional.ofNullable(Font.loadFont(path.toAbsolutePath().toUri().toURL().toExternalForm(), 0));
} catch (MalformedURLException ignored) {
LOG.warning("Failed to load font " + path);
}
}
return font = Optional.empty();
}
for (String fileName : fileNames) {
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
if (Files.isRegularFile(path)) {
try {
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
if (font != null) {
return font;
}
} catch (MalformedURLException ignored) {
}
LOG.warning("Failed to load font " + path);
}
}
return null;
});
public static Theme getTheme() {
Theme theme = config().getTheme();
@@ -123,6 +138,45 @@ public class Theme {
opacity);
}
private static Pair<String, String> parseFontStyle(String style) {
if (style == null) {
return null;
}
style = style.toLowerCase(Locale.ROOT);
String weight;
String posture;
if (style.contains("thin")) {
weight = "100";
} else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) {
weight = "200";
} else if (style.contains("medium")) {
weight = "500";
} else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) {
weight = "600";
} else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) {
weight = "800";
} else if (style.contains("black") || style.contains("heavy")) {
weight = "900";
} else if (style.contains("light")) {
weight = "lighter";
} else if (style.contains("bold")) {
weight = "bold";
} else {
weight = null;
}
if (style.contains("italic") || style.contains("oblique")) {
posture = "italic";
} else {
posture = null;
}
return Pair.pair(weight, posture);
}
public String[] getStylesheets(String overrideFontFamily) {
String css = "/assets/css/blue.css";
@@ -130,12 +184,12 @@ public class Theme {
? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT"))
: overrideFontFamily;
String fontStyle = null;
Pair<String, String> fontStyle = null;
if (fontFamily == null) {
Optional<Font> font = tryLoadFont();
if (font.isPresent()) {
fontFamily = font.get().getFamily();
fontStyle = font.get().getStyle();
Font font = FONT.get();
if (font != null) {
fontFamily = font.getFamily();
fontStyle = parseFontStyle(font.getStyle());
}
}
@@ -159,8 +213,13 @@ public class Theme {
else
themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";");
if (fontStyle != null && !fontStyle.isEmpty())
themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";");
if (fontStyle != null) {
if (fontStyle.getKey() != null)
themeBuilder.append("-fx-font-weight:").append(fontStyle.getKey()).append(";");
if (fontStyle.getValue() != null)
themeBuilder.append("-fx-font-style:").append(fontStyle.getValue()).append(";");
}
themeBuilder.append('}');
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.DoubleProperty;
@@ -34,6 +37,7 @@ import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.ModpackHelper;
@@ -42,6 +46,7 @@ import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.account.AccountListPage;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
@@ -267,6 +272,24 @@ public final class Controllers {
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
if (AnimationUtils.playWindowAnimation()) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE)
),
new KeyFrame(Duration.millis(600),
new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE)
)
);
timeline.play();
}
if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
@@ -368,10 +391,7 @@ public final class Controllers {
public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, TaskCancellationAction onCancel) {
TaskExecutor executor = task.executor();
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
pane.setTitle(title);
pane.setExecutor(executor);
dialog(pane);
TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel);
executor.start();
return pane;
}
@@ -24,10 +24,13 @@ import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.WeakListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.value.*;
import javafx.geometry.Insets;
import javafx.event.Event;
import javafx.event.EventDispatcher;
import javafx.event.EventType;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
@@ -76,6 +79,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.lang.ref.WeakReference;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -86,6 +90,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -234,20 +239,21 @@ public final class FXUtils {
});
}
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
column.setCellValueFactory(param -> {
if (column.validateValue(param))
return mapper.apply(param.getValue().getValue());
else
return column.getComputedValue(param);
@SuppressWarnings("unchecked")
public static <T extends Event> void ignoreEvent(Node node, EventType<T> type, Predicate<? super T> filter) {
EventDispatcher oldDispatcher = node.getEventDispatcher();
node.setEventDispatcher((event, tail) -> {
EventType<?> t = event.getEventType();
while (t != null && t != type)
t = t.getSuperType();
if (t == type && filter.test((T) event)) {
return tail.dispatchEvent(event);
} else {
return oldDispatcher.dispatchEvent(event, tail);
}
});
}
public static Node wrapMargin(Node node, Insets insets) {
StackPane.setMargin(node, insets);
return new StackPane(node);
}
public static void setValidateWhileTextChanged(Node field, boolean validate) {
if (field instanceof JFXTextField) {
if (validate) {
@@ -563,7 +569,7 @@ public final class FXUtils {
focusedListener = (observable, oldFocused, newFocused) -> {
if (oldFocused && !newFocused) {
if (textField.validate()) {
uppdateProperty();
updateProperty();
} else {
// Rollback to old value
updateTextField();
@@ -575,7 +581,7 @@ public final class FXUtils {
if (oldScene != null && newScene == null) {
// Component is being removed from scene
if (textField.validate()) {
uppdateProperty();
updateProperty();
}
}
};
@@ -585,7 +591,7 @@ public final class FXUtils {
};
}
public void uppdateProperty() {
public void updateProperty() {
String newText = textField.getText();
@SuppressWarnings("unchecked")
T newValue = converter == null ? (T) newText : converter.fromString(newText);
@@ -601,20 +607,80 @@ public final class FXUtils {
}
}
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().bindBidirectional(property);
}
private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {
private final WeakReference<JFXComboBox<E>> comboBoxRef;
private final WeakReference<Property<E>> propertyRef;
private final int hashCode;
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().unbindBidirectional(property);
}
private boolean updating = false;
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().bindBidirectional(property);
}
private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {
this.comboBoxRef = new WeakReference<>(comboBox);
this.propertyRef = new WeakReference<>(property);
this.hashCode = System.identityHashCode(comboBox) ^ System.identityHashCode(property);
}
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().unbindBidirectional(property);
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final JFXComboBox<E> comboBox = comboBoxRef.get();
final Property<E> property = propertyRef.get();
if (comboBox == null || property == null) {
if (comboBox != null) {
comboBox.getSelectionModel().selectedItemProperty().removeListener(this);
}
if (property != null) {
property.removeListener(this);
}
} else {
updating = true;
try {
if (property == sourceProperty) {
E newValue = property.getValue();
comboBox.getSelectionModel().select(newValue);
} else {
E newValue = comboBox.getSelectionModel().getSelectedItem();
property.setValue(newValue);
}
} finally {
updating = false;
}
}
}
}
@Override
public boolean wasGarbageCollected() {
return comboBoxRef.get() == null || propertyRef.get() == null;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof EnumBidirectionalBinding))
return false;
EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;
final JFXComboBox<E> comboBox = this.comboBoxRef.get();
final Property<E> property = this.propertyRef.get();
final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();
final Property<?> thatProperty = that.propertyRef.get();
if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)
return false;
return comboBox == thatComboBox && property == thatProperty;
}
}
/**
@@ -623,20 +689,18 @@ public final class FXUtils {
*
* @param comboBox the combo box being bound with {@code property}.
* @param property the property being bound with {@code combo box}.
* @see #unbindEnum(JFXComboBox)
* @see #unbindEnum(JFXComboBox, Property)
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
*/
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
unbindEnum(comboBox);
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
T currentValue = property.getValue();
@SuppressWarnings("unchecked")
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
property.removeListener(binding);
comboBox.getSelectionModel().select(currentValue.ordinal());
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
comboBox.getSelectionModel().select(property.getValue());
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
property.addListener(binding);
}
/**
@@ -647,11 +711,10 @@ public final class FXUtils {
* @see #bindEnum(JFXComboBox, Property)
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
*/
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
@SuppressWarnings("unchecked")
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
if (listener != null)
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
property.removeListener(binding);
}
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
@@ -991,6 +1054,8 @@ public final class FXUtils {
}
};
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
public static void onEscPressed(Node node, Runnable action) {
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
@@ -1009,6 +1074,18 @@ public final class FXUtils {
});
}
public static void copyOnDoubleClick(Labeled label) {
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
String text = label.getText();
if (text != null && !text.isEmpty()) {
copyText(label.getText());
e.consume();
}
}
});
}
public static void copyText(String text) {
ClipboardContent content = new ClipboardContent();
content.putString(text);
@@ -378,7 +378,7 @@ public class InstallerItem extends Control {
pane.getChildren().add(buttonsContainer);
JFXButton removeButton = new JFXButton();
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1));
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1));
removeButton.getStyleClass().add("toggle-icon4");
if (control.id.equals(MINECRAFT.getPatchId())) {
removeButton.setVisible(false);
@@ -396,8 +396,8 @@ public class InstallerItem extends Control {
JFXButton installButton = new JFXButton();
installButton.graphicProperty().bind(Bindings.createObjectBinding(() ->
control.resolvedStateProperty.get() instanceof InstallableState ?
SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), -1, -1) :
SVG.UPDATE.createIcon(Theme.blackFill(), -1, -1),
SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), -1) :
SVG.UPDATE.createIcon(Theme.blackFill(), -1),
control.resolvedStateProperty
));
installButton.getStyleClass().add("toggle-icon4");
@@ -69,7 +69,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
FXUtils.setLimitHeight(btnAdd, 40);
btnAdd.getStyleClass().add("jfx-button-raised-round");
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
btnAdd.setGraphic(SVG.PLUS.createIcon(Theme.whiteFill(), -1, -1));
btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1));
btnAdd.setOnAction(e -> skinnable.add());
JFXButton btnRefresh = new JFXButton();
@@ -77,7 +77,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
FXUtils.setLimitHeight(btnRefresh, 40);
btnRefresh.getStyleClass().add("jfx-button-raised-round");
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1, -1));
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1));
btnRefresh.setOnAction(e -> skinnable.refresh());
vBox.getChildren().setAll(btnAdd);
@@ -25,94 +25,88 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.SVGPath;
/**
* All vector icons used in the launcher.
* <p>
* Unless otherwise stated,
* these icons are from <a href="https://fonts.google.com/icons?icon.size=24&icon.color=%235f6368&icon.query=list&icon.set=Material+Symbols">Material Symbols</a>,
* with a style of outlined, a weight of 400, a grade of 0, and an optical size of 24 px.
* The view boxes of all icons are normalized to {@code 0 0 24 24}.
*/
public enum SVG {
GEAR("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"),
GEAR_OUTLINE("M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"),
BACK("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"),
CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"),
CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"),
CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"),
COPY("M15 16H7V6h8m.4-2H6.6A1.6 1.6 90 005 5.6V16.4a1.6 1.6 90 001.6 1.6h8.8A1.6 1.6 90 0017 16.4V5.6A1.6 1.6 90 0015.4 4M13 1H3.6A1.6 1.6 90 002 2.6V13h2V3H13V1Z"),
DOTS_VERTICAL("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"),
DOTS_HORIZONTAL("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"),
DELETE("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"),
DELETE_OUTLINE("M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8,9H16V19H8V9M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z"),
ACCOUNT_EDIT("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z"),
EXPAND("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"),
COLLAPSE("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"),
NAVIGATE("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
ROCKET_LAUNCH_OUTLINE("M13.13 22.19L11.5 18.36C13.07 17.78 14.54 17 15.9 16.09L13.13 22.19M5.64 12.5L1.81 10.87L7.91 8.1C7 9.46 6.22 10.93 5.64 12.5M19.22 4C19.5 4 19.75 4 19.96 4.05C20.13 5.44 19.94 8.3 16.66 11.58C14.96 13.29 12.93 14.6 10.65 15.47L8.5 13.37C9.42 11.06 10.73 9.03 12.42 7.34C15.18 4.58 17.64 4 19.22 4M19.22 2C17.24 2 14.24 2.69 11 5.93C8.81 8.12 7.5 10.53 6.65 12.64C6.37 13.39 6.56 14.21 7.11 14.77L9.24 16.89C9.62 17.27 10.13 17.5 10.66 17.5C10.89 17.5 11.13 17.44 11.36 17.35C13.5 16.53 15.88 15.19 18.07 13C23.73 7.34 21.61 2.39 21.61 2.39S20.7 2 19.22 2M14.54 9.46C13.76 8.68 13.76 7.41 14.54 6.63S16.59 5.85 17.37 6.63C18.14 7.41 18.15 8.68 17.37 9.46C16.59 10.24 15.32 10.24 14.54 9.46M8.88 16.53L7.47 15.12L8.88 16.53M6.24 22L9.88 18.36C9.54 18.27 9.21 18.12 8.91 17.91L4.83 22H6.24M2 22H3.41L8.18 17.24L6.76 15.83L2 20.59V22M2 19.17L6.09 15.09C5.88 14.79 5.73 14.47 5.64 14.12L2 17.76V19.17Z"),
LAUNCH_OUTLINE("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
SCRIPT("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z"),
PENCIL("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z"),
PENCIL_OUTLINE("M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z"),
REFRESH("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"),
FOLDER_OPEN("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z"),
FOLDER_OUTLINE("M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z"),
UPDATE("M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z"),
CLOSE_CIRCLE("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z"),
CHECK_CIRCLE("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"),
INFO_CIRCLE("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
HELP_CIRCLE("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"),
HELP_CIRCLE_OUTLINE("M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z"),
ALERT("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z"),
ALERT_OUTLINE("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"),
PLUS("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"),
PLUS_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"),
MINUS("M19 13H5V11H19V13Z"),
IMPORT_ICON("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z"),
EXPORT("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z"),
OPEN_IN_NEW("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
TRIANGLE("M1,21H23L12,2"),
HOME("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"),
VIEW_LIST("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z"),
CHECK("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"),
ARROW_RIGHT("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"),
WRENCH("M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z"),
WRENCH_OUTLINE("M22.61,19L13.53,9.91C14.46,7.57 14,4.81 12.09,2.91C9.79,0.61 6.21,0.4 3.66,2.26L7.5,6.11L6.08,7.5L2.25,3.69C0.39,6.23 0.6,9.82 2.9,12.11C4.76,13.97 7.47,14.46 9.79,13.59L18.9,22.7C19.29,23.09 19.92,23.09 20.31,22.7L22.61,20.4C23,20 23,19.39 22.61,19M19.61,20.59L10.15,11.13C9.54,11.58 8.86,11.85 8.15,11.95C6.79,12.15 5.36,11.74 4.32,10.7C3.37,9.76 2.93,8.5 3,7.26L6.09,10.35L10.33,6.11L7.24,3C8.5,2.95 9.73,3.39 10.68,4.33C11.76,5.41 12.17,6.9 11.92,8.29C11.8,9 11.5,9.66 11.04,10.25L20.5,19.7L19.61,20.59Z"),
UPLOAD("M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z"),
HANGER("M12 4A3.5 3.5 0 0 0 8.5 7.5H10.5A1.5 1.5 0 0 1 12 6A1.5 1.5 0 0 1 13.5 7.5A1.5 1.5 0 0 1 12 9C11.45 9 11 9.45 11 10V11.75L2.4 18.2A1 1 0 0 0 3 20H21A1 1 0 0 0 21.6 18.2L13 11.75V10.85A3.5 3.5 0 0 0 15.5 7.5A3.5 3.5 0 0 0 12 4M12 13.5L18 18H6Z"),
PUZZLE("M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z"),
CUBE("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"),
PACK("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"),
TEXTURE_BOX("M20 2H4C2.9 2 2 2.9 2 4V20C2 21.11 2.9 22 4 22H20C21.11 22 22 21.11 22 20V4C22 2.9 21.11 2 20 2M4 6L6 4H10.9L4 10.9V6M4 13.7L13.7 4H18.6L4 18.6V13.7M20 18L18 20H13.1L20 13.1V18M20 10.3L10.3 20H5.4L20 5.4V10.3Z"),
GAMEPAD("M6,9H8V11H10V13H8V15H6V13H4V11H6V9M18.5,9A1.5,1.5 0 0,1 20,10.5A1.5,1.5 0 0,1 18.5,12A1.5,1.5 0 0,1 17,10.5A1.5,1.5 0 0,1 18.5,9M15.5,12A1.5,1.5 0 0,1 17,13.5A1.5,1.5 0 0,1 15.5,15A1.5,1.5 0 0,1 14,13.5A1.5,1.5 0 0,1 15.5,12M17,5A7,7 0 0,1 24,12A7,7 0 0,1 17,19C15.04,19 13.27,18.2 12,16.9C10.73,18.2 8.96,19 7,19A7,7 0 0,1 0,12A7,7 0 0,1 7,5H17M7,7A5,5 0 0,0 2,12A5,5 0 0,0 7,17C8.64,17 10.09,16.21 11,15H13C13.91,16.21 15.36,17 17,17A5,5 0 0,0 22,12A5,5 0 0,0 17,7H7Z"),
FIRE("M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08 13.88 8.04 13.83 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2M14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5H14.5Z"),
MONITOR_SCREENSHOT("M9,6H5V10H7V8H9M19,10H17V12H15V14H19M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2"),
TEXTURE("M9.29,21H12.12L21,12.12V9.29M19,21C19.55,21 20.05,20.78 20.41,20.41C20.78,20.05 21,19.55 21,19V17L17,21M5,3A2,2 0 0,0 3,5V7L7,3M11.88,3L3,11.88V14.71L14.71,3M19.5,3.08L3.08,19.5C3.17,19.85 3.35,20.16 3.59,20.41C3.84,20.65 4.15,20.83 4.5,20.92L20.93,4.5C20.74,3.8 20.2,3.26 19.5,3.08Z"),
ALPHA_CIRCLE_OUTLINE("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z"),
BETA_CIRCLE_OUTLINE("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"),
RELEASE_CIRCLE_OUTLINE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"),
INFORMATION_OUTLINE("M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"),
HAND_HEAR_OUTLINE("M16 3.23C16.71 2.41 17.61 2 18.7 2C19.61 2 20.37 2.33 21 3C21.63 3.67 21.96 4.43 22 5.3C22 6 21.67 6.81 21 7.76S19.68 9.5 19.03 10.15C18.38 10.79 17.37 11.74 16 13C14.61 11.74 13.59 10.79 12.94 10.15S11.63 8.71 10.97 7.76C10.31 6.81 10 6 10 5.3C10 4.39 10.32 3.63 10.97 3C11.62 2.37 12.4 2.04 13.31 2C14.38 2 15.27 2.41 16 3.23M22 19V20L14 22.5L7 20.56V22H1V11H8.97L15.13 13.3C16.25 13.72 17 14.8 17 16H19C20.66 16 22 17.34 22 19M5 20V13H3V20H5M19.9 18.57C19.74 18.24 19.39 18 19 18H13.65C13.11 18 12.58 17.92 12.07 17.75L9.69 16.96L10.32 15.06L12.7 15.85C13 15.95 15 16 15 16C15 15.63 14.77 15.3 14.43 15.17L8.61 13H7V18.5L13.97 20.41L19.9 18.57Z"),
MOJANG("m 13.965847,0 c -1.010612,0.82802228 -1.197232,2.1950303 -1.265172,3.4179557 0.02123,1.0021189 1.341654,1.2994159 1.953117,0.590289 C 15.091158,2.6579315 14.369242,1.2738804 13.965847,0 Z M 10.913012,2.9296764 C 10.755901,3.6982508 10.628413,4.4668999 10.492533,5.2354744 8.9893533,3.9913178 7.1504705,3.0824837 5.1419856,3.2947971 3.4944341,3.4646478 0.94293227,2.6961479 0.14038761,4.645185 -0.12288102,8.3139606 0.07223999,12.01236 0.03402357,15.689629 c -0.12314178,1.222925 0.86170213,2.420422 2.14407513,2.280295 4.2207899,0.03397 8.4502143,0.04723 12.6710043,-0.0038 1.265389,0.135918 1.957646,-1.010748 2.13599,-2.0893 C 13.269608,16.437357 9.1760813,16.929609 5.7111265,15.129192 2.5986124,13.58355 2.246023,8.3138817 5.5581114,6.7979639 9.3203049,5.1758896 13.859607,8.0382886 14.942405,11.787743 15.613316,12.11046 16.284433,12.424684 16.95959,12.743154 16.624135,10.348258 16.653651,7.800456 15.579346,5.5881508 15.057054,4.7473897 14.097531,6.2714182 13.379911,5.6217388 12.416008,4.865903 11.749527,3.8128948 10.913012,2.9296713 Z"),
MICROSOFT("M2,3H11V12H2V3M11,22H2V13H11V22M21,3V12H12V3H21M21,22H12V13H21V22Z"),
ACCOUNT_OUTLINE("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z"),
ACCOUNT_GROUP_OUTLINE("M12,5A3.5,3.5 0 0,0 8.5,8.5A3.5,3.5 0 0,0 12,12A3.5,3.5 0 0,0 15.5,8.5A3.5,3.5 0 0,0 12,5M12,7A1.5,1.5 0 0,1 13.5,8.5A1.5,1.5 0 0,1 12,10A1.5,1.5 0 0,1 10.5,8.5A1.5,1.5 0 0,1 12,7M5.5,8A2.5,2.5 0 0,0 3,10.5C3,11.44 3.53,12.25 4.29,12.68C4.65,12.88 5.06,13 5.5,13C5.94,13 6.35,12.88 6.71,12.68C7.08,12.47 7.39,12.17 7.62,11.81C6.89,10.86 6.5,9.7 6.5,8.5C6.5,8.41 6.5,8.31 6.5,8.22C6.2,8.08 5.86,8 5.5,8M18.5,8C18.14,8 17.8,8.08 17.5,8.22C17.5,8.31 17.5,8.41 17.5,8.5C17.5,9.7 17.11,10.86 16.38,11.81C16.5,12 16.63,12.15 16.78,12.3C16.94,12.45 17.1,12.58 17.29,12.68C17.65,12.88 18.06,13 18.5,13C18.94,13 19.35,12.88 19.71,12.68C20.47,12.25 21,11.44 21,10.5A2.5,2.5 0 0,0 18.5,8M12,14C9.66,14 5,15.17 5,17.5V19H19V17.5C19,15.17 14.34,14 12,14M4.71,14.55C2.78,14.78 0,15.76 0,17.5V19H3V17.07C3,16.06 3.69,15.22 4.71,14.55M19.29,14.55C20.31,15.22 21,16.06 21,17.07V19H24V17.5C24,15.76 21.22,14.78 19.29,14.55M12,16C13.53,16 15.24,16.5 16.23,17H7.77C8.76,16.5 10.47,16 12,16Z"),
ACCOUNT_ARROW_RIGHT_OUTLINE("M19,21V19H15V17H19V15L22,18L19,21M13,18C13,18.71 13.15,19.39 13.42,20H2V17C2,14.79 5.58,13 10,13C11,13 11.96,13.09 12.85,13.26C13.68,13.42 14.44,13.64 15.11,13.92C13.83,14.83 13,16.32 13,18M4,17V18H11C11,16.96 11.23,15.97 11.64,15.08L10,15C6.69,15 4,15.9 4,17M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,6A2,2 0 0,0 8,8A2,2 0 0,0 10,10A2,2 0 0,0 12,8A2,2 0 0,0 10,6Z"),
STYLE_OUTLINE("M2.5 19.6L3.8 20.2V11.2L1.4 17C1 18.1 1.5 19.2 2.5 19.6M15.2 4.8L20.2 16.8L12.9 19.8L7.9 7.9V7.8L15.2 4.8M15.3 2.8C15 2.8 14.8 2.8 14.5 2.9L7.1 6C6.4 6.3 5.9 7 5.9 7.8C5.9 8 5.9 8.3 6 8.6L11 20.5C11.3 21.3 12 21.7 12.8 21.7C13.1 21.7 13.3 21.7 13.6 21.6L21 18.5C22 18.1 22.5 16.9 22.1 15.9L17.1 4C16.8 3.2 16 2.8 15.3 2.8M10.5 9.9C9.9 9.9 9.5 9.5 9.5 8.9S9.9 7.9 10.5 7.9C11.1 7.9 11.5 8.4 11.5 8.9S11.1 9.9 10.5 9.9M5.9 19.8C5.9 20.9 6.8 21.8 7.9 21.8H9.3L5.9 13.5V19.8Z"),
DOWNLOAD_OUTLINE("M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z"),
APPLICATION_OUTLINE("M21 2H3C1.9 2 1 2.9 1 4V20C1 21.1 1.9 22 3 22H21C22.1 22 23 21.1 23 20V4C23 2.9 22.1 2 21 2M21 20H3V6H21V20Z"),
EARTH("M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
BELL("M21,19V20H3V19L5,17V11C5,7.9 7.03,5.17 10,4.29C10,4.19 10,4.1 10,4A2,2 0 0,1 12,2A2,2 0 0,1 14,4C14,4.1 14,4.19 14,4.29C16.97,5.17 19,7.9 19,11V17L21,19M14,21A2,2 0 0,1 12,23A2,2 0 0,1 10,21"),
CONTENT_SAVE_MOVE_OUTLINE("M13 17H17V14L22 18.5L17 23V20H13V17M14 12.8C13.5 12.31 12.78 12 12 12C10.34 12 9 13.34 9 15C9 16.31 9.84 17.41 11 17.82C11.07 15.67 12.27 13.8 14 12.8M11.09 19H5V5H16.17L19 7.83V12.35C19.75 12.61 20.42 13 21 13.54V7L17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H11.81C11.46 20.39 11.21 19.72 11.09 19M6 10H15V6H6V10Z"),
ACCOUNT("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"),
SERVER("M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z"),
MESSAGE_ALERT_OUTLINE("M13,10H11V6H13V10M13,12H11V14H13V12M22,4V16A2,2 0 0,1 20,18H6L2,22V4A2,2 0 0,1 4,2H20A2,2 0 0,1 22,4M20,4H4V17.2L5.2,16H20V4Z"),
CHECK_CIRCLE_OUTLINE("M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z"),
CLOSE_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z"),
CLOCK_OUTLINE("M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z"),
MAGNIFY("M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"),
MENU_UP("M7,15L12,10L17,15H7Z"),
MENU_DOWN("M7,10L12,15L17,10H7Z"),
RESTORE("M13,3A9,9 0 0,0 4,12H1L4.89,15.89L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3Z"),
BUG("M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z"),
DISCORD("M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z"),
LAN("M10,2C8.89,2 8,2.89 8,4V7C8,8.11 8.89,9 10,9H11V11H2V13H6V15H5C3.89,15 3,15.89 3,17V20C3,21.11 3.89,22 5,22H9C10.11,22 11,21.11 11,20V17C11,15.89 10.11,15 9,15H8V13H16V15H15C13.89,15 13,15.89 13,17V20C13,21.11 13.89,22 15,22H19C20.11,22 21,21.11 21,20V17C21,15.89 20.11,15 19,15H18V13H22V11H13V9H14C15.11,9 16,8.11 16,7V4C16,2.89 15.11,2 14,2H10M10,4H14V7H10V4M5,17H9V20H5V17M15,17H19V20H15V17Z"),
THUMB_UP_OUTLINE("M5,9V21H1V9H5M9,21A2,2 0 0,1 7,19V9C7,8.45 7.22,7.95 7.59,7.59L14.17,1L15.23,2.06C15.5,2.33 15.67,2.7 15.67,3.11L15.64,3.43L14.69,8H21C22.11,8 23,8.9 23,10V12C23,12.26 22.95,12.5 22.86,12.73L19.84,19.78C19.54,20.5 18.83,21 18,21H9M9,19H18.03L21,12V10H12.21L13.34,4.68L9,9.03V19Z"),
THUMB_DOWN_OUTLINE("M19,15V3H23V15H19M15,3A2,2 0 0,1 17,5V15C17,15.55 16.78,16.05 16.41,16.41L9.83,23L8.77,21.94C8.5,21.67 8.33,21.3 8.33,20.88L8.36,20.57L9.31,16H3C1.89,16 1,15.1 1,14V12C1,11.74 1.05,11.5 1.14,11.27L4.16,4.22C4.46,3.5 5.17,3 6,3H15M15,5H5.97L3,12V14H11.78L10.65,19.32L15,14.97V5Z"),
SELECT_ALL("M9,9H15V15H9M7,17H17V7H7M15,5H17V3H15M15,21H17V19H15M19,17H21V15H19M19,9H21V7H19M19,21A2,2 0 0,0 21,19H19M19,13H21V11H19M11,21H13V19H11M9,3H7V5H9M3,17H5V15H3M5,21V19H3A2,2 0 0,0 5,21M19,3V5H21A2,2 0 0,0 19,3M13,3H11V5H13M3,9H5V7H3M7,21H9V19H7M3,13H5V11H3M3,5H5V3A2,2 0 0,0 3,5Z"),
JAVA("m 12.400746,23.498132 c 0,0 -1.047682,0.609185 0.745753,0.816159 2.173248,0.247515 3.284943,0.212301 5.680103,-0.241112 0,0 0.629462,0.394742 1.509642,0.737207 -5.369636,2.301245 -12.1518319,-0.133351 -7.934431,-1.312254 z m -0.656135,-3.003246 c 0,0 -1.175708,0.870569 0.619861,1.056206 2.321545,0.238977 4.155521,0.25925 7.32844,-0.35207 0,0 0.43849,0.444887 1.128765,0.688135 -6.492002,1.897965 -13.7233551,0.149351 -9.077066,-1.392271 z m 5.531806,-5.094318 c 1.322937,1.523496 -0.347807,2.894427 -0.347807,2.894427 0,0 3.359626,-1.733668 1.816908,-3.905821 -1.441364,-2.024926 -2.545591,-3.030987 3.43644,-6.5004569 0,0 -9.389665,2.3449859 -4.905541,7.5129179 z m 7.101193,10.318794 c 0,0 0.775626,0.639057 -0.854578,1.133019 C 20.42373,27.79123 10.623314,28.075018 7.9006204,26.88973 6.9222859,26.464048 8.75733,25.873001 9.3345153,25.749244 9.9362392,25.619093 10.280844,25.642568 10.280844,25.642568 9.1926195,24.875486 3.2457961,27.147927 7.2604887,27.798718 18.208875,29.573994 27.21766,26.999632 24.37761,25.718316 Z M 12.904316,17.383886 c 0,0 -4.9855587,1.184229 -1.765696,1.614178 1.359213,0.182426 4.069103,0.140825 6.594422,-0.07042 2.063359,-0.173899 4.135252,-0.544104 4.135252,-0.544104 0,0 -0.727616,0.311527 -1.253592,0.671064 -5.062375,1.331456 -14.8414526,0.711604 -12.02594,-0.649727 2.38129,-1.151156 4.316621,-1.019929 4.316621,-1.019929 z m 8.943706,4.998298 c 5.146658,-2.673583 2.766435,-5.24368 1.106361,-4.898013 -0.407551,0.08428 -0.58892,0.1579 -0.58892,0.1579 0,0 0.151501,-0.236845 0.439557,-0.339265 3.284942,-1.155424 5.812393,3.406524 -1.060485,5.213807 0,0 0.08003,-0.07147 0.103478,-0.134425 z M 18.744451,2.2855 c 0,0 2.849653,2.8506848 -2.703489,7.2344656 -4.453183,3.5164124 -1.015676,5.5221324 -0.0021,7.8127104 C 13.439893,14.987688 11.532301,12.92329 12.811497,11.001852 14.690284,8.1810394 19.893487,6.8143757 18.743384,2.2855 Z M 13.41002,29.628384 c 4.939684,0.315794 12.525242,-0.174974 12.70448,-2.512485 0,0 -0.345672,0.886571 -4.081906,1.589641 -4.216334,0.793754 -9.416337,0.700936 -12.4996379,0.192025 0,0 0.6315969,0.522768 3.8781309,0.730807 z");
ADD("M11 13H5V11H11V5H13V11H19V13H13V19H11V13Z"),
ADD_CIRCLE("M11 17H13V13H17V11H13V7H11V11H7V13H11V17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
ALPHA_CIRCLE("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z"), // Not Material
ARCHIVE("M12 18 16 14 14.6 12.6 13 14.2V10H11V14.2L9.4 12.6 8 14 12 18ZM5 8V19H19V8H5ZM5 21Q4.175 21 3.5875 20.4125T3 19V6.525Q3 6.175 3.1125 5.85T3.45 5.25L4.7 3.725Q4.975 3.375 5.3875 3.1875T6.25 3H17.75Q18.2 3 18.6125 3.1875T19.3 3.725L20.55 5.25Q20.775 5.525 20.8875 5.85T21 6.525V19Q21 19.825 20.4125 20.4125T19 21H5ZM5.4 6H18.6L17.75 5H6.25L5.4 6ZM12 13.5Z"),
ARROW_BACK("M7.825 13 13.425 18.6 12 20 4 12 12 4 13.425 5.4 7.825 11H20V13H7.825Z"),
ARROW_DROP_DOWN("M12 15 7 10H17L12 15Z"),
ARROW_DROP_UP("M7 14 12 9 17 14H7Z"),
ARROW_FORWARD("M16.175 13H4V11H16.175L10.575 5.4 12 4 20 12 12 20 10.575 18.6 16.175 13Z"),
BETA_CIRCLE("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
CANCEL("M8.4 17 12 13.4 15.6 17 17 15.6 13.4 12 17 8.4 15.6 7 12 10.6 8.4 7 7 8.4 10.6 12 7 15.6 8.4 17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
CHAT("M6 14H14V12H6V14ZM6 11H18V9H6V11ZM6 8H18V6H6V8ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
CHECK("M9.55 18 3.85 12.3 5.275 10.875 9.55 15.15 18.725 5.975 20.15 7.4 9.55 18Z"),
CHECKROOM("M3 20Q2.575 20 2.2875 19.7125T2 19Q2 18.75 2.1 18.5375T2.4 18.2L11 11.75V10Q11 9.575 11.3 9.2875T12.025 9Q12.65 9 13.075 8.55T13.5 7.475Q13.5 6.85 13.0625 6.425T12 6Q11.375 6 10.9375 6.4375T10.5 7.5H8.5Q8.5 6.05 9.525 5.025T12 4Q13.45 4 14.475 5.0125T15.5 7.475Q15.5 8.65 14.8125 9.575T13 10.85V11.75L21.6 18.2Q21.8 18.325 21.9 18.5375T22 19Q22 19.425 21.7125 19.7125T21 20H3ZM6 18H18L12 13.5 6 18Z"),
CHECK_CIRCLE("M10.6 16.6 17.65 9.55 16.25 8.15 10.6 13.8 7.75 10.95 6.35 12.35 10.6 16.6ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
CLOSE("M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z"),
CONTENT_COPY("M9 18Q8.175 18 7.5875 17.4125T7 16V4Q7 3.175 7.5875 2.5875T9 2H18Q18.825 2 19.4125 2.5875T20 4V16Q20 16.825 19.4125 17.4125T18 18H9ZM9 16H18V4H9V16ZM5 22Q4.175 22 3.5875 21.4125T3 20V6H5V20H16V22H5ZM9 16V4 16Z"),
DELETE("M7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17ZM7 6V19 6Z"),
DELETE_FOREVER("M9.4 16.5 12 13.9 14.6 16.5 16 15.1 13.4 12.5 16 9.9 14.6 8.5 12 11.1 9.4 8.5 8 9.9 10.6 12.5 8 15.1 9.4 16.5ZM7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM7 6V19 6Z"),
DEPLOYED_CODE("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM12 10.85 17.925 7.425 12 4 6.075 7.425 12 10.85ZM4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725L4 17.7ZM12 12Z"),
DOWNLOAD("M12 16 7 11 8.4 9.55 11 12.15V4H13V12.15L15.6 9.55 17 11 12 16ZM6 20Q5.175 20 4.5875 19.4125T4 18V15H6V18H18V15H20V18Q20 18.825 19.4125 19.4125T18 20H6Z"),
DRESSER("M4 21V5Q4 4.175 4.5875 3.5875T6 3H18Q18.825 3 19.4125 3.5875T20 5V21H18V19H6V21H4ZM6 11H11V5H6V11ZM13 7H18V5H13V7ZM13 11H18V9H13V11ZM10 16H14V14H10V16ZM6 13V17H18V13H6ZM6 13V17 13Z"),
EDIT("M5 19H6.425L16.2 9.225 14.775 7.8 5 17.575V19ZM3 21V16.75L16.2 3.575Q16.5 3.3 16.8625 3.15T17.625 3Q18.025 3 18.4 3.15T19.05 3.6L20.425 5Q20.725 5.275 20.8625 5.65T21 6.4Q21 6.8 20.8625 7.1625T20.425 7.825L7.25 21H3ZM19 6.4 17.6 5 19 6.4ZM15.475 8.525 14.775 7.8 16.2 9.225 15.475 8.525Z"),
ERROR("M12 17Q12.425 17 12.7125 16.7125T13 16Q13 15.575 12.7125 15.2875T12 15Q11.575 15 11.2875 15.2875T11 16Q11 16.425 11.2875 16.7125T12 17ZM11 13H13V7H11V13ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
EXTENSION("M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z"),
FEEDBACK("M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
FOLDER("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8V18Q22 18.825 21.4125 19.4125T20 20H4ZM4 18H20V8H11.175L9.175 6H4V18ZM4 18V6 18Z"),
FOLDER_COPY("M3 21Q2.175 21 1.5875 20.4125T1 19V6H3V19H20V21H3ZM7 17Q6.175 17 5.5875 16.4125T5 15V4Q5 3.175 5.5875 2.5875T7 2H12L14 4H21Q21.825 4 22.4125 4.5875T23 6V15Q23 15.825 22.4125 16.4125T21 17H7ZM7 15H21V6H13.175L11.175 4H7V15ZM7 15V4 15Z"),
FOLDER_OPEN("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8H11.175L9.175 6H4V18L6.4 10H23.5L20.925 18.575Q20.725 19.225 20.1875 19.6125T19 20H4ZM6.1 18H19L20.8 12H7.9L6.1 18ZM6.1 18 7.9 12 6.1 18ZM4 8V6 8Z"),
FORMAT_LIST_BULLETED("M9 19V17H21V19H9ZM9 13V11H21V13H9ZM9 7V5H21V7H9ZM5 20Q4.175 20 3.5875 19.4125T3 18Q3 17.175 3.5875 16.5875T5 16Q5.825 16 6.4125 16.5875T7 18Q7 18.825 6.4125 19.4125T5 20ZM5 14Q4.175 14 3.5875 13.4125T3 12Q3 11.175 3.5875 10.5875T5 10Q5.825 10 6.4125 10.5875T7 12Q7 12.825 6.4125 13.4125T5 14ZM5 8Q4.175 8 3.5875 7.4125T3 6Q3 5.175 3.5875 4.5875T5 4Q5.825 4 6.4125 4.5875T7 6Q7 6.825 6.4125 7.4125T5 8Z"),
FOR_YOU("M12 12Q14.025 12 16.225 11.5875T20 10.5V20.5Q18.5 21.175 16.35 21.5875T12 22Q9.8 22 7.65 21.5875T4 20.5V10.5Q5.575 11.175 7.775 11.5875T12 12ZM18 19V13.25Q16.75 13.6 15.1125 13.8T12 14Q10.525 14 8.8875 13.8T6 13.25V19Q7.25 19.45 8.875 19.725T12 20Q13.5 20 15.125 19.725T18 19ZM12 2Q13.65 2 14.825 3.175T16 6Q16 7.65 14.825 8.825T12 10Q10.35 10 9.175 8.825T8 6Q8 4.35 9.175 3.175T12 2ZM12 8Q12.825 8 13.4125 7.4125T14 6Q14 5.175 13.4125 4.5875T12 4Q11.175 4 10.5875 4.5875T10 6Q10 6.825 10.5875 7.4125T12 8ZM12 6ZM12 16.625Z"),
GAMEPAD("M12 7.65ZM16.35 12ZM7.65 12ZM12 16.35ZM12 10.5 9 7.5V2H15V7.5L12 10.5ZM16.5 15 13.5 12 16.5 9H22V15H16.5ZM2 15V9H7.5L10.5 12 7.5 15H2ZM9 22V16.5L12 13.5 15 16.5V22H9ZM12 7.65 13 6.65V4H11V6.65L12 7.65ZM4 13H6.65L7.65 12 6.65 11H4V13ZM11 20H13V17.35L12 16.35 11 17.35V20ZM17.35 13H20V11H17.35L16.35 12 17.35 13Z"),
GLOBE_BOOK("M3.075 13Q3.05 12.75 3.0375 12.5T3.025 12Q3.025 10.125 3.725 8.4875T5.65 5.6375Q6.875 4.425 8.5 3.7125T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 12.25 20.9875 12.5T20.95 13H18.925Q18.975 12.75 18.9875 12.5T19 12Q19 11.75 18.9875 11.5T18.925 11H15.975Q16 11.25 16 11.5V12.5Q16 12.75 15.975 13H14V12.175Q14 11.875 13.9875 11.575T13.95 11H10.075Q10.05 11.275 10.0375 11.575T10.025 12.175V13H8.05Q8.025 12.75 8.025 12.5V11.5Q8.025 11.25 8.05 11H5.1Q5.05 11.25 5.0375 11.5T5.025 12Q5.025 12.25 5.0375 12.5T5.1 13H3.075ZM5.7 9H8.275Q8.475 7.925 8.775 7.0625T9.425 5.5Q8.225 5.95 7.25 6.8625T5.7 9ZM10.35 9H13.65Q13.4 7.925 13.025 6.9T12 5Q11.35 5.875 10.9625 6.9T10.35 9ZM15.75 9H18.325Q17.75 7.775 16.7625 6.8625T14.575 5.5Q14.925 6.25 15.2375 7.0875T15.75 9ZM11 21V20Q11 18.75 10.125 17.875T8 17H2V15H8Q9.2 15 10.2375 15.525T12 17Q12.725 16.05 13.7625 15.525T16 15H22V17H16Q14.75 17 13.875 17.875T13 20V21H11Z"),
HELP("M11.95 18Q12.475 18 12.8375 17.6375T13.2 16.75Q13.2 16.225 12.8375 15.8625T11.95 15.5Q11.425 15.5 11.0625 15.8625T10.7 16.75Q10.7 17.275 11.0625 17.6375T11.95 18ZM11.05 14.15H12.9Q12.9 13.325 13.0875 12.85T14.15 11.55Q14.8 10.9 15.175 10.3125T15.55 8.9Q15.55 7.5 14.525 6.75T12.1 6Q10.675 6 9.7875 6.75T8.55 8.55L10.2 9.2Q10.325 8.75 10.7625 8.225T12.1 7.7Q12.9 7.7 13.3 8.1375T13.7 9.1Q13.7 9.6 13.4 10.0375T12.65 10.85Q11.55 11.825 11.3 12.325T11.05 14.15ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
HOME("M6 19H9V13H15V19H18V10L12 5.5 6 10V19ZM4 21V9L12 3 20 9V21H13V15H11V21H4ZM12 12.25Z"),
HOST("M4 21Q3.175 21 2.5875 20.4125T2 19V5Q2 4.175 2.5875 3.5875T4 3H9Q9.825 3 10.4125 3.5875T11 5V19Q11 19.825 10.4125 20.4125T9 21H4ZM15 21Q14.175 21 13.5875 20.4125T13 19V5Q13 4.175 13.5875 3.5875T15 3H20Q20.825 3 21.4125 3.5875T22 5V19Q22 19.825 21.4125 20.4125T20 21H15ZM4 19H9V5H4V19ZM15 19H20V5H15V19ZM5 15H8V13H5V15ZM16 15H19V13H16V15ZM5 12H8V10H5V12ZM16 12H19V10H16V12ZM5 9H8V7H5V9ZM16 9H19V7H16V9ZM4 19H9 4ZM15 19H20 15Z"),
INFO("M11 17H13V11H11V17ZM12 9Q12.425 9 12.7125 8.7125T13 8Q13 7.575 12.7125 7.2875T12 7Q11.575 7 11.2875 7.2875T11 8Q11 8.425 11.2875 8.7125T12 9ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
KEYBOARD_ARROW_DOWN("M12 15.4 6 9.4 7.4 8 12 12.6 16.6 8 18 9.4 12 15.4Z"),
KEYBOARD_ARROW_UP("M12 10.8 7.4 15.4 6 14 12 8 18 14 16.6 15.4 12 10.8Z"),
LIST("M7 9V7H21V9H7ZM7 13V11H21V13H7ZM7 17V15H21V17H7ZM4 9Q3.575 9 3.2875 8.7125T3 8Q3 7.575 3.2875 7.2875T4 7Q4.425 7 4.7125 7.2875T5 8Q5 8.425 4.7125 8.7125T4 9ZM4 13Q3.575 13 3.2875 12.7125T3 12Q3 11.575 3.2875 11.2875T4 11Q4.425 11 4.7125 11.2875T5 12Q5 12.425 4.7125 12.7125T4 13ZM4 17Q3.575 17 3.2875 16.7125T3 16Q3 15.575 3.2875 15.2875T4 15Q4.425 15 4.7125 15.2875T5 16Q5 16.425 4.7125 16.7125T4 17Z"),
LISTS("M2 20V16H6V20H2ZM8 20V16H22V20H8ZM2 14V10H6V14H2ZM8 14V10H22V14H8ZM2 8V4H6V8H2ZM8 8V4H22V8H8Z"),
LOCAL_CAFE("M4 21V19H20V21H4ZM8 17Q6.35 17 5.175 15.825T4 13V3H20Q20.825 3 21.4125 3.5875T22 5V8Q22 8.825 21.4125 9.4125T20 10H18V13Q18 14.65 16.825 15.825T14 17H8ZM8 15H14Q14.825 15 15.4125 14.4125T16 13V5H6V13Q6 13.825 6.5875 14.4125T8 15ZM18 8H20V5H18V8ZM8 15H6 16 8Z"),
MENU("M3 18V16H21V18H3ZM3 13V11H21V13H3ZM3 8V6H21V8H3Z"),
MICROSOFT("M4 20H22v2H4V13H20v7h2V4H20v7H4V4h7V20h2V4h9V2H2V22H4"), // Not Material
MINIMIZE("M6 21V19H18V21H6Z"),
MOJANG("M13.9658 0C12.9552.828 12.7686 2.195 12.7007 3.418 12.7219 4.4201 14.0423 4.7174 14.6538 4.0082 15.0912 2.6579 14.3692 1.2739 13.9658 0ZM10.913 2.9297C10.7559 3.6983 10.6284 4.4669 10.4925 5.2355 8.9894 3.9913 7.1505 3.0825 5.142 3.2948 3.4944 3.4646.9429 2.6961.1404 4.6452-.1229 8.314.0722 12.0124.034 15.6896-.0891 16.9126.8957 18.1101 2.1781 17.9699 6.3989 18.0039 10.6283 18.0172 14.8491 17.9661 16.1145 18.102 16.8067 16.9554 16.9851 15.8768 13.2696 16.4374 9.1761 16.9296 5.7111 15.1292 2.5986 13.5836 2.246 8.3139 5.5581 6.798 9.3203 5.1759 13.8596 8.0383 14.9424 11.7877 15.6133 12.1105 16.2844 12.4247 16.9596 12.7432 16.6241 10.3483 16.6537 7.8005 15.5793 5.5882 15.0571 4.7474 14.0975 6.2714 13.3799 5.6217 12.416 4.8659 11.7495 3.8129 10.913 2.9297Z"), // Not Material
MORE_HORIZ("M6 14Q5.175 14 4.5875 13.4125T4 12Q4 11.175 4.5875 10.5875T6 10Q6.825 10 7.4125 10.5875T8 12Q8 12.825 7.4125 13.4125T6 14ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM18 14Q17.175 14 16.5875 13.4125T16 12Q16 11.175 16.5875 10.5875T18 10Q18.825 10 19.4125 10.5875T20 12Q20 12.825 19.4125 13.4125T18 14Z"),
MORE_VERT("M12 20Q11.175 20 10.5875 19.4125T10 18Q10 17.175 10.5875 16.5875T12 16Q12.825 16 13.4125 16.5875T14 18Q14 18.825 13.4125 19.4125T12 20ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM12 8Q11.175 8 10.5875 7.4125T10 6Q10 5.175 10.5875 4.5875T12 4Q12.825 4 13.4125 4.5875T14 6Q14 6.825 13.4125 7.4125T12 8Z"),
OPEN_IN_NEW("M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H12V5H5V19H19V12H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM9.7 15.7 8.3 14.3 17.6 5H14V3H21V10H19V6.4L9.7 15.7Z"),
OUTPUT("M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V7H19V5H5V19H19V17H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM17 17 15.6 15.6 18.175 13H9V11H18.175L15.6 8.4 17 7 22 12 17 17Z"),
PACKAGE("M10 9.75 12 8.75 14 9.75V5H10V9.75ZM7 17V15H12V17H7ZM5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V19Q21 19.825 20.4125 20.4125T19 21H5ZM5 5V19 5ZM5 19H19V5H16V13L12 11 8 13V5H5V19Z"),
PACKAGE2("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM11 21.725 4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725ZM16 8.525 17.925 7.425 12 4 10.05 5.125 16 8.525ZM12 10.85 13.95 9.725 8.025 6.3 6.075 7.425 12 10.85Z"),
PERSON("M12 12Q10.35 12 9.175 10.825T8 8Q8 6.35 9.175 5.175T12 4Q13.65 4 14.825 5.175T16 8Q16 9.65 14.825 10.825T12 12ZM4 20V17.2Q4 16.35 4.4375 15.6375T5.6 14.55Q7.15 13.775 8.75 13.3875T12 13Q13.65 13 15.25 13.3875T18.4 14.55Q19.125 14.925 19.5625 15.6375T20 17.2V20H4ZM6 18H18V17.2Q18 16.925 17.8625 16.7T17.5 16.35Q16.15 15.675 14.775 15.3375T12 15Q10.6 15 9.225 15.3375T6.5 16.35Q6.275 16.475 6.1375 16.7T6 17.2V18ZM12 10Q12.825 10 13.4125 9.4125T14 8Q14 7.175 13.4125 6.5875T12 6Q11.175 6 10.5875 6.5875T10 8Q10 8.825 10.5875 9.4125T12 10ZM12 8ZM12 18Z"),
PUBLIC("M12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM11 19.95V18Q10.175 18 9.5875 17.4125T9 16V15L4.2 10.2Q4.125 10.65 4.0625 11.1T4 12Q4 15.025 5.9875 17.3T11 19.95ZM17.9 17.4Q18.925 16.275 19.4625 14.8875T20 12Q20 9.55 18.6375 7.525T15 4.6V5Q15 5.825 14.4125 6.4125T13 7H11V9Q11 9.425 10.7125 9.7125T10 10H8V12H14Q14.425 12 14.7125 12.2875T15 13V16H16Q16.65 16 17.175 16.3875T17.9 17.4Z"),
REFRESH("M12 20Q8.65 20 6.325 17.675T4 12Q4 8.65 6.325 6.325T12 4Q13.725 4 15.3 4.7125T18 6.75V4H20V11H13V9H17.2Q16.4 7.6 15.0125 6.8T12 6Q9.5 6 7.75 7.75T6 12Q6 14.5 7.75 16.25T12 18Q13.925 18 15.475 16.9T17.65 14H19.75Q19.05 16.65 16.9 18.325T12 20Z"),
RELEASE_CIRCLE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
RESTORE("M12 21Q8.55 21 5.9875 18.7125T3.05 13H5.1Q5.45 15.6 7.4125 17.3T12 19Q14.925 19 16.9625 16.9625T19 12Q19 9.075 16.9625 7.0375T12 5Q10.275 5 8.775 5.8T6.25 8H9V10H3V4H5V6.35Q6.275 4.75 8.1125 3.875T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 13.875 20.2875 15.5125T18.3625 18.3625Q17.15 19.575 15.5125 20.2875T12 21Z"), // Not Material
ROCKET_LAUNCH("M5.65 10.025 7.6 10.85Q7.95 10.15 8.325 9.5T9.15 8.2L7.75 7.925 5.65 10.025ZM9.2 12.1 12.05 14.925Q13.1 14.525 14.3 13.7T16.55 11.825Q18.3 10.075 19.2875 7.9375T20.15 4Q18.35 3.875 16.2 4.8625T12.3 7.6Q11.25 8.65 10.425 9.85T9.2 12.1ZM13.65 10.475Q13.075 9.9 13.075 9.0625T13.65 7.65Q14.225 7.075 15.075 7.075T16.5 7.65Q17.075 8.225 17.075 9.0625T16.5 10.475Q15.925 11.05 15.075 11.05T13.65 10.475ZM14.125 18.5 16.225 16.4 15.95 15Q15.3 15.45 14.65 15.8125T13.3 16.525L14.125 18.5ZM21.95 2.175Q22.425 5.2 21.3625 8.0625T17.7 13.525L18.2 16Q18.3 16.5 18.15 16.975T17.65 17.8L13.45 22 11.35 17.075 7.075 12.8 2.15 10.7 6.325 6.5Q6.675 6.15 7.1625 6T8.15 5.95L10.625 6.45Q13.225 3.85 16.075 2.775T21.95 2.175ZM3.925 15.975Q4.8 15.1 6.0625 15.0875T8.2 15.95Q9.075 16.825 9.0625 18.0875T8.175 20.225Q7.55 20.85 6.0875 21.3T2.05 22.1Q2.4 19.525 2.85 18.0625T3.925 15.975ZM5.35 17.375Q5.1 17.625 4.85 18.2875T4.5 19.625Q5.175 19.525 5.8375 19.2875T6.75 18.8Q7.05 18.5 7.075 18.075T6.8 17.35Q6.5 17.05 6.075 17.0625T5.35 17.375Z"),
SCREENSHOT_MONITOR("M15 16H19V12H17.5V14.5H15V16ZM5 10H6.5V7.5H9V6H5V10ZM8 21V19H4Q3.175 19 2.5875 18.4125T2 17V5Q2 4.175 2.5875 3.5875T4 3H20Q20.825 3 21.4125 3.5875T22 5V17Q22 17.825 21.4125 18.4125T20 19H16V21H8ZM4 17H20V5H4V17ZM4 17V5 17Z"),
SCRIPT("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z"), // Not Material
SEARCH("M19.6 21 13.3 14.7Q12.55 15.3 11.575 15.65T9.5 16Q6.775 16 4.8875 14.1125T3 9.5Q3 6.775 4.8875 4.8875T9.5 3Q12.225 3 14.1125 4.8875T16 9.5Q16 10.6 15.65 11.575T14.7 13.3L21 19.6 19.6 21ZM9.5 14Q11.375 14 12.6875 12.6875T14 9.5Q14 7.625 12.6875 6.3125T9.5 5Q7.625 5 6.3125 6.3125T5 9.5Q5 11.375 6.3125 12.6875T9.5 14Z"),
SELECT_ALL("M7 17V7H17V17H7ZM9 15H15V9H9V15ZM5 19V21Q4.175 21 3.5875 20.4125T3 19H5ZM3 17V15H5V17H3ZM3 13V11H5V13H3ZM3 9V7H5V9H3ZM5 5H3Q3 4.175 3.5875 3.5875T5 3V5ZM7 21V19H9V21H7ZM7 5V3H9V5H7ZM11 21V19H13V21H11ZM11 5V3H13V5H11ZM15 21V19H17V21H15ZM15 5V3H17V5H15ZM19 21V19H21Q21 19.825 20.4125 20.4125T19 21ZM19 17V15H21V17H19ZM19 13V11H21V13H19ZM19 9V7H21V9H19ZM19 5V3Q19.825 3 20.4125 3.5875T21 5H19Z"),
SETTINGS("M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12 19.5 11.66 19.47 11.34 19.43 11.02L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.57 5.11 19.4 5.02 19.22 5.02 19.16 5.02 19.1 5.03 19.05 5.05L16.56 6.05C16.04 5.65 15.48 5.32 14.87 5.07L14.49 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.89 5.03 4.83 5.02 4.77 5.02 4.6 5.02 4.43 5.11 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11.02C4.53 11.34 4.5 11.67 4.5 12 4.5 12.33 4.53 12.66 4.57 12.98L2.46 14.63C2.27 14.78 2.22 15.05 2.34 15.27L4.34 18.73C4.43 18.89 4.6 18.98 4.78 18.98 4.84 18.98 4.9 18.97 4.95 18.95L7.44 17.95C7.96 18.35 8.52 18.68 9.13 18.93L9.51 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.49 21.58L14.87 18.93C15.48 18.68 16.04 18.34 16.56 17.95L19.05 18.95C19.11 18.97 19.17 18.98 19.23 18.98 19.4 18.98 19.57 18.89 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.98ZM17.45 11.27C17.49 11.58 17.5 11.79 17.5 12 17.5 12.21 17.48 12.43 17.45 12.73L17.31 13.86 18.2 14.56 19.28 15.4 18.58 16.61 17.31 16.1 16.27 15.68 15.37 16.36C14.94 16.68 14.53 16.92 14.12 17.09L13.06 17.52 12.9 18.65 12.7 20H11.3L11.11 18.65 10.95 17.52 9.89 17.09C9.46 16.91 9.06 16.68 8.66 16.38L7.75 15.68 6.69 16.11 5.42 16.62 4.72 15.41 5.8 14.57 6.69 13.87 6.55 12.74C6.52 12.43 6.5 12.2 6.5 12S6.52 11.57 6.55 11.27L6.69 10.14 5.8 9.44 4.72 8.6 5.42 7.39 6.69 7.9 7.73 8.32 8.63 7.64C9.06 7.32 9.47 7.08 9.88 6.91L10.94 6.48 11.1 5.35 11.3 4H12.69L12.88 5.35 13.04 6.48 14.1 6.91C14.53 7.09 14.93 7.32 15.33 7.62L16.24 8.32 17.3 7.89 18.57 7.38 19.27 8.59 18.2 9.44 17.31 10.14 17.45 11.27ZM12 8C9.79 8 8 9.79 8 12S9.79 16 12 16 16 14.21 16 12 14.21 8 12 8ZM12 14C10.9 14 10 13.1 10 12S10.9 10 12 10 14 10.9 14 12 13.1 14 12 14Z"), // Material Icons
STADIA_CONTROLLER("M4.725 20Q3.225 20 2.1625 18.925T1.05 16.325Q1.05 16.1 1.075 15.875T1.15 15.425L3.25 7.025Q3.6 5.675 4.675 4.8375T7.125 4H16.875Q18.25 4 19.325 4.8375T20.75 7.025L22.85 15.425Q22.9 15.65 22.9375 15.8875T22.975 16.35Q22.975 17.875 21.8875 18.9375T19.275 20Q18.225 20 17.325 19.45T15.975 17.95L15.275 16.5Q15.15 16.25 14.9 16.125T14.375 16H9.625Q9.35 16 9.1 16.125T8.725 16.5L8.025 17.95Q7.575 18.9 6.675 19.45T4.725 20ZM4.8 18Q5.275 18 5.6625 17.75T6.25 17.075L6.95 15.65Q7.325 14.875 8.05 14.4375T9.625 14H14.375Q15.225 14 15.95 14.45T17.075 15.65L17.775 17.075Q17.975 17.5 18.3625 17.75T19.225 18Q19.925 18 20.425 17.5375T20.95 16.375Q20.95 16.4 20.9 15.9L18.8 7.525Q18.625 6.85 18.1 6.425T16.875 6H7.125Q6.425 6 5.8875 6.425T5.2 7.525L3.1 15.9Q3.05 16.05 3.05 16.35 3.05 17.05 3.5625 17.525T4.8 18ZM13.5 11Q13.925 11 14.2125 10.7125T14.5 10Q14.5 9.575 14.2125 9.2875T13.5 9Q13.075 9 12.7875 9.2875T12.5 10Q12.5 10.425 12.7875 10.7125T13.5 11ZM15.5 9Q15.925 9 16.2125 8.7125T16.5 8Q16.5 7.575 16.2125 7.2875T15.5 7Q15.075 7 14.7875 7.2875T14.5 8Q14.5 8.425 14.7875 8.7125T15.5 9ZM15.5 13Q15.925 13 16.2125 12.7125T16.5 12Q16.5 11.575 16.2125 11.2875T15.5 11Q15.075 11 14.7875 11.2875T14.5 12Q14.5 12.425 14.7875 12.7125T15.5 13ZM17.5 11Q17.925 11 18.2125 10.7125T18.5 10Q18.5 9.575 18.2125 9.2875T17.5 9Q17.075 9 16.7875 9.2875T16.5 10Q16.5 10.425 16.7875 10.7125T17.5 11ZM8.5 12.5Q8.825 12.5 9.0375 12.2875T9.25 11.75V10.75H10.25Q10.575 10.75 10.7875 10.5375T11 10Q11 9.675 10.7875 9.4625T10.25 9.25H9.25V8.25Q9.25 7.925 9.0375 7.7125T8.5 7.5Q8.175 7.5 7.9625 7.7125T7.75 8.25V9.25H6.75Q6.425 9.25 6.2125 9.4625T6 10Q6 10.325 6.2125 10.5375T6.75 10.75H7.75V11.75Q7.75 12.075 7.9625 12.2875T8.5 12.5ZM12 12Z"),
STYLE("M3.975 19.8 3.125 19.45Q2.35 19.125 2.0875 18.325T2.175 16.75L3.975 12.85V19.8ZM7.975 22Q7.15 22 6.5625 21.4125T5.975 20V14L8.625 21.35Q8.7 21.525 8.775 21.6875T8.975 22H7.975ZM13.125 21.9Q12.325 22.2 11.575 21.825T10.525 20.65L6.075 8.45Q5.775 7.65 6.125 6.8875T7.275 5.85L14.825 3.1Q15.625 2.8 16.375 3.175T17.425 4.35L21.875 16.55Q22.175 17.35 21.825 18.1125T20.675 19.15L13.125 21.9ZM10.975 10Q11.4 10 11.6875 9.7125T11.975 9Q11.975 8.575 11.6875 8.2875T10.975 8Q10.55 8 10.2625 8.2875T9.975 9Q9.975 9.425 10.2625 9.7125T10.975 10ZM12.425 20 19.975 17.25 15.525 5 7.975 7.75 12.425 20ZM7.975 7.75 15.525 5 7.975 7.75Z"),
TEXTURE("M4.4-3Q3.925-3.1 3.5125-3.5125T3-4.4L19.6-21Q20.125-20.875 20.5-20.4875T21.025-19.6L4.4-3ZM3-9.3V-12.1L11.9-21H14.7L3-9.3ZM3-17V-19Q3-19.825 3.5875-20.4125T5-21H7L3-17ZM17-3 21-7V-5Q21-4.175 20.4125-3.5875T19-3H17ZM9.3-3 21-14.7V-11.9L12.1-3H9.3Z"),
TRIP("M4 21Q3.175 21 2.5875 20.4125T2 19V8Q2 7.175 2.5875 6.5875T4 6H8V4Q8 3.175 8.5875 2.5875T10 2H14Q14.825 2 15.4125 2.5875T16 4V6H20Q20.825 6 21.4125 6.5875T22 8V19Q22 19.825 21.4125 20.4125T20 21H4ZM10 6H14V4H10V6ZM6 8H4V19H6V8ZM16 19V8H8V19H16ZM18 8V19H20V8H18ZM12 13.5Z"),
TUNE("M11 21V15H13V17H21V19H13V21H11ZM3 19V17H9V19H3ZM7 15V13H3V11H7V9H9V15H7ZM11 13V11H21V13H11ZM15 9V3H17V5H21V7H17V9H15ZM3 7V5H13V7H3Z"),
UPDATE("M12 21Q10.125 21 8.4875 20.2875T5.6375 18.3625Q4.425 17.15 3.7125 15.5125T3 12Q3 10.125 3.7125 8.4875T5.6375 5.6375Q6.85 4.425 8.4875 3.7125T12 3Q14.05 3 15.8875 3.875T19 6.35V4H21V10H15V8H17.75Q16.725 6.6 15.225 5.8T12 5Q9.075 5 7.0375 7.0375T5 12Q5 14.925 7.0375 16.9625T12 19Q14.625 19 16.5875 17.3T18.9 13H20.95Q20.575 16.425 18.0125 18.7125T12 21ZM14.8 16.2 11 12.4V7H13V11.6L16.2 14.8 14.8 16.2Z"),
WARNING("M1 21 12 2 23 21H1ZM4.45 19H19.55L12 6 4.45 19ZM12 18Q12.425 18 12.7125 17.7125T13 17Q13 16.575 12.7125 16.2875T12 16Q11.575 16 11.2875 16.2875T11 17Q11 17.425 11.2875 17.7125T12 18ZM11 15H13V10H11V15ZM12 12.5Z"),
WB_SUNNY("M11 4V1H13V4H11ZM11 23V20H13V23H11ZM20 13V11H23V13H20ZM1 13V11H4V13H1ZM18.7 6.7 17.3 5.3 19.05 3.5 20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3 6.7 18.7 4.95 20.5ZM19.05 20.5 17.3 18.7 18.7 17.3 20.5 19.05 19.05 20.5ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18Q9.5 18 7.75 16.25T6 12Q6 9.5 7.75 7.75T12 6Q14.5 6 16.25 7.75T18 12Q18 14.5 16.25 16.25T12 18ZM12 16Q13.675 16 14.8375 14.8375T16 12Q16 10.325 14.8375 9.1625T12 8Q10.325 8 9.1625 9.1625T8 12Q8 13.675 9.1625 14.8375T12 16ZM12 12Z"),
;
public static final double DEFAULT_SIZE = 24;
private final String path;
@@ -124,39 +118,38 @@ public enum SVG {
return path;
}
private static Node createIcon(SVGPath path, double width, double height) {
if (width < 0 || height < 0) {
private static Node createIcon(SVGPath path, double size) {
if (size < 0) {
StackPane pane = new StackPane(path);
pane.setAlignment(Pos.CENTER);
return pane;
}
Group svg = new Group(path);
double scale = Math.min(width / 24, height / 24);
double scale = size / 24;
svg.setScaleX(scale);
svg.setScaleY(scale);
return svg;
}
public Node createIcon(ObservableValue<? extends Paint> fill, double width, double height) {
public Node createIcon(ObservableValue<? extends Paint> fill, double size) {
SVGPath p = new SVGPath();
p.getStyleClass().add("svg");
p.setContent(path);
if (fill != null)
p.fillProperty().bind(fill);
return createIcon(p, width, height);
return createIcon(p, size);
}
public Node createIcon(Paint fill, double width, double height) {
public Node createIcon(Paint fill, double size) {
SVGPath p = new SVGPath();
p.getStyleClass().add("svg");
p.setContent(path);
if (fill != null)
p.fillProperty().set(fill);
return createIcon(p, width, height);
return createIcon(p, size);
}
}
@@ -89,7 +89,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-tool-bar-button");
ret.textFillProperty().bind(Theme.foregroundFillBinding());
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
ret.setText(text);
ret.setOnAction(e -> onClick.run());
return ret;
@@ -98,7 +98,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) {
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-tool-bar-button");
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1, -1)));
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1)));
ret.setText(text);
ret.setOnAction(e -> onClick.run());
return ret;
@@ -108,7 +108,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-decorator-button");
ret.textFillProperty().bind(Theme.foregroundFillBinding());
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
FXUtils.installFastTooltip(ret, tooltip);
ret.setOnAction(e -> onClick.run());
return ret;
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
@@ -50,13 +51,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
super(skinnable);
BorderPane root = new BorderPane();
root.setCursor(Cursor.HAND);
FXUtils.onClicked(root, skinnable::fire);
JFXRadioButton chkSelected = new JFXRadioButton() {
@Override
public void fire() {
skinnable.fire();
}
};
JFXRadioButton chkSelected = new JFXRadioButton();
chkSelected.setMouseTransparent(true);
BorderPane.setAlignment(chkSelected, Pos.CENTER);
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
root.setLeft(chkSelected);
@@ -116,10 +115,10 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
});
btnMove.getStyleClass().add("toggle-icon4");
if (skinnable.getAccount().isPortable()) {
btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
btnMove.setGraphic(SVG.PUBLIC.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
} else {
btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1));
btnMove.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
}
spinnerMove.setContent(btnMove);
@@ -141,7 +140,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
.start();
});
btnRefresh.getStyleClass().add("toggle-icon4");
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1));
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
spinnerRefresh.setContent(btnRefresh);
right.getChildren().add(spinnerRefresh);
@@ -158,7 +157,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
}
});
btnUpload.getStyleClass().add("toggle-icon4");
btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1));
btnUpload.setGraphic(SVG.CHECKROOM.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty());
spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin());
@@ -171,7 +170,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
btnUpload.getStyleClass().add("toggle-icon4");
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
btnCopyUUID.setGraphic(SVG.COPY.createIcon(Theme.blackFill(), -1, -1));
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
spinnerCopyUUID.setContent(btnCopyUUID);
right.getChildren().add(spinnerCopyUUID);
@@ -180,7 +179,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1));
btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
right.getChildren().add(btnRemove);
root.setRight(right);
@@ -103,7 +103,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG.ACCOUNT));
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
boxMethods.getChildren().add(offlineItem);
@@ -119,7 +119,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setLeftGraphic(wrap(SVG.SERVER));
item.setLeftGraphic(wrap(SVG.DRESSER));
item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));
JFXButton btnRemove = new JFXButton();
@@ -130,7 +130,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
e.consume();
});
btnRemove.getStyleClass().add("toggle-icon4");
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
item.setRightGraphic(btnRemove);
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
@@ -158,7 +158,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
addAuthServerItem.setTitle(i18n("account.injector.add"));
addAuthServerItem.setSubtitle(i18n("account.methods.authlib_injector"));
addAuthServerItem.setActionButtonVisible(false);
addAuthServerItem.setLeftGraphic(wrap(SVG.PLUS_CIRCLE_OUTLINE));
addAuthServerItem.setLeftGraphic(wrap(SVG.ADD_CIRCLE));
addAuthServerItem.setOnAction(e -> Controllers.dialog(new AddAuthlibInjectorServerPane()));
VBox.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0));
}
@@ -262,7 +262,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
MessageDialogPane.MessageType.WARNING,
doCreate,
() -> {
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid"));
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid.tip"));
body.setDisable(false);
spinner.hideSpinner();
}
@@ -460,7 +460,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
linksContainer.setMinWidth(USE_PREF_SIZE);
JFXButton btnAddServer = new JFXButton();
btnAddServer.setGraphic(SVG.PLUS.createIcon(Theme.blackFill(), 20, 20));
btnAddServer.setGraphic(SVG.ADD.createIcon(Theme.blackFill(), 20));
btnAddServer.getStyleClass().add("toggle-icon4");
btnAddServer.setOnAction(e -> {
Controllers.dialog(new AddAuthlibInjectorServerPane());
@@ -101,6 +101,9 @@ public class OfflineAccountSkinPane extends StackPane {
VBox optionPane = new VBox(skinItem, skinOptionPane);
pane.setRight(optionPane);
skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
layout.setBody(pane);
cslApiField.setPromptText(i18n("account.skin.type.csl_api.location.hint"));
@@ -108,10 +111,10 @@ public class OfflineAccountSkinPane extends StackPane {
skinItem.loadChildren(Arrays.asList(
new MultiFileItem.Option<>(i18n("message.default"), Skin.Type.DEFAULT),
new MultiFileItem.Option<>("Steve", Skin.Type.STEVE),
new MultiFileItem.Option<>("Alex", Skin.Type.ALEX),
new MultiFileItem.Option<>(i18n("account.skin.type.steve"), Skin.Type.STEVE),
new MultiFileItem.Option<>(i18n("account.skin.type.alex"), Skin.Type.ALEX),
new MultiFileItem.Option<>(i18n("account.skin.type.local_file"), Skin.Type.LOCAL_FILE),
new MultiFileItem.Option<>("LittleSkin", Skin.Type.LITTLE_SKIN),
new MultiFileItem.Option<>(i18n("account.skin.type.little_skin"), Skin.Type.LITTLE_SKIN),
new MultiFileItem.Option<>(i18n("account.skin.type.csl_api"), Skin.Type.CUSTOM_SKIN_LOADER_API)
));
@@ -201,6 +204,12 @@ public class OfflineAccountSkinPane extends StackPane {
}
private Skin getSkin() {
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
Skin.Type type = skinItem.getSelectedData();
if (type == Skin.Type.LOCAL_FILE) {
return new Skin(type, cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
} else {
String cslApi = type == Skin.Type.CUSTOM_SKIN_LOADER_API ? cslApiField.getText() : null;
return new Skin(type, cslApi, null, null, null);
}
}
}
@@ -1,7 +1,28 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.animation;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
/**
* @author Glavo
*/
public final class AnimationUtils {
private AnimationUtils() {
@@ -15,9 +36,14 @@ public final class AnimationUtils {
public static void init() {
}
private static final boolean enabled = !ConfigHolder.config().isAnimationDisabled();
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();
public static boolean isAnimationEnabled() {
return enabled;
return ENABLED;
}
public static boolean playWindowAnimation() {
return PLAY_WINDOW_ANIMATION;
}
}
@@ -17,7 +17,6 @@
*/
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.scene.Node;
@@ -26,6 +25,8 @@ import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
public class TransitionPane extends StackPane implements AnimationHandler {
private static final Duration DEFAULT_DURATION = Duration.millis(200);
private Duration duration;
private Node previousNode, currentNode;
@@ -55,7 +56,7 @@ public class TransitionPane extends StackPane implements AnimationHandler {
}
public void setContent(Node newView, AnimationProducer transition) {
setContent(newView, transition, Duration.millis(160));
setContent(newView, transition, DEFAULT_DURATION);
}
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
@@ -64,26 +65,25 @@ public class TransitionPane extends StackPane implements AnimationHandler {
updateContent(newView);
if (previousNode == EMPTY_PANE) {
setMouseTransparent(false);
getChildren().setAll(newView);
return;
}
if (AnimationUtils.isAnimationEnabled()) {
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
setMouseTransparent(true);
transition.init(this);
// runLater or "init" will not work
Platform.runLater(() -> {
Timeline newAnimation = new Timeline();
newAnimation.getKeyFrames().addAll(transition.animate(this));
newAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
newAnimation.getKeyFrames().setAll(transition.animate(this));
newAnimation.setOnFinished(e -> {
setMouseTransparent(false);
getChildren().remove(previousNode);
}));
});
FXUtils.playAnimation(this, "transition_pane", newAnimation);
});
} else {
setMouseTransparent(false);
getChildren().remove(previousNode);
}
}
@@ -99,8 +99,6 @@ public class TransitionPane extends StackPane implements AnimationHandler {
if (previousNode == newView)
previousNode = EMPTY_PANE;
setMouseTransparent(true);
currentNode = newView;
getChildren().setAll(previousNode, currentNode);
@@ -24,6 +24,8 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.versions.VersionPage;
import java.util.function.Consumer;
@@ -54,11 +56,36 @@ public class AdvancedListBox extends ScrollPane {
return this;
}
public AdvancedListBox addNavigationDrawerItem(Consumer<AdvancedListItem> fn) {
private AdvancedListItem createNavigationDrawerItem(String title, SVG leftGraphic) {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setActionButtonVisible(false);
fn.accept(item);
item.setTitle(title);
if (leftGraphic != null) {
item.setLeftGraphic(VersionPage.wrap(leftGraphic));
}
return item;
}
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction) {
return addNavigationDrawerItem(title, leftGraphic, onAction, null);
}
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction, Consumer<AdvancedListItem> initializer) {
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
if (onAction != null) {
item.setOnAction(e -> onAction.run());
}
if (initializer != null) {
initializer.accept(item);
}
return add(item);
}
public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title, SVG leftGraphic) {
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
item.setOnAction(e -> tabHeader.select(tab));
return add(item);
}
@@ -41,6 +41,7 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
RipplerContainer container = new RipplerContainer(root);
HBox left = new HBox();
left.setAlignment(Pos.CENTER_LEFT);
left.setMouseTransparent(true);
TwoLineListItem item = new TwoLineListItem();
@@ -1,52 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
public final class AnnouncementCard extends VBox {
public AnnouncementCard(String title, String content, Runnable onClose) {
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
body.setLineSpacing(4);
BorderPane titleBar = new BorderPane();
titleBar.getStyleClass().add("title");
titleBar.setLeft(new Label(title));
if (onClose != null) {
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
hideNode.setOnMouseClicked(e -> onClose.run());
hideNode.setCursor(Cursor.HAND);
titleBar.setRight(hideNode);
}
getChildren().setAll(titleBar, body);
setSpacing(16);
getStyleClass().addAll("card", "announcement");
}
}
@@ -82,7 +82,7 @@ final class ComponentListCell extends StackPane {
VBox groupNode = new VBox();
Node expandIcon = SVG.EXPAND.createIcon(Theme.blackFill(), 20, 20);
Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(Theme.blackFill(), 20);
JFXButton expandButton = new JFXButton();
expandButton.setGraphic(expandIcon);
expandButton.getStyleClass().add("options-list-item-expand-button");
@@ -149,8 +149,8 @@ final class ComponentListCell extends StackPane {
}
Platform.runLater(() -> {
double newAnimatedHeight = (list.prefHeight(-1) + 8 + 10) * (expanded ? 1 : -1);
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(-1);
double newAnimatedHeight = (list.prefHeight(list.getWidth()) + 8 + 10) * (expanded ? 1 : -1);
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(list.getWidth());
double contentHeight = expanded ? newAnimatedHeight : 0;
if (expanded) {
@@ -58,7 +58,7 @@ public class FileItem extends BorderPane {
setLeft(left);
JFXButton right = new JFXButton();
right.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 15, 15));
right.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 16));
right.getStyleClass().add("toggle-icon4");
right.setOnAction(e -> onExplore());
FXUtils.installFastTooltip(right, i18n("button.edit"));
@@ -81,7 +81,7 @@ public class FileSelector extends HBox {
FXUtils.bindString(customField, valueProperty());
JFXButton selectButton = new JFXButton();
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15, 15));
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15));
selectButton.setOnAction(e -> {
if (directory) {
DirectoryChooser chooser = new DirectoryChooser();
@@ -49,25 +49,25 @@ public class HintPane extends VBox {
SVG svg;
switch (type) {
case INFO:
svg = SVG.INFORMATION_OUTLINE;
svg = SVG.INFO;
break;
case ERROR:
svg = SVG.CLOSE_CIRCLE_OUTLINE;
svg = SVG.ERROR;
break;
case SUCCESS:
svg = SVG.CHECK_CIRCLE_OUTLINE;
svg = SVG.CHECK_CIRCLE;
break;
case WARNING:
svg = SVG.ALERT_OUTLINE;
svg = SVG.WARNING;
break;
case QUESTION:
svg = SVG.HELP_CIRCLE_OUTLINE;
svg = SVG.HELP;
break;
default:
throw new IllegalArgumentException("Unrecognized message box message type " + type);
}
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16, 16), new Text(type.getDisplayName()));
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16), new Text(type.getDisplayName()));
hbox.setAlignment(Pos.CENTER_LEFT);
flow.getChildren().setAll(label);
getChildren().setAll(hbox, flow);
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.ui.SVG;
public class IconedMenuItem extends IconedItem {
public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) {
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14, 14), 14, 14) : null, text);
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14), 14, 14) : null, text);
getStyleClass().setAll("iconed-menu-item");
@@ -110,7 +110,7 @@ public class IconedTwoLineListItem extends HBox {
if (externalLinkButton == null) {
externalLinkButton = new JFXButton();
externalLinkButton.getStyleClass().add("toggle-icon4");
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1));
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
}
return externalLinkButton;
@@ -54,12 +54,12 @@ public final class ImagePickerItem extends BorderPane {
imageView.setPreserveRatio(true);
JFXButton selectButton = new JFXButton();
selectButton.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 20, 20));
selectButton.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 20));
selectButton.onActionProperty().bind(onSelectButtonClicked);
selectButton.getStyleClass().add("toggle-icon4");
JFXButton deleteButton = new JFXButton();
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20));
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
deleteButton.getStyleClass().add("toggle-icon4");
@@ -20,24 +20,28 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Stack;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class StackContainerPane extends StackPane {
private final Stack<Node> stack = new Stack<>();
public class JFXDialogPane extends StackPane {
private final ArrayList<Node> stack = new ArrayList<>();
public int size() {
return stack.size();
}
public Optional<Node> peek() {
if (stack.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(stack.peek());
return Optional.of(stack.get(stack.size() - 1));
}
}
public void push(Node node) {
stack.push(node);
stack.add(node);
getChildren().setAll(node);
LOG.info(this + " " + stack);
@@ -48,7 +52,7 @@ public class StackContainerPane extends StackPane {
if (stack.isEmpty())
getChildren().setAll();
else
getChildren().setAll(stack.peek());
getChildren().setAll(stack.get(stack.size() - 1));
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
}
@@ -22,18 +22,12 @@ import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
public class JFXHyperlink extends Hyperlink {
public JFXHyperlink() {
super();
setGraphic(SVG.LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 16, 16));
}
public final class JFXHyperlink extends Hyperlink {
public JFXHyperlink(String text) {
super(text);
setGraphic(SVG.LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 16, 16));
setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16));
}
public void setExternalLink(String externalLink) {
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXListView;
import javafx.beans.binding.DoubleBinding;
import javafx.css.PseudoClass;
import javafx.scene.control.ListCell;
import javafx.scene.layout.Region;
@@ -45,9 +46,11 @@ public abstract class MDListCell<T> extends ListCell<T> {
Region clippedContainer = (Region) listView.lookup(".clipped-container");
setPrefWidth(0);
if (clippedContainer != null) {
maxWidthProperty().bind(clippedContainer.widthProperty());
prefWidthProperty().bind(clippedContainer.widthProperty());
minWidthProperty().bind(clippedContainer.widthProperty());
DoubleBinding converted = clippedContainer.widthProperty().subtract(1);
maxWidthProperty().bind(converted);
prefWidthProperty().bind(converted);
minWidthProperty().bind(converted);
}
}
@@ -81,8 +81,8 @@ public class MenuUpDownButton extends Control {
label.setStyle("-fx-text-fill: black;");
label.textProperty().bind(control.text);
Node up = SVG.MENU_UP.createIcon(Theme.blackFill(), 16, 16);
Node down = SVG.MENU_DOWN.createIcon(Theme.blackFill(), 16, 16);
Node up = SVG.ARROW_DROP_UP.createIcon(Theme.blackFill(), 16);
Node down = SVG.ARROW_DROP_DOWN.createIcon(Theme.blackFill(), 16);
JFXButton button = new JFXButton();
button.setGraphic(content);
@@ -74,24 +74,24 @@ public final class MessageDialogPane extends HBox {
SVG svg;
switch (type) {
case INFO:
svg = SVG.INFO_CIRCLE;
svg = SVG.INFO;
break;
case ERROR:
svg = SVG.CLOSE_CIRCLE;
svg = SVG.ERROR;
break;
case SUCCESS:
svg = SVG.CHECK_CIRCLE;
break;
case WARNING:
svg = SVG.ALERT;
svg = SVG.WARNING;
break;
case QUESTION:
svg = SVG.HELP_CIRCLE;
svg = SVG.HELP;
break;
default:
throw new IllegalArgumentException("Unrecognized message box message type " + type);
}
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40, 40));
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40));
VBox vbox = new VBox();
HBox.setHgrow(vbox, Priority.ALWAYS);
@@ -53,10 +53,9 @@ public final class MultiFileItem<T> extends VBox {
setSpacing(8);
group.selectedToggleProperty().addListener((a, b, newValue) -> {
selectedData.set(newValue != null ? (T) newValue.getUserData() : null);
if (toggleSelectedListener != null)
toggleSelectedListener.accept(newValue);
selectedData.set(newValue != null ? (T) newValue.getUserData() : null);
});
selectedData.addListener((a, b, newValue) -> {
Optional<Toggle> selecting = group.getToggles().stream()
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.util.Lang;
import java.util.List;
@@ -46,6 +47,7 @@ import java.util.List;
@DefaultProperty("container")
public class RipplerContainer extends StackPane {
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
private static final Duration DURATION = Duration.millis(200);
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
@@ -103,11 +105,10 @@ public class RipplerContainer extends StackPane {
rectangle.heightProperty().bind(heightProperty());
}));
setOnMouseEntered(e -> {
new Transition() {
if (AnimationUtils.isAnimationEnabled()) {
setOnMouseEntered(e -> new Transition() {
{
setCycleCount(1);
setCycleDuration(Duration.millis(200));
setCycleDuration(DURATION);
setInterpolator(Interpolator.EASE_IN);
}
@@ -115,14 +116,11 @@ public class RipplerContainer extends StackPane {
protected void interpolate(double frac) {
interpolateBackground(frac);
}
}.play();
});
}.play());
setOnMouseExited(e -> {
new Transition() {
setOnMouseExited(e -> new Transition() {
{
setCycleCount(1);
setCycleDuration(Duration.millis(200));
setCycleDuration(DURATION);
setInterpolator(Interpolator.EASE_OUT);
}
@@ -130,8 +128,11 @@ public class RipplerContainer extends StackPane {
protected void interpolate(double frac) {
interpolateBackground(1 - frac);
}
}.play();
});
}.play());
} else {
setOnMouseEntered(e -> interpolateBackground(1));
setOnMouseExited(e -> interpolateBackground(0));
}
}
private void interpolateBackground(double frac) {
@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
}
}
public interface State {}
public static class LoadedState implements State {}
public static class LoadingState implements State {}
public static class FailedState implements State {
private final String reason;
public FailedState(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
}
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
}
@@ -270,21 +270,21 @@ public final class TaskListPane extends StackPane {
BorderPane.setMargin(title, new Insets(0, 0, 0, 8));
setPadding(new Insets(0, 0, 8, 4));
setCenter(title);
setLeft(FXUtils.limitingSize(SVG.DOTS_HORIZONTAL.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.MORE_HORIZ.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void begin() {
if (started) return;
started = true;
setLeft(FXUtils.limitingSize(SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void fail() {
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void succeed() {
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void count() {
@@ -18,6 +18,9 @@
package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXSnackbar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.wizard.Navigation;
public class Decorator extends Control {
@@ -61,12 +67,40 @@ public class Decorator extends Control {
private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();
private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();
private boolean playRestoreMinimizeAnimation = false;
public Decorator(Stage primaryStage) {
this.primaryStage = primaryStage;
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
primaryStage.initStyle(StageStyle.UNDECORATED);
if (AnimationUtils.playWindowAnimation()) {
FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {
if (playRestoreMinimizeAnimation && !iconified) {
playRestoreMinimizeAnimation = false;
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
)
);
timeline.play();
}
});
}
}
public Stage getPrimaryStage() {
@@ -239,7 +273,29 @@ public class Decorator extends Control {
}
public void minimize() {
primaryStage.setIconified(true);
if (AnimationUtils.playWindowAnimation()) {
playRestoreMinimizeAnimation = true;
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
)
);
timeline.setOnFinished(event -> primaryStage.setIconified(true));
timeline.play();
} else {
primaryStage.setIconified(true);
}
}
public void close() {
@@ -31,7 +31,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DecoratorAnimationProducer implements AnimationProducer {
public final class DecoratorAnimationProducer implements AnimationProducer {
@Override
public void init(AnimationHandler handler) {
}
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXSnackbar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
@@ -35,6 +38,7 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
@@ -42,11 +46,12 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.construct.DialogAware;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jetbrains.annotations.Nullable;
@@ -77,11 +82,32 @@ public class DecoratorController {
private final Navigator navigator;
private JFXDialog dialog;
private StackContainerPane dialogPane;
private JFXDialogPane dialogPane;
public DecoratorController(Stage stage, Node mainPage) {
decorator = new Decorator(stage);
decorator.setOnCloseButtonAction(Launcher::stopApplication);
decorator.setOnCloseButtonAction(() -> {
if (AnimationUtils.playWindowAnimation()) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
)
);
timeline.setOnFinished(event -> Launcher.stopApplication());
timeline.play();
} else {
Launcher.stopApplication();
}
});
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
navigator = new Navigator();
@@ -354,7 +380,7 @@ public class DecoratorController {
return;
}
dialog = new JFXDialog();
dialogPane = new StackContainerPane();
dialogPane = new JFXDialogPane();
dialog.setContent(dialogPane);
decorator.capableDraggingWindow(dialog);
@@ -397,18 +423,21 @@ public class DecoratorController {
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
if (dialog != null) {
dialogPane.pop(node);
JFXDialogPane pane = dialogPane;
if (node instanceof DialogAware) {
((DialogAware) node).onDialogClosed();
}
if (dialogPane.getChildren().isEmpty()) {
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
dialog.setOnDialogClosed(e -> pane.pop(node));
dialog.close();
dialog = null;
dialogPane = null;
navigator.setDisable(false);
} else {
pane.pop(node);
}
if (node instanceof DialogAware) {
((DialogAware) node).onDialogClosed();
}
}
}
@@ -48,8 +48,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
private final Stage primaryStage;
private final TransitionPane navBarPane;
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean titleBarTransparent = true;
private double mouseInitX, mouseInitY, stageInitX, stageInitY, stageInitWidth, stageInitHeight;
/**
* Constructor for all SkinBase instances.
@@ -188,17 +187,20 @@ public class DecoratorSkin extends SkinBase<Decorator> {
buttonsContainer.setMaxHeight(40);
{
JFXButton btnHelp = new JFXButton();
btnHelp.setGraphic(SVG.HELP_CIRCLE_OUTLINE.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnHelp.setFocusTraversable(false);
btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1));
btnHelp.getStyleClass().add("jfx-decorator-button");
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
JFXButton btnMin = new JFXButton();
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnMin.setFocusTraversable(false);
btnMin.setGraphic(SVG.MINIMIZE.createIcon(Theme.foregroundFillBinding(), -1));
btnMin.getStyleClass().add("jfx-decorator-button");
btnMin.setOnAction(e -> skinnable.minimize());
JFXButton btnClose = new JFXButton();
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnClose.setFocusTraversable(false);
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
btnClose.getStyleClass().add("jfx-decorator-button");
btnClose.setOnAction(e -> skinnable.close());
@@ -225,7 +227,8 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (canBack) {
JFXButton backNavButton = new JFXButton();
backNavButton.setGraphic(SVG.BACK.createIcon(Theme.foregroundFillBinding(), -1, -1));
backNavButton.setFocusTraversable(false);
backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Theme.foregroundFillBinding(), -1));
backNavButton.getStyleClass().add("jfx-decorator-button");
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
@@ -236,14 +239,15 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (canClose) {
JFXButton closeNavButton = new JFXButton();
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.setFocusTraversable(false);
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
closeNavButton.getStyleClass().add("jfx-decorator-button");
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
if (showCloseAsHome)
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1));
else
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
navLeft.getChildren().add(closeNavButton);
}
@@ -259,7 +263,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
titleLabel.getStyleClass().add("jfx-decorator-title");
if (titleNode == null) {
titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(
() -> skinnable.getWidth() - 100 - navLeft.getWidth(),
() -> skinnable.getWidth() - 150 - navLeft.getWidth(),
skinnable.widthProperty(), navLeft.widthProperty()));
} else {
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
@@ -282,7 +286,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
HBox navRight = new HBox();
navRight.setAlignment(Pos.CENTER_RIGHT);
JFXButton refreshNavButton = new JFXButton();
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1, -1));
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1));
refreshNavButton.getStyleClass().add("jfx-decorator-button");
refreshNavButton.ripplerFillProperty().set(Theme.whiteFill());
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
@@ -299,13 +303,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
return navBar;
}
private void updateInitMouseValues(MouseEvent mouseEvent) {
initX = mouseEvent.getScreenX();
initY = mouseEvent.getScreenY();
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
}
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
return x < root.getWidth() && x >= root.getWidth() - root.snappedLeftInset();
}
@@ -322,146 +319,139 @@ public class DecoratorSkin extends SkinBase<Decorator> {
return x >= 0 && x <= root.snappedLeftInset();
}
private boolean setStageWidth(double width) {
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
primaryStage.setWidth(width);
initX = newX;
return true;
} else {
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
primaryStage.setWidth(titleContainer.getMinWidth());
private void resizeStage(double newWidth, double newHeight) {
if (newWidth < 0)
newWidth = primaryStage.getWidth();
if (newWidth < primaryStage.getMinWidth())
newWidth = primaryStage.getMinWidth();
if (newWidth < titleContainer.getMinWidth())
newWidth = titleContainer.getMinWidth();
return false;
}
if (newHeight < 0)
newHeight = primaryStage.getHeight();
if (newHeight < primaryStage.getMinHeight())
newHeight = primaryStage.getMinHeight();
if (newHeight < titleContainer.getMinHeight())
newHeight = titleContainer.getMinHeight();
// Width and height must be set simultaneously to avoid JDK-8344372 (https://github.com/openjdk/jfx/pull/1654)
primaryStage.setWidth(newWidth);
primaryStage.setHeight(newHeight);
}
private boolean setStageHeight(double height) {
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
primaryStage.setHeight(height);
initY = newY;
return true;
} else {
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
primaryStage.setHeight(titleContainer.getHeight());
return false;
}
}
// ====
protected void onMouseMoved(MouseEvent mouseEvent) {
if (!primaryStage.isFullScreen()) {
updateInitMouseValues(mouseEvent);
if (primaryStage.isResizable()) {
double x = mouseEvent.getX(), y = mouseEvent.getY();
Bounds boundsInParent = root.getBoundsInParent();
double diagonalSize = root.snappedLeftInset() + 10;
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.E_RESIZE);
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else {
root.setCursor(Cursor.W_RESIZE);
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else {
root.setCursor(Cursor.N_RESIZE);
}
} else if (this.isBottomEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.S_RESIZE);
}
private void onMouseMoved(MouseEvent mouseEvent) {
if (!primaryStage.isFullScreen() && primaryStage.isResizable()) {
double x = mouseEvent.getX(), y = mouseEvent.getY();
Bounds boundsInParent = root.getBoundsInParent();
double diagonalSize = root.snappedLeftInset() + 10;
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.DEFAULT);
root.setCursor(Cursor.E_RESIZE);
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else {
root.setCursor(Cursor.W_RESIZE);
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else {
root.setCursor(Cursor.N_RESIZE);
}
} else if (this.isBottomEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.S_RESIZE);
}
} else {
root.setCursor(Cursor.DEFAULT);
}
} else {
root.setCursor(Cursor.DEFAULT);
}
}
protected void onMouseReleased(MouseEvent mouseEvent) {
private void onMouseReleased(MouseEvent mouseEvent) {
getSkinnable().setDragging(false);
}
protected void onMouseDragged(MouseEvent mouseEvent) {
getSkinnable().setDragging(true);
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) {
this.newX = mouseEvent.getScreenX();
this.newY = mouseEvent.getScreenY();
double deltaX = this.newX - this.initX;
double deltaY = this.newY - this.initY;
Cursor cursor = root.getCursor();
if (Cursor.E_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
} else if (Cursor.NE_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
private void onMouseDragged(MouseEvent mouseEvent) {
if (!getSkinnable().isDragging()) {
getSkinnable().setDragging(true);
mouseInitX = mouseEvent.getScreenX();
mouseInitY = mouseEvent.getScreenY();
stageInitX = primaryStage.getX();
stageInitY = primaryStage.getY();
stageInitWidth = primaryStage.getWidth();
stageInitHeight = primaryStage.getHeight();
}
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
} else if (Cursor.SE_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.S_RESIZE == cursor) {
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.W_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
if (primaryStage.isFullScreen() || !mouseEvent.isPrimaryButtonDown() || mouseEvent.isStillSincePress())
return;
mouseEvent.consume();
} else if (Cursor.SW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
double dx = mouseEvent.getScreenX() - mouseInitX;
double dy = mouseEvent.getScreenY() - mouseInitY;
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.NW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
Cursor cursor = root.getCursor();
if (getSkinnable().isAllowMove()) {
if (cursor == Cursor.DEFAULT) {
primaryStage.setX(stageInitX + dx);
primaryStage.setY(stageInitY + dy);
mouseEvent.consume();
}
}
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
if (getSkinnable().isResizable()) {
if (cursor == Cursor.E_RESIZE) {
resizeStage(stageInitWidth + dx, -1);
mouseEvent.consume();
mouseEvent.consume();
} else if (Cursor.N_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
} else if (cursor == Cursor.S_RESIZE) {
resizeStage(-1, stageInitHeight + dy);
mouseEvent.consume();
mouseEvent.consume();
} else if (getSkinnable().isAllowMove()) {
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
mouseEvent.consume();
}
} else if (cursor == Cursor.W_RESIZE) {
resizeStage(stageInitWidth - dx, -1);
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
mouseEvent.consume();
} else if (cursor == Cursor.N_RESIZE) {
resizeStage(-1, stageInitHeight - dy);
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
mouseEvent.consume();
} else if (cursor == Cursor.SE_RESIZE) {
resizeStage(stageInitWidth + dx, stageInitHeight + dy);
mouseEvent.consume();
} else if (cursor == Cursor.SW_RESIZE) {
resizeStage(stageInitWidth - dx, stageInitHeight + dy);
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
mouseEvent.consume();
} else if (cursor == Cursor.NW_RESIZE) {
resizeStage(stageInitWidth - dx, stageInitHeight - dy);
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
mouseEvent.consume();
} else if (cursor == Cursor.NE_RESIZE) {
resizeStage(stageInitWidth + dx, stageInitHeight - dy);
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
mouseEvent.consume();
}
}
}
@@ -60,7 +60,6 @@ import java.util.concurrent.CancellationException;
import java.util.function.Supplier;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
@@ -100,43 +99,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
{
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("game"));
item.setLeftGraphic(wrap(SVG.GAMEPAD));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(newGameTab));
item.setOnAction(e -> tab.select(newGameTab));
})
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("modpack"));
settingsItem.setLeftGraphic(wrap(SVG.PACK));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
settingsItem.setOnAction(e -> tab.select(modpackTab));
})
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("mods"));
item.setLeftGraphic(wrap(SVG.PUZZLE));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab));
item.setOnAction(e -> tab.select(modTab));
})
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("resourcepack"));
item.setLeftGraphic(wrap(SVG.TEXTURE_BOX));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab));
item.setOnAction(e -> tab.select(resourcePackTab));
})
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("world"));
item.setLeftGraphic(wrap(SVG.EARTH));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab));
item.setOnAction(e -> selectTabIfCurseForgeAvailable(worldTab));
});
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
}
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.STADIA_CONTROLLER)
.addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACKAGE2)
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.EXTENSION)
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE)
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
setCenter(transitionPane);
}
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static javafx.beans.binding.Bindings.createBooleanBinding;
@@ -121,8 +122,25 @@ public class InstallersPage extends Control implements WizardPage {
}
protected void onInstall() {
controller.getSettings().put("name", txtName.getText());
controller.onFinish();
String name = txtName.getText();
// Check for non-ASCII characters.
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
Controllers.dialog(new MessageDialogPane.Builder(
i18n("install.name.invalid"),
i18n("message.warning"),
MessageDialogPane.MessageType.QUESTION)
.yesOrNo(() -> {
controller.getSettings().put("name", name);
controller.onFinish();
}, () -> {
// The user selects Cancel and does nothing.
})
.build());
} else {
controller.getSettings().put("name", name);
controller.onFinish();
}
}
@Override
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
@@ -149,10 +150,27 @@ public final class LocalModpackPage extends ModpackPage {
}
protected void onInstall() {
if (!txtModpackName.validate()) return;
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.onFinish();
String name = txtModpackName.getText();
// Check for non-ASCII characters.
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
Controllers.dialog(new MessageDialogPane.Builder(
i18n("install.name.invalid"),
i18n("message.warning"),
MessageDialogPane.MessageType.QUESTION)
.yesOrNo(() -> {
controller.getSettings().put(MODPACK_NAME, name);
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.onFinish();
}, () -> {
// The user selects Cancel and does nothing.
})
.build());
} else {
controller.getSettings().put(MODPACK_NAME, name);
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.onFinish();
}
}
protected void onDescribe() {
@@ -99,7 +99,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
graphic.setLeft(new TwoLineListItem(i18n("modpack.choose." + type), i18n("modpack.choose." + type + ".detail")));
SVGPath arrow = new SVGPath();
arrow.setContent(SVG.ARROW_RIGHT.getPath());
arrow.setContent(SVG.ARROW_FORWARD.getPath());
BorderPane.setAlignment(arrow, Pos.CENTER);
graphic.setRight(arrow);
@@ -21,12 +21,20 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXSpinner;
import com.jfoenix.controls.JFXTextField;
import javafx.animation.PauseTransition;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.util.Duration;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
@@ -53,15 +61,21 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
@@ -78,14 +92,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private final JFXCheckBox chkRelease;
private final JFXCheckBox chkSnapshot;
private final JFXCheckBox chkOld;
private final JFXButton btnRefresh;
private final HBox checkPane;
private final ComponentList centrePane;
private final StackPane center;
private final VersionList<?> versionList;
private CompletableFuture<?> executor;
private final HBox searchBar;
private final StringProperty queryString = new SimpleStringProperty();
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.title = title;
this.gameVersion = gameVersion;
@@ -100,6 +115,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
this.setTop(hintPane);
root = new TransitionPane();
BorderPane toolbarPane = new BorderPane();
JFXButton btnRefresh;
{
spinner = new JFXSpinner();
@@ -109,7 +126,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
centrePane = new ComponentList();
centrePane.getStyleClass().add("no-padding");
{
checkPane = new HBox();
HBox checkPane = new HBox();
checkPane.setSpacing(10);
{
chkRelease = new JFXCheckBox(i18n("version.game.releases"));
@@ -122,21 +139,69 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
chkOld = new JFXCheckBox(i18n("version.game.old"));
HBox.setMargin(chkOld, new Insets(10, 0, 10, 0));
HBox pane = new HBox();
HBox.setHgrow(pane, Priority.ALWAYS);
btnRefresh = new JFXButton(i18n("button.refresh"));
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
btnRefresh.setOnAction(e -> onRefresh());
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld, pane, btnRefresh);
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld);
}
list = new JFXListView<>();
list.getStyleClass().add("jfx-list-view-float");
VBox.setVgrow(list, Priority.ALWAYS);
centrePane.getContent().setAll(checkPane, list);
TransitionPane rightToolbarPane = new TransitionPane();
{
HBox refreshPane = new HBox();
refreshPane.setAlignment(Pos.CENTER_RIGHT);
btnRefresh = new JFXButton(i18n("button.refresh"));
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
btnRefresh.setOnAction(e -> onRefresh());
JFXButton btnSearch = new JFXButton(i18n("search"));
btnSearch.getStyleClass().add("jfx-tool-bar-button");
btnSearch.setGraphic(wrap(SVG.SEARCH.createIcon(Theme.blackFill(), -1)));
searchBar = new HBox();
{
searchBar.setAlignment(Pos.CENTER);
searchBar.setPadding(new Insets(0, 5, 0, 0));
JFXTextField searchField = new JFXTextField();
searchField.setPromptText(i18n("search"));
HBox.setHgrow(searchField, Priority.ALWAYS);
JFXButton closeSearchBar = new JFXButton();
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1)));
closeSearchBar.setOnAction(e -> {
searchField.clear();
rightToolbarPane.setContent(refreshPane, ContainerAnimations.FADE);
});
onEscPressed(searchField, closeSearchBar::fire);
PauseTransition pause = new PauseTransition(Duration.millis(100));
pause.setOnFinished(e -> queryString.set(searchField.getText()));
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
pause.setRate(1);
pause.playFromStart();
});
searchBar.getChildren().setAll(searchField, closeSearchBar);
btnSearch.setOnAction(e -> {
rightToolbarPane.setContent(searchBar, ContainerAnimations.FADE);
searchField.requestFocus();
});
}
refreshPane.getChildren().setAll(new HBox(btnSearch, btnRefresh));
rightToolbarPane.setContent(refreshPane, ContainerAnimations.NONE);
}
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
toolbarPane.setLeft(checkPane);
toolbarPane.setRight(rightToolbarPane);
centrePane.getContent().setAll(toolbarPane, list);
}
center.getChildren().setAll(centrePane);
@@ -163,19 +228,50 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
this.setCenter(root);
versionList = downloadProvider.getVersionListById(libraryId);
if (versionList.hasType()) {
centrePane.getContent().setAll(checkPane, list);
boolean hasType = versionList.hasType();
chkRelease.setManaged(hasType);
chkRelease.setVisible(hasType);
chkSnapshot.setManaged(hasType);
chkSnapshot.setVisible(hasType);
chkOld.setManaged(hasType);
chkOld.setVisible(hasType);
if (hasType) {
centrePane.getContent().setAll(toolbarPane, list);
} else {
centrePane.getContent().setAll(list);
}
ComponentList.setVgrow(list, Priority.ALWAYS);
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
InvalidationListener listener = o -> {
List<RemoteVersion> versions = loadVersions();
String query = queryString.get();
if (!StringUtils.isBlank(query)) {
Predicate<RemoteVersion> predicate;
if (query.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(query.substring("regex:".length()));
predicate = it -> pattern.matcher(it.getSelfVersion()).find();
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
} else {
String lowerQueryString = query.toLowerCase(Locale.ROOT);
predicate = it -> it.getSelfVersion().toLowerCase(Locale.ROOT).contains(lowerQueryString);
}
versions = versions.stream().filter(predicate).collect(Collectors.toList());
}
list.getItems().setAll(versions);
};
chkRelease.selectedProperty().addListener(listener);
chkSnapshot.selectedProperty().addListener(listener);
chkOld.selectedProperty().addListener(listener);
queryString.addListener(listener);
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)));
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1)));
Holder<RemoteVersionListCell> lastCell = new Holder<>();
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, libraryId));
@@ -259,7 +355,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
refresh();
}
private void onBack() { navigation.onPrev(true); }
private void onBack() {
navigation.onPrev(true);
}
private void onSponsor() {
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
@@ -275,7 +373,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell, String libraryId) {
this.lastCell = lastCell;
if ("game".equals(libraryId)) {
content.getExternalLinkButton().setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
content.getExternalLinkButton().setGraphic(SVG.GLOBE_BOOK.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(content.getExternalLinkButton(), i18n("wiki.tooltip"));
}
@@ -301,7 +399,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
content.setTitle(remoteVersion.getSelfVersion());
if (remoteVersion.getReleaseDate() != null) {
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
content.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
} else {
content.setSubtitle(null);
}
@@ -192,7 +192,8 @@ public final class ExportWizardProvider implements WizardProvider {
/* overrideJavaArgs */ true,
/* overrideConsole */ true,
/* overrideCommands */ true,
/* overrideWindow */ true
/* overrideWindow */ true,
/* iconKey */ null // TODO
), modpackFile);
}
@@ -72,7 +72,7 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
graphic.setLeft(new TwoLineListItem(i18n("modpack.type." + type), i18n("modpack.type." + type + ".export")));
SVGPath arrow = new SVGPath();
arrow.setContent(SVG.ARROW_RIGHT.getPath());
arrow.setContent(SVG.ARROW_FORWARD.getPath());
BorderPane.setAlignment(arrow, Pos.CENTER);
graphic.setRight(arrow);
@@ -216,6 +216,7 @@ public final class JavaDownloadDialog extends StackPane {
distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));
FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {
ObservableList<DiscoJavaRemoteVersion> versions;
if (packageType == null
|| currentJavaVersionList.get() == null
@@ -224,8 +225,26 @@ public final class JavaDownloadDialog extends StackPane {
return;
}
DiscoJavaRemoteVersion oldVersion = remoteVersionBox.getSelectionModel().getSelectedItem();
remoteVersionBox.setItems(versions);
if (oldVersion != null) {
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (Objects.equals(version.getDistributionVersion(), oldVersion.getDistributionVersion())) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.getJdkVersion() == oldVersion.getJdkVersion()) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
}
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
@@ -181,6 +181,7 @@ public final class JavaInstallPage extends WizardSinglePage {
pane.setLeft(new Label(name));
Label valueLabel = FXUtils.truncatedLabel(value, 60);
FXUtils.copyOnDoubleClick(valueLabel);
BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT);
pane.setCenter(valueLabel);
@@ -262,7 +262,7 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
{
JFXButton revealButton = new JFXButton();
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
revealButton.setOnAction(e -> control.onReveal());
FXUtils.installFastTooltip(revealButton, i18n("java.reveal"));
@@ -275,12 +275,12 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
null
));
if (java.isManaged()) {
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(removeButton, i18n("java.uninstall"));
if (JavaRuntime.CURRENT_JAVA != null && java.getBinary().equals(JavaRuntime.CURRENT_JAVA.getBinary()))
removeButton.setDisable(true);
} else {
removeButton.setGraphic(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(removeButton, i18n("java.disable"));
}
@@ -307,11 +307,11 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
res.add(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, JavaManager::refresh));
if (skinnable.onInstallJava != null) {
res.add(createToolbarButton2(i18n("java.download"), SVG.DOWNLOAD_OUTLINE, skinnable.onInstallJava));
res.add(createToolbarButton2(i18n("java.download"), SVG.DOWNLOAD, skinnable.onInstallJava));
}
res.add(createToolbarButton2(i18n("java.add"), SVG.PLUS, skinnable::onAddJava));
res.add(createToolbarButton2(i18n("java.add"), SVG.ADD, skinnable::onAddJava));
JFXButton disableJava = createToolbarButton2(i18n("java.disabled.management"), SVG.VIEW_LIST, skinnable::onShowRestoreJavaPage);
JFXButton disableJava = createToolbarButton2(i18n("java.disabled.management"), SVG.FORMAT_LIST_BULLETED, skinnable::onShowRestoreJavaPage);
disableJava.disableProperty().bind(Bindings.isEmpty(ConfigHolder.globalConfig().getDisabledJava()));
res.add(disableJava);
@@ -160,7 +160,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
{
JFXButton revealButton = new JFXButton();
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
revealButton.setOnAction(e -> skinnable.onReveal());
FXUtils.installFastTooltip(revealButton, i18n("java.reveal"));
@@ -169,7 +169,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
JFXButton removeButton = new JFXButton();
removeButton.getStyleClass().add("toggle-icon4");
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
removeButton.setOnAction(e -> skinnable.onRemove());
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
@@ -177,7 +177,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
} else {
JFXButton restoreButton = new JFXButton();
restoreButton.getStyleClass().add("toggle-icon4");
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
restoreButton.setOnAction(e -> skinnable.onRestore());
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
@@ -63,67 +62,25 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
tab.select(gameTab);
gameTab.initializeIfNeeded();
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
{
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("settings.type.global.manage"));
settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
settingsItem.setOnAction(e -> tab.select(gameTab));
})
.addNavigationDrawerItem(javaItem -> {
javaItem.setTitle(i18n("java.management"));
javaItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
javaItem.setOnAction(e -> tab.select(javaManagementTab));
})
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("settings.launcher.general"));
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab));
settingsItem.setOnAction(e -> tab.select(settingsTab));
})
.addNavigationDrawerItem(personalizationItem -> {
personalizationItem.setTitle(i18n("settings.launcher.appearance"));
personalizationItem.setLeftGraphic(wrap(SVG.STYLE_OUTLINE));
personalizationItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab));
personalizationItem.setOnAction(e -> tab.select(personalizationTab));
})
.addNavigationDrawerItem(downloadItem -> {
downloadItem.setTitle(i18n("download"));
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
downloadItem.setOnAction(e -> tab.select(downloadTab));
})
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(helpItem -> {
helpItem.setTitle(i18n("help"));
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
helpItem.setOnAction(e -> tab.select(helpTab));
})
.addNavigationDrawerItem(feedbackItem -> {
feedbackItem.setTitle(i18n("feedback"));
feedbackItem.setLeftGraphic(wrap(SVG.MESSAGE_ALERT_OUTLINE));
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
feedbackItem.setOnAction(e -> tab.select(feedbackTab));
})
.addNavigationDrawerItem(aboutItem -> {
aboutItem.setTitle(i18n("about"));
aboutItem.setLeftGraphic(wrap(SVG.INFORMATION_OUTLINE));
aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab));
aboutItem.setOnAction(e -> tab.select(aboutTab));
});
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
}
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER)
.addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.LOCAL_CAFE)
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, settingsTab, i18n("settings.launcher.general"), SVG.TUNE)
.addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE)
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD)
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP)
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.FEEDBACK)
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFO);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
setCenter(transitionPane);
}
@@ -28,13 +28,16 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.TextFlow;
import javafx.util.Duration;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.Version;
@@ -47,7 +50,6 @@ import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.AnnouncementCard;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
@@ -102,23 +104,49 @@ public final class MainPage extends StackPane implements DecoratorPage {
setPadding(new Insets(20));
if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) {
AnnouncementCard announcementCard = null;
String title;
String content;
if (Metadata.isNightly()) {
announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null);
} else if (Metadata.isDev()) {
announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane);
title = i18n("update.channel.nightly.title");
content = i18n("update.channel.nightly.hint");
} else {
title = i18n("update.channel.dev.title");
content = i18n("update.channel.dev.hint");
}
if (announcementCard != null) {
VBox announcementBox = new VBox(16);
announcementBox.getChildren().add(announcementCard);
VBox announcementCard = new VBox();
announcementPane = new TransitionPane();
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
BorderPane titleBar = new BorderPane();
titleBar.getStyleClass().add("title");
titleBar.setLeft(new Label(title));
getChildren().add(announcementPane);
}
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20);
hideNode.setCursor(Cursor.HAND);
titleBar.setRight(hideNode);
FXUtils.onClicked(hideNode, () -> {
if (announcementPane != null) {
if (Metadata.isDev()) {
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
}
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
}
});
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
body.setLineSpacing(4);
announcementCard.getChildren().setAll(titleBar, body);
announcementCard.setSpacing(16);
announcementCard.getStyleClass().addAll("card", "announcement");
VBox announcementBox = new VBox(16);
announcementBox.getChildren().add(announcementCard);
announcementPane = new TransitionPane();
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
getChildren().add(announcementPane);
}
updatePane = new StackPane();
@@ -138,7 +166,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));
{
Label lblIcon = new Label();
lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20, 20));
lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20));
TwoLineListItem prompt = new TwoLineListItem();
prompt.setSubtitle(i18n("update.bubble.subtitle"));
@@ -150,7 +178,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
JFXButton closeUpdateButton = new JFXButton();
closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10, 10));
closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10));
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
StackPane.setMargin(closeUpdateButton, new Insets(5));
@@ -219,10 +247,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
menuButton.setOnAction(e -> onMenu());
menuButton.setClip(new Rectangle(211, -100, 100, 200));
StackPane graphic = new StackPane();
Node svg = SVG.TRIANGLE.createIcon(Theme.foregroundFillBinding(), 10, 10);
Node svg = SVG.ARROW_DROP_UP.createIcon(Theme.foregroundFillBinding(), 30);
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
graphic.getChildren().setAll(svg);
graphic.setTranslateX(12);
graphic.setTranslateX(6);
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
menuButton.setGraphic(graphic);
@@ -297,13 +325,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
showUpdate.set(false);
}
public void hideAnnouncementPane() {
if (announcementPane != null) {
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
}
}
@Override
public ReadOnlyObjectWrapper<State> stateProperty() {
return state;
@@ -204,7 +204,7 @@ public class PersonalizationPage extends StackPane {
JFXButton clearButton = new JFXButton();
clearButton.getStyleClass().add("toggle-icon4");
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1, -1));
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
clearButton.setOnAction(e -> config().setLauncherFontFamily(null));
hBox.getChildren().setAll(cboFont, clearButton);
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
if (mainPage == null) {
MainPage mainPage = new MainPage();
FXUtils.applyDragListener(mainPage,
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file),
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
modpacks -> {
File file = modpacks.get(0);
if (ModpackHelper.isFileModpackByExtension(file)) {
Controllers.getDecorator().startWizard(
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
i18n("install.modpack"));
} else if (NBTHelper.isNBTFileByExtension(file)) {
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
try {
Controllers.navigate(new NBTEditorPage(file));
Controllers.navigate(new NBTEditorPage(file.toPath()));
} catch (Throwable e) {
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
@@ -154,14 +154,14 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
// third item in left sidebar
AdvancedListItem gameItem = new AdvancedListItem();
gameItem.setLeftGraphic(wrap(SVG.VIEW_LIST));
gameItem.setLeftGraphic(wrap(SVG.FORMAT_LIST_BULLETED));
gameItem.setActionButtonVisible(false);
gameItem.setTitle(i18n("version.manage"));
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
// forth item in left sidebar
AdvancedListItem downloadItem = new AdvancedListItem();
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD));
downloadItem.setActionButtonVisible(false);
downloadItem.setTitle(i18n("download"));
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
@@ -169,7 +169,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
// fifth item in left sidebar
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
launcherSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
launcherSettingsItem.setActionButtonVisible(false);
launcherSettingsItem.setTitle(i18n("settings"));
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
@@ -123,7 +123,7 @@ public abstract class SettingsView extends StackPane {
btnUpdate = new JFXButton();
btnUpdate.setOnAction(e -> onUpdate());
btnUpdate.getStyleClass().add("toggle-icon4");
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20, 20));
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20));
updatePane.setHeaderRight(btnUpdate);
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.jfoenix.controls.JFXButton;
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.*;
import java.util.concurrent.CompletableFuture;
import java.nio.file.Path;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTEditorPage extends BorderPane implements DecoratorPage {
/**
* @author Glavo
*/
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state;
private final File file;
private final Path file;
private final NBTFileType type;
public NBTEditorPage(File file) throws IOException {
private final BorderPane root = new BorderPane();
public NBTEditorPage(Path file) throws IOException {
getStyleClass().add("gray-background");
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath())));
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
this.file = file;
this.type = NBTFileType.ofFile(file);
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
throw new IOException("Unknown type of file " + file);
}
setCenter(new ProgressIndicator());
setContent(root);
setLoading(true);
HBox actions = new HBox(8);
actions.setPadding(new Insets(8));
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
actions.getChildren().setAll(saveButton, cancelButton);
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file)))
.thenAcceptAsync(tree -> {
setCenter(new NBTTreeView(tree));
// setBottom(actions);
}, Schedulers.javafx())
.handleAsync((result, e) -> {
if (e != null) {
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
Task.supplyAsync(() -> type.readAsTree(file))
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
setLoading(false);
root.setCenter(new NBTTreeView(result));
} else {
LOG.warning("Fail to open nbt file", exception);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
}
return null;
}, Schedulers.javafx());
}).start();
}
public void save() throws IOException {
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.NBTIO;
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* @author Glavo
*/
public enum NBTFileType {
COMPRESSED("dat", "dat_old") {
@Override
public Tag read(File file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) {
public Tag read(Path file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
fileInputStream.mark(3);
byte[] header = new byte[3];
if (fileInputStream.read(header) < 3) {
@@ -42,25 +64,28 @@ public enum NBTFileType {
},
ANVIL("mca") {
@Override
public Tag read(File file) throws IOException {
public Tag read(Path file) throws IOException {
return REGION.read(file);
}
@Override
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
return REGION.readAsTree(file);
}
},
REGION("mcr") {
@Override
public Tag read(File file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file, "r")) {
public Tag read(Path file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
if (r.length() == 0) {
return tag;
}
byte[] header = new byte[4096];
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
Inflater inflater = new Inflater();
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
r.readFully(header);
for (int i = 0; i < 4096; i += 4) {
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
@@ -109,7 +134,7 @@ public enum NBTFileType {
}
@Override
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
for (Tag tag : ((ListTag) item.getValue())) {
@@ -135,7 +160,11 @@ public enum NBTFileType {
static final NBTFileType[] types = values();
public static NBTFileType ofFile(File file) {
public static boolean isNBTFileByExtension(Path file) {
return NBTFileType.ofFile(file) != null;
}
public static NBTFileType ofFile(Path file) {
String ext = FileUtils.getExtension(file);
for (NBTFileType type : types) {
for (String extension : type.extensions) {
@@ -153,11 +182,11 @@ public enum NBTFileType {
this.extensions = extensions;
}
public abstract Tag read(File file) throws IOException;
public abstract Tag read(Path file) throws IOException;
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
root.setName(file.getName());
root.setName(file.getFileName().toString());
return root;
}
}
@@ -1,12 +0,0 @@
package org.jackhuang.hmcl.ui.nbt;
import java.io.File;
public final class NBTHelper {
private NBTHelper() {
}
public static boolean isNBTFileByExtension(File file) {
return NBTFileType.ofFile(file) != null;
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.Tag;
@@ -6,6 +23,9 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author Glavo
*/
public enum NBTTagType {
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
@@ -14,7 +31,10 @@ import java.util.EnumMap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTTreeView extends TreeView<Tag> {
/**
* @author Glavo
*/
public final class NBTTreeView extends TreeView<Tag> {
public NBTTreeView(NBTTreeView.Item tree) {
this.setRoot(tree);
@@ -48,7 +48,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true));
Node left = VersionPage.wrap(SVG.FOLDER_OUTLINE);
Node left = VersionPage.wrap(SVG.FOLDER);
root.setLeft(left);
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
@@ -64,7 +64,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
btnRemove.setOnAction(e -> skinnable.remove());
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
right.getChildren().add(btnRemove);
root.setRight(right);
@@ -240,7 +240,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
FXUtils.unbindEnum(cboRenderer);
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
@@ -27,6 +27,8 @@ import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SkinBase;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
@@ -38,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -57,7 +60,7 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
toolbar.setPickOnBounds(false);
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh));
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.PLUS, skinnable::add));
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.ADD, skinnable::add));
toolbar.getChildren().add(createToolbarButton(i18n("button.remove"), SVG.DELETE, () -> {
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
@@ -110,6 +113,9 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Bindings.bindContent(listView.getItems(), skinnable.getItems());
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
center.setContent(listView);
root.setCenter(center);
}
@@ -36,6 +36,8 @@ import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.mod.RemoteMod;
@@ -64,6 +66,7 @@ import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
@@ -92,11 +95,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
private Runnable retrySearch;
public DownloadListPage(RemoteModRepository repository) {
this(repository, null);
}
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback) {
this(repository, callback, false);
this(repository, null, false);
}
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback, boolean versionSelection) {
@@ -493,6 +492,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem();
Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback));
});
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
listView.setCellFactory(x -> new FloatListCell<RemoteMod>(listView) {
TwoLineListItem content = new TwoLineListItem();
ImageView imageView = new ImageView();
@@ -55,9 +55,6 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -308,7 +305,7 @@ public class DownloadPage extends Control implements DecoratorPage {
ComponentList sublist = new ComponentList(() -> {
ArrayList<ModItem> items = new ArrayList<>(versions.size());
for (RemoteMod.Version v: versions) {
for (RemoteMod.Version v : versions) {
items.add(new ModItem(v, control));
}
return items;
@@ -388,20 +385,20 @@ public class DownloadPage extends Control implements DecoratorPage {
TwoLineListItem content = new TwoLineListItem();
HBox.setHgrow(content, Priority.ALWAYS);
content.setTitle(dataItem.getName());
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
content.setSubtitle(I18n.formatDateTime(dataItem.getDatePublished()));
switch (dataItem.getVersionType()) {
case Alpha:
content.getTags().add(i18n("mods.channel.alpha"));
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(Theme.blackFill(), 24));
break;
case Beta:
content.getTags().add(i18n("mods.channel.beta"));
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(Theme.blackFill(), 24));
break;
case Release:
content.getTags().add(i18n("mods.channel.release"));
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(Theme.blackFill(), 24));
break;
}
@@ -442,7 +439,9 @@ public class DownloadPage extends Control implements DecoratorPage {
private static final class ModVersion extends JFXDialogLayout {
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
this.setHeading(new HBox(new Label(i18n("mods.download.title", version.getName()))));
boolean isModpack = selfPage.repository.getType() == RemoteModRepository.Type.MODPACK;
this.setHeading(new HBox(new Label(i18n(isModpack ? "modpack.download.title" : "mods.download.title", version.getName()))));
VBox box = new VBox(8);
box.setPadding(new Insets(8));
@@ -464,10 +463,10 @@ public class DownloadPage extends Control implements DecoratorPage {
this.setBody(box);
JFXButton downloadButton = new JFXButton(i18n("mods.install"));
JFXButton downloadButton = new JFXButton(isModpack ? i18n("install.modpack") : i18n("mods.install"));
downloadButton.getStyleClass().add("dialog-accept");
downloadButton.setOnAction(e -> {
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
if (isModpack || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(version);
@@ -528,8 +527,6 @@ public class DownloadPage extends Control implements DecoratorPage {
}
}
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
public interface DownloadCallback {
void download(Profile profile, @Nullable String version, RemoteMod.Version file);
}
@@ -44,17 +44,17 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
JFXPopup popup = new JFXPopup(menu);
menu.getContent().setAll(
new IconedMenuItem(SVG.ROCKET_LAUNCH_OUTLINE, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup),
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup),
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), () -> currentSkinnable.generateLaunchScript(), popup),
new MenuSeparator(),
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup),
new IconedMenuItem(SVG.SETTINGS, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup),
new MenuSeparator(),
new IconedMenuItem(SVG.PENCIL_OUTLINE, i18n("version.manage.rename"), () -> currentSkinnable.rename(), popup),
new IconedMenuItem(SVG.COPY, i18n("version.manage.duplicate"), () -> currentSkinnable.duplicate(), popup),
new IconedMenuItem(SVG.DELETE_OUTLINE, i18n("version.manage.remove"), () -> currentSkinnable.remove(), popup),
new IconedMenuItem(SVG.EXPORT, i18n("modpack.export"), () -> currentSkinnable.export(), popup),
new IconedMenuItem(SVG.EDIT, i18n("version.manage.rename"), () -> currentSkinnable.rename(), popup),
new IconedMenuItem(SVG.FOLDER_COPY, i18n("version.manage.duplicate"), () -> currentSkinnable.duplicate(), popup),
new IconedMenuItem(SVG.DELETE, i18n("version.manage.remove"), () -> currentSkinnable.remove(), popup),
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), () -> currentSkinnable.export(), popup),
new MenuSeparator(),
new IconedMenuItem(SVG.FOLDER_OUTLINE, i18n("folder.game"), () -> currentSkinnable.browse(), popup));
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.game"), () -> currentSkinnable.browse(), popup));
return popup;
});
@@ -80,7 +80,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
JFXButton btnUpgrade = new JFXButton();
btnUpgrade.setOnAction(e -> skinnable.update());
btnUpgrade.getStyleClass().add("toggle-icon4");
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
right.getChildren().add(btnUpgrade);
}
@@ -90,7 +90,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
btnLaunch.setOnAction(e -> skinnable.launch());
btnLaunch.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
right.getChildren().add(btnLaunch);
}
@@ -103,7 +103,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
});
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
btnManage.setGraphic(FXUtils.limitingSize(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), 24, 24), 24, 24));
btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
right.getChildren().add(btnManage);
}
@@ -73,7 +73,7 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
addProfileItem.getStyleClass().add("navigation-drawer-item");
addProfileItem.setTitle(i18n("profile.new"));
addProfileItem.setActionButtonVisible(false);
addProfileItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
addProfileItem.setLeftGraphic(VersionPage.wrap(SVG.ADD_CIRCLE));
addProfileItem.setOnAction(e -> Controllers.navigate(new ProfilePage(null)));
pane.setFitToWidth(true);
@@ -87,26 +87,10 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
}
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
.addNavigationDrawerItem(installNewGameItem -> {
installNewGameItem.setTitle(i18n("install.new_game"));
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
installNewGameItem.setOnAction(e -> Versions.addNewGame());
})
.addNavigationDrawerItem(installModpackItem -> {
installModpackItem.setTitle(i18n("install.modpack"));
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK));
installModpackItem.setOnAction(e -> Versions.importModpack());
})
.addNavigationDrawerItem(refreshItem -> {
refreshItem.setTitle(i18n("button.refresh"));
refreshItem.setLeftGraphic(VersionPage.wrap(SVG.REFRESH));
refreshItem.setOnAction(e -> gameList.refreshList());
})
.addNavigationDrawerItem(globalManageItem -> {
globalManageItem.setTitle(i18n("settings.type.global.manage"));
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE));
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
});
.addNavigationDrawerItem(i18n("install.new_game"), SVG.ADD_CIRCLE, Versions::addNewGame)
.addNavigationDrawerItem(i18n("install.modpack"), SVG.PACKAGE2, Versions::importModpack)
.addNavigationDrawerItem(i18n("button.refresh"), SVG.REFRESH, gameList::refreshList)
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.SETTINGS, this::modifyGlobalGameSettings);
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
setLeft(pane, bottomLeftCornerList);
}
@@ -172,7 +172,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
@Override
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
return Collections.singletonList(
createToolbarButton2(i18n("install.installer.install_offline"), SVG.PLUS, skinnable::installOffline)
createToolbarButton2(i18n("install.installer.install_offline"), SVG.ADD, skinnable::installOffline)
);
}
}
@@ -31,6 +31,8 @@ import javafx.scene.control.SelectionMode;
import javafx.scene.control.SkinBase;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
@@ -70,6 +72,7 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -131,16 +134,18 @@ class ModListPageSkin extends SkinBase<ModListPage> {
Bindings.bindContent(listView.getItems(), getSkinnable().getItems());
});
onEscPressed(searchField, closeSearchBar::fire);
searchBar.getChildren().setAll(searchField, closeSearchBar);
// Toolbar Normal
toolbarNormal.getChildren().setAll(
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
createToolbarButton2(i18n("mods.add"), SVG.PLUS, skinnable::add),
createToolbarButton2(i18n("mods.add"), SVG.ADD, skinnable::add),
createToolbarButton2(i18n("folder.mod"), SVG.FOLDER_OPEN, skinnable::openModFolder),
createToolbarButton2(i18n("mods.check_updates"), SVG.UPDATE, skinnable::checkUpdates),
createToolbarButton2(i18n("download"), SVG.DOWNLOAD_OUTLINE, skinnable::download),
createToolbarButton2(i18n("search"), SVG.MAGNIFY, () -> changeToolbar(searchBar))
createToolbarButton2(i18n("download"), SVG.DOWNLOAD, skinnable::download),
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar))
);
// Toolbar Selecting
@@ -168,6 +173,16 @@ class ModListPageSkin extends SkinBase<ModListPage> {
changeToolbar(toolbarSelecting);
});
root.getContent().add(toolbarPane);
// Clear selection when pressing ESC
root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
if (listView.getSelectionModel().getSelectedItem() != null) {
listView.getSelectionModel().clearSelection();
e.consume();
}
}
});
}
{
@@ -194,6 +209,10 @@ class ModListPageSkin extends SkinBase<ModListPage> {
}
});
// ListViewBehavior would consume ESC pressed event, preventing us from handling it
// So we ignore it here
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
center.setContent(listView);
root.getContent().add(center);
}
@@ -271,7 +290,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
StringBuilder message = new StringBuilder(localModFile.getFileName());
if (isNotBlank(localModFile.getGameVersion()))
message.append(", ").append(i18n("game.version")).append(": ").append(localModFile.getGameVersion());
message.append(", ").append(i18n("mods.game.version")).append(": ").append(localModFile.getGameVersion());
if (isNotBlank(localModFile.getAuthors()))
message.append(", ").append(i18n("archive.author")).append(": ").append(localModFile.getAuthors());
this.message = message.toString();
@@ -379,6 +398,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
setHeading(titleContainer);
Label description = new Label(modInfo.getModInfo().getDescription().toString());
FXUtils.copyOnDoubleClick(description);
setBody(description);
if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {
@@ -500,15 +520,15 @@ class ModListPageSkin extends SkinBase<ModListPage> {
setSelectable();
restoreButton.getStyleClass().add("toggle-icon4");
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(Theme.blackFill(), 24), 24, 24));
infoButton.getStyleClass().add("toggle-icon4");
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFORMATION_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(Theme.blackFill(), 24), 24, 24));
container.getChildren().setAll(checkBox, content, restoreButton, revealButton, infoButton);
@@ -90,7 +90,7 @@ public class VersionIconDialog extends DialogPane {
}
private Node createCustomIcon() {
Node shape = SVG.PLUS_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 32, 32);
Node shape = SVG.ADD_CIRCLE.createIcon(Theme.blackFill(), 32);
shape.setMouseTransparent(true);
RipplerContainer container = new RipplerContainer(shape);
FXUtils.setLimitWidth(container, 36);
@@ -66,7 +66,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
private String preferredVersionName = null;
{
public VersionPage() {
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
@@ -77,7 +77,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
tab.select(versionSettingsTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
@@ -241,7 +242,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem versionSettingsItem = new AdvancedListItem();
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
versionSettingsItem.setTitle(i18n("settings.game"));
versionSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
versionSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
versionSettingsItem.setActionButtonVisible(false);
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab));
@@ -249,7 +250,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem installerListItem = new AdvancedListItem();
installerListItem.getStyleClass().add("navigation-drawer-item");
installerListItem.setTitle(i18n("settings.tabs.installers"));
installerListItem.setLeftGraphic(wrap(SVG.CUBE));
installerListItem.setLeftGraphic(wrap(SVG.DEPLOYED_CODE));
installerListItem.setActionButtonVisible(false);
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
installerListItem.setOnAction(e -> control.tab.select(control.installerListTab));
@@ -257,7 +258,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem modListItem = new AdvancedListItem();
modListItem.getStyleClass().add("navigation-drawer-item");
modListItem.setTitle(i18n("mods.manage"));
modListItem.setLeftGraphic(wrap(SVG.PUZZLE));
modListItem.setLeftGraphic(wrap(SVG.EXTENSION));
modListItem.setActionButtonVisible(false);
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
modListItem.setOnAction(e -> control.tab.select(control.modListTab));
@@ -265,7 +266,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem worldListItem = new AdvancedListItem();
worldListItem.getStyleClass().add("navigation-drawer-item");
worldListItem.setTitle(i18n("world.manage"));
worldListItem.setLeftGraphic(wrap(SVG.EARTH));
worldListItem.setLeftGraphic(wrap(SVG.PUBLIC));
worldListItem.setActionButtonVisible(false);
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
worldListItem.setOnAction(e -> control.tab.select(control.worldListTab));
@@ -280,26 +281,26 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
PopupMenu browseList = new PopupMenu();
JFXPopup browsePopup = new JFXPopup(browseList);
browseList.getContent().setAll(
new IconedMenuItem(SVG.GAMEPAD, i18n("folder.game"), () -> control.onBrowse(""), browsePopup),
new IconedMenuItem(SVG.PUZZLE, i18n("folder.mod"), () -> control.onBrowse("mods"), browsePopup),
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
new IconedMenuItem(SVG.STADIA_CONTROLLER, i18n("folder.game"), () -> control.onBrowse(""), browsePopup),
new IconedMenuItem(SVG.EXTENSION, i18n("folder.mod"), () -> control.onBrowse("mods"), browsePopup),
new IconedMenuItem(SVG.SETTINGS, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
new IconedMenuItem(SVG.TEXTURE, i18n("folder.resourcepacks"), () -> control.onBrowse("resourcepacks"), browsePopup),
new IconedMenuItem(SVG.APPLICATION_OUTLINE, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
new IconedMenuItem(SVG.MONITOR_SCREENSHOT, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
new IconedMenuItem(SVG.EARTH, i18n("folder.saves"), () -> control.onBrowse("saves"), browsePopup),
new IconedMenuItem(SVG.WB_SUNNY, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
new IconedMenuItem(SVG.SCREENSHOT_MONITOR, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
new IconedMenuItem(SVG.PUBLIC, i18n("folder.saves"), () -> control.onBrowse("saves"), browsePopup),
new IconedMenuItem(SVG.SCRIPT, i18n("folder.logs"), () -> control.onBrowse("logs"), browsePopup)
);
PopupMenu managementList = new PopupMenu();
JFXPopup managementPopup = new JFXPopup(managementList);
managementList.getContent().setAll(
new IconedMenuItem(SVG.ROCKET_LAUNCH_OUTLINE, i18n("version.launch.test"), control::testGame, managementPopup),
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), control::testGame, managementPopup),
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), control::generateLaunchScript, managementPopup),
new MenuSeparator(),
new IconedMenuItem(SVG.PENCIL, i18n("version.manage.rename"), control::rename, managementPopup),
new IconedMenuItem(SVG.COPY, i18n("version.manage.duplicate"), control::duplicate, managementPopup),
new IconedMenuItem(SVG.DELETE_OUTLINE, i18n("version.manage.remove"), control::remove, managementPopup),
new IconedMenuItem(SVG.EXPORT, i18n("modpack.export"), control::export, managementPopup),
new IconedMenuItem(SVG.EDIT, i18n("version.manage.rename"), control::rename, managementPopup),
new IconedMenuItem(SVG.FOLDER_COPY, i18n("version.manage.duplicate"), control::duplicate, managementPopup),
new IconedMenuItem(SVG.DELETE, i18n("version.manage.remove"), control::remove, managementPopup),
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), control::export, managementPopup),
new MenuSeparator(),
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), control::redownloadAssetIndex, managementPopup),
new IconedMenuItem(null, i18n("version.manage.remove_assets"), control::clearAssets, managementPopup),
@@ -308,25 +309,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
);
AdvancedListBox toolbar = new AdvancedListBox()
.addNavigationDrawerItem(upgradeItem -> {
upgradeItem.setTitle(i18n("version.update"));
upgradeItem.setLeftGraphic(wrap(SVG.UPDATE));
.addNavigationDrawerItem(i18n("version.update"), SVG.UPDATE, control::updateGame, upgradeItem -> {
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
upgradeItem.setOnAction(e -> control.updateGame());
})
.addNavigationDrawerItem(testGameItem -> {
testGameItem.setTitle(i18n("version.launch.test"));
testGameItem.setLeftGraphic(wrap(SVG.ROCKET_LAUNCH_OUTLINE));
testGameItem.setOnAction(e -> control.testGame());
})
.addNavigationDrawerItem(browseMenuItem -> {
browseMenuItem.setTitle(i18n("settings.game.exploration"));
browseMenuItem.setLeftGraphic(wrap(SVG.FOLDER_OUTLINE));
.addNavigationDrawerItem(i18n("version.launch.test"), SVG.ROCKET_LAUNCH, control::testGame)
.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OPEN, null, browseMenuItem -> {
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
})
.addNavigationDrawerItem(managementItem -> {
managementItem.setTitle(i18n("settings.game.management"));
managementItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
.addNavigationDrawerItem(i18n("settings.game.management"), SVG.MENU, null, managementItem -> {
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
});
toolbar.getStyleClass().add("advanced-list-box-clear-padding");
@@ -356,7 +346,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
}
public static Node wrap(SVG svg) {
return wrap(svg.createIcon((Paint) null, 20, 20));
return wrap(svg.createIcon((Paint) null, 20));
}
public interface VersionLoadable {
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.java.JavaRuntime;
@@ -65,6 +66,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
private static final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
private static TimerTask memoryStatusUpdateTask;
private static void initMemoryStatusUpdateTask() {
FXUtils.checkFxUserThread();
if (memoryStatusUpdateTask != null)
@@ -135,10 +137,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
rootPane.getStyleClass().add("card-list");
if (globalSetting) {
HintPane skinHint = new HintPane(MessageDialogPane.MessageType.INFO);
skinHint.setText(i18n("settings.skin"));
rootPane.getChildren().add(skinHint);
HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING);
Text text = new Text();
text.textProperty().bind(BindingMapping.of(selectedVersion)
@@ -190,6 +188,31 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
componentList = new ComponentList();
componentList.setDepth(1);
if (!globalSetting) {
BorderPane copyGlobalPane = new BorderPane();
{
Label label = new Label(i18n("settings.game.copy_global"));
copyGlobalPane.setLeft(label);
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
JFXButton copyAll = new JFXButton(i18n("settings.game.copy_global.copy_all"));
copyAll.disableProperty().bind(modpack);
copyGlobalPane.setRight(copyAll);
copyAll.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
Set<String> ignored = new HashSet<>(Arrays.asList(
"usesGlobal",
"versionIcon"
));
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
}, null));
copyAll.getStyleClass().add("jfx-button-border");
BorderPane.setAlignment(copyAll, Pos.CENTER_RIGHT);
}
componentList.getContent().add(copyGlobalPane);
}
javaItem = new MultiFileItem<>();
javaSublist = new ComponentSublist();
javaSublist.getContent().add(javaItem);
@@ -446,7 +469,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
showAdvancedSettingPane.setRight(button);
}
componentList.getContent().setAll(
componentList.getContent().addAll(
javaSublist,
gameDirSublist,
maxMemoryPane,
@@ -528,11 +551,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty());
gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty());
FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty());
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
FXUtils.unbindEnum(cboLauncherVisibility);
FXUtils.unbindEnum(cboProcessPriority);
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
@@ -563,8 +586,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());
gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty());
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
@@ -0,0 +1,88 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Task;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author Glavo
*/
public final class WorldBackupTask extends Task<Path> {
private final World world;
private final Path backupsDir;
public WorldBackupTask(World world, Path backupsDir) {
this.world = world;
this.backupsDir = backupsDir;
}
@Override
public void execute() throws Exception {
try (FileChannel lockChannel = world.lock()) {
Files.createDirectories(backupsDir);
String time = LocalDateTime.now().format(WorldBackupsPage.TIME_FORMATTER);
String baseName = time + "_" + world.getFileName();
Path backupFile = null;
OutputStream outputStream = null;
int count;
for (count = 0; count < 256; count++) {
try {
backupFile = backupsDir.resolve(baseName + (count == 0 ? "" : " " + count) + ".zip").toAbsolutePath();
outputStream = Files.newOutputStream(backupFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
break;
} catch (FileAlreadyExistsException ignored) {
}
}
if (outputStream == null)
throw new IOException("Too many attempts");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(outputStream))) {
String rootName = world.getFileName();
Path rootDir = this.world.getFile();
Files.walkFileTree(this.world.getFile(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (path.endsWith("session.lock")) {
return FileVisitResult.CONTINUE;
}
zipOutputStream.putNextEntry(new ZipEntry(rootName + "/" + rootDir.relativize(path).toString().replace('\\', '/')));
Files.copy(path, zipOutputStream);
zipOutputStream.closeEntry();
return FileVisitResult.CONTINUE;
}
});
}
setResult(backupFile);
}
}
}
@@ -0,0 +1,281 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.game.WorldLockedException;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
*/
public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.BackupInfo> implements DecoratorPage {
static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
private final World world;
private final Path backupsDir;
private final Pattern backupFileNamePattern;
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
public WorldBackupsPage(World world, Path backupsDir) {
this.backupsDir = backupsDir;
this.world = world;
this.backupFileNamePattern = Pattern.compile("(?<datetime>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_" + Pattern.quote(world.getFileName()) + "( (?<count>[0-9]+))?\\.zip");
this.state.set(State.fromTitle(i18n("world.backup.title", world.getWorldName())));
loadBackups();
}
@Override
public ReadOnlyObjectProperty<State> stateProperty() {
return state;
}
@Override
public void refresh() {
loadBackups();
}
private void loadBackups() {
setLoading(true);
Task.supplyAsync(() -> {
if (Files.isDirectory(backupsDir)) {
try (Stream<Path> paths = Files.list(backupsDir)) {
ArrayList<BackupInfo> result = new ArrayList<>();
paths.forEach(path -> {
if (Files.isRegularFile(path)) {
try {
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
if (matcher.matches()) {
LocalDateTime time = LocalDateTime.parse(matcher.group("datetime"), TIME_FORMATTER);
int count = 0;
if (matcher.group("count") != null) {
count = Integer.parseInt(matcher.group("count"));
}
result.add(new BackupInfo(path, new World(path), time, count));
}
} catch (Throwable e) {
LOG.warning("Failed to load backup file " + path, e);
}
}
});
result.sort(Comparator.naturalOrder());
return result;
}
} else {
return new ArrayList<BackupInfo>();
}
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
this.setLoading(false);
if (exception == null) {
this.setItems(FXCollections.observableList(result));
} else {
LOG.warning("Failed to load backups", exception);
}
}).start();
}
@Override
protected Skin<?> createDefaultSkin() {
return new WorldBackupsPageSkin();
}
void createBackup() {
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup")).thenApplyAsync(path -> {
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
if (!matcher.matches()) {
throw new AssertionError("Wrong backup file name" + path);
}
LocalDateTime time = LocalDateTime.parse(matcher.group("datetime"), TIME_FORMATTER);
int count = 0;
if (matcher.group("count") != null) {
count = Integer.parseInt(matcher.group("count"));
}
return Pair.pair(path, new BackupInfo(world.getFile(), new World(path), time, count));
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
WorldBackupsPage.this.getItems().add(result.getValue());
WorldBackupsPage.this.getItems().sort(Comparator.naturalOrder());
Controllers.dialog(i18n("world.backup.create.success", result.getKey()), null, MessageDialogPane.MessageType.INFO);
} else if (exception instanceof WorldLockedException) {
Controllers.dialog(i18n("world.backup.create.locked"), null, MessageDialogPane.MessageType.WARNING);
} else {
LOG.warning("Failed to create backup", exception);
Controllers.dialog(i18n("world.backup.create.failed", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);
}
}), i18n("world.backup"), null);
}
private final class WorldBackupsPageSkin extends ToolbarListPageSkin<WorldBackupsPage> {
WorldBackupsPageSkin() {
super(WorldBackupsPage.this);
}
@Override
protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup"), SVG.ARCHIVE, skinnable::createBackup));
}
}
public final class BackupInfo extends Control implements Comparable<BackupInfo> {
private final Path file;
private final World backupWorld;
private final LocalDateTime backupTime;
private final int count;
public BackupInfo(Path file, World backupWorld, LocalDateTime backupTime, int count) {
this.file = file;
this.backupWorld = backupWorld;
this.backupTime = backupTime;
this.count = count;
}
public World getBackupWorld() {
return backupWorld;
}
public LocalDateTime getBackupTime() {
return backupTime;
}
@Override
protected Skin<?> createDefaultSkin() {
return new BackupInfoSkin(this);
}
void onReveal() {
FXUtils.showFileInExplorer(file);
}
void onDelete() {
WorldBackupsPage.this.getItems().remove(this);
Task.runAsync(() -> Files.delete(file)).start();
}
@Override
public int compareTo(@NotNull WorldBackupsPage.BackupInfo that) {
int c = this.backupTime.compareTo(that.backupTime);
return c != 0 ? c : Integer.compare(this.count, that.count);
}
}
private static final class BackupInfoSkin extends SkinBase<BackupInfo> {
BackupInfoSkin(BackupInfo skinnable) {
super(skinnable);
World world = skinnable.getBackupWorld();
BorderPane root = new BorderPane();
root.getStyleClass().add("md-list-cell");
root.setPadding(new Insets(8));
{
StackPane left = new StackPane();
root.setLeft(left);
left.setPadding(new Insets(0, 8, 0, 0));
ImageView imageView = new ImageView();
left.getChildren().add(imageView);
FXUtils.limitSize(imageView, 32, 32);
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
}
{
TwoLineListItem item = new TwoLineListItem();
root.setCenter(item);
if (skinnable.getBackupWorld().getWorldName() != null)
item.setTitle(parseColorEscapes(skinnable.getBackupWorld().getWorldName()));
item.setSubtitle(formatDateTime(skinnable.getBackupTime()) + (skinnable.count == 0 ? "" : " (" + skinnable.count + ")"));
if (world.getGameVersion() != null) item.getTags().add(world.getGameVersion());
}
{
HBox right = new HBox(8);
root.setRight(right);
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnReveal = new JFXButton();
right.getChildren().add(btnReveal);
FXUtils.installFastTooltip(btnReveal, i18n("world.reveal"));
btnReveal.getStyleClass().add("toggle-icon4");
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
btnReveal.setOnAction(event -> skinnable.onReveal());
JFXButton btnDelete = new JFXButton();
right.getChildren().add(btnDelete);
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
btnDelete.getStyleClass().add("toggle-icon4");
btnDelete.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
}
getChildren().setAll(new RipplerContainer(root));
}
}
}
@@ -27,22 +27,19 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.DoubleValidator;
import org.jackhuang.hmcl.ui.construct.NumberValidator;
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.time.Instant;
import java.util.Arrays;
@@ -55,30 +52,68 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author Glavo
*/
public final class WorldInfoPage extends StackPane implements DecoratorPage {
public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
private final World world;
private CompoundTag levelDat;
private final ObjectProperty<State> stateProperty;
private FileChannel sessionLockChannel;
@Override
public boolean back() {
closePage();
return true;
}
@Override
public void closePage() {
if (sessionLockChannel != null) {
try {
sessionLockChannel.close();
} catch (IOException e) {
LOG.warning("Failed to close session lock channel", e);
}
sessionLockChannel = null;
}
}
public WorldInfoPage(World world) {
this.world = world;
this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName())));
this.getChildren().add(new ProgressIndicator());
Task.supplyAsync(world::readLevelDat)
this.setLoading(true);
Task.supplyAsync(this::loadWorldInfo)
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
this.levelDat = result;
loadWorldInfo();
updateControls();
setLoading(false);
} else {
LOG.warning("Failed to load level.dat", exception);
this.getChildren().setAll(new Label(i18n("world.info.failed")));
setFailedReason(i18n("world.info.failed"));
}
})).start();
}
private void loadWorldInfo() {
private CompoundTag loadWorldInfo() throws IOException {
if (!Files.isDirectory(world.getFile()))
throw new IOException("Not a valid world directory");
try {
sessionLockChannel = world.lock();
} catch (IOException ignored) {
}
return world.readLevelDat();
}
private boolean isReadOnly() {
return sessionLockChannel == null;
}
private void updateControls() {
CompoundTag dataTag = levelDat.get("Data");
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
@@ -86,7 +121,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
getChildren().setAll(scrollPane);
setContent(scrollPane);
VBox rootPane = new VBox();
rootPane.setFillWidth(true);
@@ -103,6 +138,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
Label worldNameLabel = new Label();
FXUtils.copyOnDoubleClick(worldNameLabel);
worldNameLabel.setText(world.getWorldName());
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
worldNamePane.setRight(worldNameLabel);
@@ -115,6 +151,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
gameVersionPane.setLeft(label);
Label gameVersionLabel = new Label();
FXUtils.copyOnDoubleClick(gameVersionLabel);
gameVersionLabel.setText(world.getGameVersion());
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
gameVersionPane.setRight(gameVersionLabel);
@@ -127,6 +164,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
randomSeedPane.setLeft(label);
Label randomSeedLabel = new Label();
FXUtils.copyOnDoubleClick(randomSeedLabel);
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
randomSeedPane.setRight(randomSeedLabel);
@@ -143,6 +181,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
lastPlayedPane.setLeft(label);
Label lastPlayedLabel = new Label();
FXUtils.copyOnDoubleClick(lastPlayedLabel);
lastPlayedLabel.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
lastPlayedPane.setRight(lastPlayedLabel);
@@ -155,6 +194,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
timePane.setLeft(label);
Label timeLabel = new Label();
FXUtils.copyOnDoubleClick(timeLabel);
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
timePane.setRight(timeLabel);
@@ -168,6 +208,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
OptionToggleButton allowCheatsButton = new OptionToggleButton();
{
allowCheatsButton.setTitle(i18n("world.info.allow_cheats"));
allowCheatsButton.setDisable(isReadOnly());
Tag tag = dataTag.get("allowCommands");
if (tag instanceof ByteTag) {
@@ -190,6 +231,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
OptionToggleButton generateFeaturesButton = new OptionToggleButton();
{
generateFeaturesButton.setTitle(i18n("world.info.generate_features"));
generateFeaturesButton.setDisable(isReadOnly());
Tag tag = worldGenSettings != null ? worldGenSettings.get("generate_features") : dataTag.get("MapFeatures");
if (tag instanceof ByteTag) {
@@ -216,6 +258,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
difficultyPane.setLeft(label);
JFXComboBox<Difficulty> difficultyBox = new JFXComboBox<>(Difficulty.items);
difficultyBox.setDisable(isReadOnly());
BorderPane.setAlignment(difficultyBox, Pos.CENTER_RIGHT);
difficultyPane.setRight(difficultyBox);
@@ -258,6 +301,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
locationPane.setLeft(label);
Label locationLabel = new Label();
FXUtils.copyOnDoubleClick(locationLabel);
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
locationPane.setRight(locationLabel);
@@ -276,6 +320,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
lastDeathLocationPane.setLeft(label);
Label lastDeathLocationLabel = new Label();
FXUtils.copyOnDoubleClick(lastDeathLocationLabel);
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
lastDeathLocationPane.setRight(lastDeathLocationLabel);
@@ -297,6 +342,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
spawnPane.setLeft(label);
Label spawnLabel = new Label();
FXUtils.copyOnDoubleClick(spawnLabel);
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
spawnPane.setRight(spawnLabel);
@@ -318,6 +364,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
playerGameTypePane.setLeft(label);
JFXComboBox<GameType> gameTypeBox = new JFXComboBox<>(GameType.items);
gameTypeBox.setDisable(isReadOnly());
BorderPane.setAlignment(gameTypeBox, Pos.CENTER_RIGHT);
playerGameTypePane.setRight(gameTypeBox);
@@ -348,6 +395,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
healthPane.setLeft(label);
JFXTextField healthField = new JFXTextField();
healthField.setDisable(isReadOnly());
healthField.setPrefWidth(50);
healthField.setAlignment(Pos.CENTER_RIGHT);
BorderPane.setAlignment(healthField, Pos.CENTER_RIGHT);
@@ -381,6 +429,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
foodLevelPane.setLeft(label);
JFXTextField foodLevelField = new JFXTextField();
foodLevelField.setDisable(isReadOnly());
foodLevelField.setPrefWidth(50);
foodLevelField.setAlignment(Pos.CENTER_RIGHT);
BorderPane.setAlignment(foodLevelField, Pos.CENTER_RIGHT);
@@ -414,6 +463,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
xpLevelPane.setLeft(label);
JFXTextField xpLevelField = new JFXTextField();
xpLevelField.setDisable(isReadOnly());
xpLevelField.setPrefWidth(50);
xpLevelField.setAlignment(Pos.CENTER_RIGHT);
BorderPane.setAlignment(xpLevelField, Pos.CENTER_RIGHT);
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,13 +17,8 @@
*/
package org.jackhuang.hmcl.ui.versions;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
@@ -32,26 +27,17 @@ import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.time.Instant;
import java.nio.file.Path;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItem extends Control {
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
public final class WorldListItem extends Control {
private final World world;
private final Path backupsDir;
public WorldListItem(World world) {
public WorldListItem(World world, Path backupsDir) {
this.world = world;
title.set(parseColorEscapes(world.getWorldName()));
subtitle.set(i18n("world.description", world.getFileName(), formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
FXUtils.onClicked(this, this::showInfo);
this.backupsDir = backupsDir;
}
@Override
@@ -59,16 +45,8 @@ public class WorldListItem extends Control {
return new WorldListItemSkin(this);
}
public StringProperty titleProperty() {
return title;
}
public StringProperty subtitleProperty() {
return subtitle;
}
public ObjectProperty<Image> imageProperty() {
return image;
public World getWorld() {
return world;
}
public void export() {
@@ -85,11 +63,7 @@ public class WorldListItem extends Control {
}
public void reveal() {
try {
FXUtils.openFolder(world.getFile().toFile());
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}
FXUtils.openFolder(world.getFile().toFile());
}
public void manageDatapacks() {
@@ -104,4 +78,8 @@ public class WorldListItem extends Control {
public void showInfo() {
Controllers.navigate(new WorldInfoPage(world));
}
public void showBackupPage() {
Controllers.navigate(new WorldBackupsPage(world, backupsDir));
}
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.SkinBase;
@@ -26,66 +25,96 @@ import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import java.time.Instant;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItemSkin extends SkinBase<WorldListItem> {
public final class WorldListItemSkin extends SkinBase<WorldListItem> {
public WorldListItemSkin(WorldListItem skinnable) {
super(skinnable);
World world = skinnable.getWorld();
BorderPane root = new BorderPane();
HBox center = new HBox();
center.setMouseTransparent(true);
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
StackPane imageViewContainer = new StackPane();
FXUtils.setLimitWidth(imageViewContainer, 32);
FXUtils.setLimitHeight(imageViewContainer, 32);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
imageViewContainer.getChildren().setAll(imageView);
TwoLineListItem item = new TwoLineListItem();
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
root.setCenter(center);
PopupMenu menu = new PopupMenu();
JFXPopup popup = new JFXPopup(menu);
menu.getContent().setAll(
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("world.datapack"), skinnable::manageDatapacks, popup),
new IconedMenuItem(SVG.EXPORT, i18n("world.export"), skinnable::export, popup),
new IconedMenuItem(SVG.FOLDER_OUTLINE, i18n("world.reveal"), skinnable::reveal, popup));
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnManage = new JFXButton();
btnManage.setOnAction(e -> popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
btnManage.setGraphic(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), -1, -1));
right.getChildren().add(btnManage);
root.setRight(right);
root.getStyleClass().add("md-list-cell");
root.setPadding(new Insets(8));
{
StackPane left = new StackPane();
FXUtils.installSlowTooltip(left, world.getFile().toString());
root.setLeft(left);
left.setPadding(new Insets(0, 8, 0, 0));
ImageView imageView = new ImageView();
left.getChildren().add(imageView);
FXUtils.limitSize(imageView, 32, 32);
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
}
{
TwoLineListItem item = new TwoLineListItem();
root.setCenter(item);
if (world.getWorldName() != null)
item.setTitle(parseColorEscapes(world.getWorldName()));
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
if (world.getGameVersion() != null)
item.getTags().add(world.getGameVersion());
if (world.isLocked())
item.getTags().add(i18n("world.locked"));
}
{
HBox right = new HBox(8);
root.setRight(right);
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnReveal = new JFXButton();
right.getChildren().add(btnReveal);
FXUtils.installFastTooltip(btnReveal, i18n("world.reveal"));
btnReveal.getStyleClass().add("toggle-icon4");
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
btnReveal.setOnAction(event -> skinnable.reveal());
JFXButton btnExport = new JFXButton();
right.getChildren().add(btnExport);
FXUtils.installFastTooltip(btnExport, i18n("world.export"));
btnExport.getStyleClass().add("toggle-icon4");
btnExport.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
btnExport.setOnAction(event -> skinnable.export());
JFXButton btnBackup = new JFXButton();
right.getChildren().add(btnBackup);
FXUtils.installFastTooltip(btnBackup, i18n("world.backup"));
btnBackup.getStyleClass().add("toggle-icon4");
btnBackup.setGraphic(SVG.ARCHIVE.createIcon(Theme.blackFill(), -1));
btnBackup.setOnAction(event -> skinnable.showBackupPage());
JFXButton btnDatapack = new JFXButton();
right.getChildren().add(btnDatapack);
FXUtils.installFastTooltip(btnDatapack, i18n("world.datapack"));
btnDatapack.getStyleClass().add("toggle-icon4");
btnDatapack.setGraphic(SVG.EXTENSION.createIcon(Theme.blackFill(), -1));
btnDatapack.setOnAction(event -> skinnable.manageDatapacks());
JFXButton btnInfo = new JFXButton();
right.getChildren().add(btnInfo);
FXUtils.installFastTooltip(btnInfo, i18n("world.info"));
btnInfo.getStyleClass().add("toggle-icon4");
btnInfo.setGraphic(SVG.INFO.createIcon(Theme.blackFill(), -1));
btnInfo.setOnAction(event -> skinnable.showInfo());
}
getChildren().setAll(new RipplerContainer(root));
}
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXCheckBox;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Node;
@@ -37,17 +36,17 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
private Path savesDir;
private Path backupsDir;
private List<World> worlds;
private Profile profile;
private String id;
@@ -62,7 +61,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
if (worlds != null)
itemsProperty().setAll(worlds.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(WorldListItem::new).collect(Collectors.toList()));
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
});
}
@@ -71,36 +70,39 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
return new WorldListPageSkin();
}
@Override
public void loadVersion(Profile profile, String id) {
this.profile = profile;
this.id = id;
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
this.savesDir = profile.getRepository().getSavesDirectory(id);
this.backupsDir = profile.getRepository().getBackupsDirectory(id);
refresh();
}
public CompletableFuture<?> refresh() {
public void refresh() {
if (profile == null || id == null)
return CompletableFuture.completedFuture(null);
return;
setLoading(true);
return CompletableFuture
.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
.thenApplyAsync(unused -> {
try (Stream<World> stream = World.getWorlds(savesDir)) {
return stream.parallel().collect(Collectors.toList());
}
})
.whenCompleteAsync((result, exception) -> {
.whenComplete(Schedulers.javafx(), (result, exception) -> {
worlds = result;
setLoading(false);
if (exception == null)
itemsProperty().setAll(result.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(WorldListItem::new).collect(Collectors.toList()));
if (exception == null) {
itemsProperty().setAll(result.stream().filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
} else {
LOG.warning("Failed to load world list page", exception);
}
// https://github.com/HMCL-dev/HMCL/issues/938
System.gc();
}, Platform::runLater);
}).start();
}
public void add() {
@@ -126,7 +128,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.runAsync(() -> world.install(savesDir, name))
.whenComplete(Schedulers.javafx(), () -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)), backupsDir));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)
@@ -155,7 +157,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
this.showAll.set(showAll);
}
private class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
private final class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
WorldListPageSkin() {
super(WorldListPage.this);
@@ -169,8 +171,8 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
return Arrays.asList(chkShowAll,
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
createToolbarButton2(i18n("world.add"), SVG.PLUS, skinnable::add),
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD_OUTLINE, skinnable::download));
createToolbarButton2(i18n("world.add"), SVG.ADD, skinnable::add),
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD, skinnable::download));
}
}
}
@@ -34,8 +34,8 @@ public final class Lazy<T> {
}
public T get() {
if (value == null) {
value = Objects.requireNonNull(supplier.get());
if (supplier != null) {
value = supplier.get();
supplier = null;
}
return value;
@@ -18,6 +18,8 @@
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
@@ -60,7 +62,11 @@ public final class NativePatcher {
});
}
public static Version patchNative(Version version, String gameVersion, JavaRuntime javaVersion, VersionSetting settings) {
public static Version patchNative(DefaultGameRepository repository,
Version version, String gameVersion,
JavaRuntime javaVersion,
VersionSetting settings,
List<String> javaArguments) {
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
return version;
@@ -83,7 +89,7 @@ public final class NativePatcher {
final boolean useNativeOpenAL = settings.isUseNativeOpenAL();
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
&& GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
&& gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
version = version.setLibraries(version.getLibraries().stream()
.filter(library -> {
@@ -124,6 +130,7 @@ public final class NativePatcher {
return version;
}
boolean lwjglVersionChanged = false;
ArrayList<Library> newLibraries = new ArrayList<>();
for (Library library : version.getLibraries()) {
if (!library.appliesToCurrentEnvironment())
@@ -145,14 +152,33 @@ public final class NativePatcher {
} else if (replacement != null) {
LOG.info("Replace " + library.getName() + " with " + replacement.getName());
newLibraries.add(replacement);
if ("org.lwjgl:lwjgl".equals(library.getName()) && !Objects.equals(library.getVersion(), replacement.getVersion())) {
lwjglVersionChanged = true;
}
}
}
}
if (lwjglVersionChanged) {
ModManager modManager = repository.getModManager(version.getId());
try {
for (LocalModFile mod : modManager.getMods()) {
if ("sodium".equals(mod.getId())) {
// https://github.com/CaffeineMC/sodium/issues/2561
javaArguments.add("-Dsodium.checks.issue2561=false");
break;
}
}
} catch (Throwable e) {
LOG.warning("Failed to get mods", e);
}
}
return version.setLibraries(newLibraries);
}
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
public static Library getMesaLoader(JavaRuntime javaVersion) {
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
}
}
@@ -19,9 +19,9 @@ package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -74,13 +74,13 @@ public final class I18n {
}
}
public static String formatDateTime(Instant instant) {
public static String formatDateTime(TemporalAccessor time) {
DateTimeFormatter formatter = dateTimeFormatter;
if (formatter == null) {
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format")).withZone(ZoneId.systemDefault());
}
return formatter.format(instant);
return formatter.format(time);
}
public static boolean hasKey(String key) {
+66 -30
View File
@@ -33,6 +33,8 @@ case "$(uname -m)" in
_HMCL_ARCH="arm64";;
arm|arm32|aarch32|armv7*)
_HMCL_ARCH="arm32";;
riscv64)
_HMCL_ARCH="riscv64";;
loongarch64)
_HMCL_ARCH="loongarch64";;
*)
@@ -56,17 +58,42 @@ else
_HMCL_VM_OPTIONS="-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"
fi
function show_warning_console() {
echo -e "\033[1;31m$1\033[0m" >&2
}
function show_warning_dialog() {
if [ -n "$3" ]; then
if [ -n "$(command -v xdg-open)" ]; then
if [ -n "$(command -v zenity)" ]; then
zenity --question --title="$1" --text="$2" && xdg-open "$3"
elif [ -n "$(command -v kdialog)" ]; then
kdialog --title "$1" --warningyesno "$2" && xdg-open "$3"
fi
fi
else
if [ -n "$(command -v zenity)" ]; then
zenity --info --title="$1" --text="$2"
elif [ -n "$(command -v kdialog)" ]; then
kdialog --title "$1" --msgbox "$2"
fi
fi
}
function show_warning() {
show_warning_console "$1: $2"
show_warning_dialog "$1" "$2"
}
# First, find Java in HMCL_JAVA_HOME
if [ -n "${HMCL_JAVA_HOME+x}" ]; then
if [ -x "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" ]; then
exec "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
else
if [ "$_HMCL_USE_CHINESE" == true ]; then
echo "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。" 1>&2
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
show_warning "错误" "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
else
echo "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path." 1>&2
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
show_warning "Error" "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path.\nYou can visit the https://docs.hmcl.net/help.html page for help."
fi
exit 1
fi
@@ -97,6 +124,11 @@ case "$_HMCL_ARCH" in
exec "$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
;;
riscv64)
if [ -x "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
exec "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
;;
loongarch64)
if [ -x "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
exec "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
@@ -114,37 +146,41 @@ if [ -x "$(command -v $_HMCL_JAVA_EXE_NAME)" ]; then
exec $_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
# Java not found
if [ "$_HMCL_OS" == "osx" ]; then
_HMCL_DOWNLOAD_PAGE_OS="macos"
else
_HMCL_DOWNLOAD_PAGE_OS="$_HMCL_OS"
if [ "$_HMCL_OS" == "freebsd" ] && [ -x "$(command -v javavm)" ]; then
exec javavm $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
# Java not found
case "$_HMCL_OS-$_HMCL_ARCH" in
windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-loongarch64|macos-x86_64|macos-arm64)
if [ "$_HMCL_USE_CHINESE" == true ]; then
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
else
echo "The Java runtime environment is required to run HMCL. " 1>&2
echo "Please install Java and set the environment variables and try again." 1>&2
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
fi
windows-x86|windows-x86_64|windows-arm64)
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/windows/$_HMCL_ARCH.html"
;;
*)
if [ "$_HMCL_USE_CHINESE" == true ]; then
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
else
echo "The Java runtime environment is required to run HMCL. " 1>&2
echo "Please install Java and set the environment variables and try again." 1>&2
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
fi
linux-x86|linux-x86_64|linux-arm64|linux-arm32|linux-riscv64|linux-loongarch64)
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/linux/$_HMCL_ARCH.html"
;;
osx-x86_64|osx-arm64)
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/macos/$_HMCL_ARCH.html"
;;
esac
if [ "$_HMCL_USE_CHINESE" == true ]; then
_HMCL_WARNING_MESSAGE="运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。"
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
show_warning_console "错误: $_HMCL_WARNING_MESSAGE\n你可以前往此处下载:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
show_warning_dialog "错误" "$_HMCL_WARNING_MESSAGE\n\n是否要前往 Java 下载页面?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
else
show_warning "错误" "$_HMCL_WARNING_MESSAGE\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
fi
else
_HMCL_WARNING_MESSAGE="The Java runtime environment is required to run HMCL.\nPlease install Java and set the environment variables and try again."
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
show_warning_console "Error: $_HMCL_WARNING_MESSAGE\nYou can download it from here:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
show_warning_dialog "Error" "$_HMCL_WARNING_MESSAGE\n\nDo you want to go to the Java download page?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
else
show_warning "Error" "$_HMCL_WARNING_MESSAGE\nYou can visit the https://docs.hmcl.net/help.html page for help."
fi
fi
exit 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
about=About
about.copyright=Copyright
about.copyright.statement=Copyright © 2024 huangyuhui
about.copyright.statement=Copyright © 2025 huangyuhui
about.author=Author
about.author.statement=bilibili @huanghongxun
about.claim=EULA
@@ -66,17 +66,17 @@ account.failed.injector_download_failure=Failed to download the authlib-injector
account.failed.invalid_credentials=Incorrect password or rate limited. Please try again later.
account.failed.invalid_password=Invalid password.
account.failed.invalid_token=Please try logging in again.
account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in to your migrated Microsoft account again.
account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in using your migrated Microsoft account again.
account.failed.no_character=There are no characters linked to this account.
account.failed.server_disconnected=Failed to connect to the authentication server. You can log in using offline mode or try logging in again.\n\
If you try several times and still fail, please try logging in to the account again.
account.failed.server_response_malformed=Invalid server response. The authentication server may not be working.
If the issue persists after multiple attempts, please try logging in to the account again.
account.failed.server_response_malformed=Invalid server response. The authentication server may be down.
account.failed.ssl=An SSL error occurred while connecting to the server. Please try updating your Java.
account.failed.wrong_account=You have logged in to the wrong account.
account.hmcl.hint=You need to click "Login" and complete the process in the opened browser window.
account.hmcl.hint=You need to click "Log in" and complete the process in the opened browser window.
account.injector.add=New Auth Server
account.injector.empty=None (You can click "+" to add one)
account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone between your connections will be able to see your credentials in cleartext.
account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone intercepting your connection would be able to see your credentials in plaintext.
account.injector.link.homepage=Homepage
account.injector.link.register=Register
account.injector.server=Authentication Server
@@ -95,16 +95,16 @@ account.copy_uuid=Copy UUID of the Account
account.methods=Login Type
account.methods.authlib_injector=authlib-injector
account.methods.microsoft=Microsoft
account.methods.microsoft.birth=How to Change Your Account Birthday
account.methods.microsoft.birth=How to Change Your Account Birth Date
account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\
\n\
There are some extra works for us, but you can safely close this tab for now.
account.methods.microsoft.deauthorize=Deauthorize
account.methods.microsoft.error.add_family=An adult must add you to a family in order for you to play Minecraft because you are not yet 18 years old.
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birthday" to learn how to change it.
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birth Date" to learn how to change it.
account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click "Create Profile / Edit Profile Name" to create one before continuing.
account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created\nplease click "Create Profile / Edit Profile Name" to create it.
account.methods.microsoft.error.no_character=Please ensure you have purchased Minecraft: Java Edition.\nIf you have already purchased the game, a profile may not have been created yet.\nClick "Create Profile / Edit Profile Name" to create it.
account.methods.microsoft.error.unknown=Failed to log in, error code: %d.
account.methods.microsoft.error.wrong_verify_method=Please log in using your password on the Microsoft account login page, and do not use a verification code to log in.
account.methods.microsoft.logging_in=Logging in...
@@ -113,26 +113,27 @@ account.methods.microsoft.hint=Please click "Log in" and copy the code displayed
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
account.methods.microsoft.manual=Your device code is <b>%1$s</b>. Please click here to copy.\n\
\n\
After clicking "Log in", you should complete the login process in the opened browser window. If it did not show, you can navigate to %2$s manually.\n\
After clicking "Log in", you should complete the login process in the opened browser window. If that does not show up, you can navigate to %2$s manually.\n\
\n\
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
account.methods.microsoft.profile=Account Profile
account.methods.microsoft.purchase=Buy Minecraft
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login.
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
account.methods.microsoft.snapshot.website=Official Website
account.methods.offline=Offline
account.methods.offline.name.special_characters=Recommended to use English letters, numbers, and underscores
account.methods.offline.name.invalid=Generally, game usernames only allow English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
account.methods.offline.name.invalid.tip=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.
account.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\n\
\n\
\ · Some legitimate usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Some illegal usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\ · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Illegal: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\n\
If you believe that there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
If you believe there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
account.methods.offline.uuid=UUID
account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Editing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\
account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\
\n\
This option is for advanced users only. We do not recommend editing this option unless you know what you are doing.
This option is for advanced users only. We do not recommend changing this option unless you know what you are doing.
account.methods.offline.uuid.malformed=Invalid format.
account.methods.forgot_password=Forgot Password
account.missing=No Accounts
@@ -141,18 +142,20 @@ account.move_to_global=Convert to Global Account\nThe account information will b
account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL.
account.not_logged_in=Not Logged in
account.password=Password
account.portable=Portable Account
account.portable=Portable
account.skin=Skin
account.skin.file=Skin File
account.skin.model=Model
account.skin.model.default=Classic
account.skin.model.slim=Slim
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=Address
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
account.skin.type.little_skin=LittleSkin
account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.
account.skin.type.local_file=Local Skin File
account.skin.type.steve=Steve
account.skin.upload=Upload/Edit Skin
account.skin.upload.failed=Failed to upload skin.
account.skin.invalid_skin=Invalid skin file.
@@ -165,7 +168,7 @@ archive.version=Version
assets.download=Downloading Assets
assets.download_all=Validating Assets Integrity
assets.index.malformed=Index files of downloaded assets were corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page.
assets.index.malformed=Index files of downloaded assets are corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page.
button.cancel=Cancel
button.change_source=Change Download Source
@@ -193,7 +196,7 @@ color.recent=Recommended
color.custom=Custom Color
crash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java.
crash.user_fault=The launcher crashed because of corrupted Java or system environment. Please make sure your Java or OS is installed properly.
crash.user_fault=The launcher crashed due to corrupted Java or system environment. Please make sure your Java or OS is installed properly.
curse.category.0=All
@@ -319,9 +322,11 @@ curse.sort.name=Name
curse.sort.popularity=Popularity
curse.sort.total_downloads=Total Downloads
datetime.format=MMM d, yyyy, h\:mm\:ss a
download=Download
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
download.code.404=File not found on the remote server: %s
download.code.404=File "%s" not found on the remote server.
download.content=Addons
download.curseforge.unavailable=HMCL Nightly channel build does not support access to CurseForge. Please use Release or Beta channel builds to download.
download.existing=The file cannot be saved because it already exists. You can click "Save As" to save the file elsewhere.
@@ -348,16 +353,16 @@ download.javafx.prepare=Preparing to download
exception.access_denied=HMCL is unable to access the file "%s". It may be locked by another process.\n\
\n\
For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after closing that process.\n\
If not, please check if your account has enough permissions to access it.
For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after terminating that process.\n\
If not, please check if your user account has adequate permissions to access it.
exception.artifact_malformed=Cannot verify the integrity of the downloaded files.
exception.ssl_handshake=Failed to establish an SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java and try again.
exception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again.
extension.bat=Windows Batch File (.bat)
extension.bat=Windows Batch File
extension.mod=Mod File
extension.png=Image File
extension.ps1=Windows PowerShell Script (.ps1)
extension.sh=Shell Script (.sh)
extension.ps1=Windows PowerShell Script
extension.sh=Shell Script
fatal.javafx.incompatible=Missing JavaFX environment.\n\
Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\
@@ -365,7 +370,7 @@ fatal.javafx.incompatible=Missing JavaFX environment.\n\
fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\
Please try replacing your Java or reinstalling OpenJFX.
fatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with Java, which includes OpenJFX.
fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may cause you to fail to open HMCL with another account in the future.\n\
fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may prevent you from opening HMCL with another account in the future.\n\
Do you still want to continue?
fatal.config_in_temp_dir=You are opening Hello Minecraft! Launcher in a temporary directory. Your settings and game data may be lost.\n\
It is recommended to move HMCL to another location and reopen it.\n\
@@ -375,7 +380,7 @@ fatal.config_loading_failure=Cannot load configuration files.\n\
For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads", and "Documents" and try again.
fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the configuration file because it was created by user "%1$s".\n\
Please open HMCL as root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s
fatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS because of macOS security mechanisms.\n\
fatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS due to macOS security mechanisms.\n\
Please move HMCL to a different directory before attempting to open. Otherwise, your settings and game data may be lost after restarting.\n\
Do you still want to continue?
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please restart the launcher.
@@ -388,13 +393,13 @@ fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically upda
You can update manually by downloading a newer launcher version from %s.
fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory.
fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
fatal.unsupported_platform=Minecraft is not yet fully supported for your platform, so you may experience missing functionality or even be unable to launch the game.\n\
fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\
\n\
If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Software" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility.
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for the Loongson platform.\n\
If you encounter problems when playing a game, you can visit https://docs.hmcl.net/groups.html for help.
fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother game experience.\n\
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may have better compatibility.
fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother gaming experience.\n\
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may offer better compatibility.
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing a game, please try launching the game with Java based on x86 architecture.\n\
\n\
If you are using the <b>Qualcomm</b> platform, you may need to install the <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL Compatibility Pack</a> before playing games.\n\
@@ -421,7 +426,7 @@ folder.saves=Saves
folder.screenshots=Screenshots
game=Games
game.crash.feedback=<b>Please do not share screenshots of this interface with others!</b> If you ask for help from others, please click <b>"Export Crash Logs"</b> and send the exported file to others for analysis.
game.crash.feedback=<b>Please do not share screenshots of this window with others!</b> If you ask for help from others, please click <b>"Export Crash Logs"</b> and send the exported file to others for analysis.
game.crash.info=Crash Info
game.crash.reason=Crash Cause
game.crash.reason.analyzing=Analyzing...
@@ -476,32 +481,32 @@ game.crash.reason.need_jdk11=The game crashed because of an inappropriate Java V
You need to download and install Java 11, and set it in "Global/Instance-specific Settings → Java".
game.crash.reason.mod_name=The game crashed because of mod filename problems.\n\
\n\
Mod file names should use only English letters (Aa~Zz), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\
Mod file names should use only English letters (A~Z, a~z), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\
\n\
Please navigate to the mod directory and add all non-compliant mod file names with one of the above compliant characters.
Please navigate to the mod directory and change all non-compliant mod file names using the compliant characters above.
game.crash.reason.incomplete_forge_installation=The game cannot continue to run because of an incomplete Forge/NeoForge installation.\n\
\n\
Please reinstall Forge/NeoForge in "Edit Instance → Loaders".
game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 or later is incompatible with currently installed mods. You need to downgrade it to 0.11.7.
game.crash.reason.fabric_warnings=The Fabric warned:\n\
game.crash.reason.fabric_warnings=The Fabric Loader warned:\n\
\n\
%1$s
game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\
\n\
You can try backing up and deleting that file, then relaunching the game.
game.crash.reason.file_changed=The game crashed because the file "%1$s" already exists.\n\
game.crash.reason.file_changed=The game crashed because the file verification failed.\n\
\n\
You can try backing up and deleting that file, then relaunching the game.
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource packs.\n\
If you modified the Minecraft.jar file, you will need to revert the modification or re-download the game.
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource/texture packs.\n\
\n\
Please disable the mods, shaders, or resource packs you are using and then try again.
Please disable the mods, shaders, or resource/texture packs you are using and then try again.
game.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\n\
\n\
Please try again after updating your graphics driver to the latest version.\n\
\n\
If your computer has a discrete graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your discrete graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\n\
If your computer has a dedicated graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your dedicated graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\n\
\n\
If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or previous requires <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 or previous</a> for Intel(R) Core(TM) 3000 processor series or previous.
If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or earlier requires <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 or earlier</a> for Intel(R) Core(TM) 3000 processor series or earlier.
game.crash.reason.macos_failed_to_find_service_port_for_display=The game cannot continue because the OpenGL window on the Apple silicon platform failed to initialize.\n\
\n\
For this problem, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and <b>quickly return to the browser page</b> before the game window pops up, wait for the game window to appear, and then switch back to the game window.
@@ -513,15 +518,15 @@ game.crash.reason.install_mixinbootstrap=The game crashed because of the missing
You can try installing <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem.
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\n\
\n\
Please navigate to<a href="https://optifine.net/downloads">the official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
Please navigate to the <a href="https://optifine.net/downloads">official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
\n\
After testing, we believe that too high or too low OptiFine versions may cause crashes.
game.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\n\
\n\
Please put the entire mod file directly into the mod directory!\n\
\n\
If extract will cause errors in the game, please delete the extracted mod in the mod directory and then launch the game.
game.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod installed at the same time.\n\
If extraction causes errors in the game, please delete the extracted mod in the mod directory and then launch the game.
game.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod are installed at the same time.\n\
\n\
Just remove the Shader mod because OptiFine has built-in support for shaders.
game.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Statistical Server (RTSS) is not compatible with Sodium.\n\
@@ -529,7 +534,7 @@ game.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Stat
Click <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">here</a> for more details.
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed because you installed too many mods and exceeded the game ID limit.\n\
\n\
Please try installing<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> or deleting some large mods.
Please try installing <a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> or deleting some large mods.
game.crash.reason.night_config_fixes=The game crashed because of some problems with Night Config.\n\
\n\
You can try installing the <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> mod, which may help you with this problem.\n\
@@ -557,7 +562,7 @@ game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or O
Please try updating Forge and OptiFine, or try using Java 8u251 or previous versions.
game.crash.reason.forge_repeat_installation=The game crashed because of a duplicate Forge installation. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">This is a known problem</a>\n\
\n\
It is recommended to upload the log feedback to GitHub so that we can find more clues and resolve this problem.\n\
It is recommended to submit feedback on GitHub along with this log so we can find more clues and resolve the issue.\n\
\n\
Currently you can uninstall Forge and reinstall it in "Edit Instance → Loaders".
game.crash.reason.optifine_repeat_installation=The game crashed because of a duplicate OptiFine installation.\n\
@@ -584,9 +589,9 @@ game.crash.reason.forge_found_duplicate_mods=The game crashed because of a dupli
%1$s
game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\
\n\
"%1$s" requires "%2$s".\n\
"%1$s" requires mod "%2$s".\n\
\n\
You need to upgrade or downgrade %3$s before continuing.
You need to upgrade or downgrade "%3$s" before continuing.
game.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\n\
\n\
"%1$s" is incompatible with "%2$s".
@@ -607,7 +612,7 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version)
game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation. Please try updating Forge.
game.crash.reason.no_class_def_found_error=The game cannot continue to run because of incomplete code.\n\
\n\
Your game instance is missing %1$s. This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\
Your game instance is missing "%1$s". This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\
\n\
You may need to reinstall the game and all mods or ask someone for help.
game.crash.reason.no_such_method_error=The game cannot continue to run because of incomplete code.\n\
@@ -617,20 +622,20 @@ game.crash.reason.no_such_method_error=The game cannot continue to run because o
You may need to reinstall the game and all mods or ask someone for help.
game.crash.reason.opengl_not_supported=The game crashed because your graphics driver does not support OpenGL.\n\
\n\
If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local one.\n\
If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local machine.\n\
\n\
Or, you can try updating your driver to the latest version and then try again.\n\
Or, you can update your graphic driver to the latest version and then try again.\n\
\n\
If your computer has a discrete graphics card, please make sure the game is actually using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer.
game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one online.
If your computer has a dedicated graphics card, please make sure the game is indeed using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer.
game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one.
game.crash.reason.out_of_memory=The game crashed because the computer ran out of memory.\n\
\n\
Maybe because there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
\n\
If you still encounter these problems, you may need a better computer.
game.crash.reason.resolution_too_high=The game crashed because the resource pack resolution is too high.\n\
game.crash.reason.resolution_too_high=The game crashed because the resource/texture pack resolution is too high.\n\
\n\
You should switch to a resource pack with lower resolution or consider buying a better graphics card with more VRAM.
You should switch to a resource/texture pack with lower resolution or consider buying a better graphics card with more VRAM.
game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\
\n\
There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\
@@ -644,12 +649,12 @@ game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of m
\n\
If you have edited the native library path, please make sure these libraries do exist. Or, please try launching again after reverting it to default.\n\
\n\
If you did not, please check if you have missing dependency mods.\n\
If you have not, please check if you have missing dependency mods.\n\
\n\
Otherwise, if you believe this is caused by HMCL, please provide feedback to us.
game.crash.title=Game Crashed
game.directory=Game Path
game.version=Game Version
game.version=Game Instance
help=Help
help.doc=Hello Minecraft! Launcher Documentation
@@ -677,7 +682,7 @@ install.installer.choose=Choose Your %s Version
install.installer.depend=Requires %s
install.installer.fabric=Fabric
install.installer.fabric-api=Fabric API
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it.
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not function. If you do want to change the directory, you should reinstall it.
install.installer.forge=Forge
install.installer.neoforge=NeoForge
install.installer.game=Minecraft
@@ -696,6 +701,7 @@ install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s (Installed by external process, which cannot be configured)
install.modpack=Install Modpack
install.name.invalid=The name contains non-ASCII characters (such as emoji or CJK characters).\nIt is recommended to change the name to include only English letters, numbers, and underscores to avoid potential issues when launching the game.\nDo you want to proceed with the installation?
install.new_game=Install Instance
install.new_game.already_exists=This instance name already exists. Please use another name.
install.new_game.current_game_version=Current Instance Version
@@ -736,32 +742,33 @@ java.uninstall.confirm=Are you sure you want to uninstall this Java? This action
lang=English
lang.default=Use System Locales
launch.advice=%s Do you still want to continue to launch?
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may cause you to be unable to launch the game or affect the game experience. \nDo you still want to continue to launch?
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may prevent you from launching the game or affect gaming experience.\nDo you still want to continue to launch?
launch.advice.java.auto=The current Java VM version is not compatible with the instance.\n\nClick "Yes" to automatically choose the most compatible Java VM version. Or, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself.
launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or previous.
launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or earlier.
launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one.
launch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader. Please consider upgrading Forge to version 2773 or later.
launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or previous.
launch.advice.forge37_0_60=Forge versions previous to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.
launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or earlier.
launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.
launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions previous to 1.8.0_51. Please install the latest Java 8 version.
launch.advice.java9=You cannot launch Minecraft 1.12 or previous with Java 9 or later. Please use Java 8 instead.
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.
launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %s to launch Minecraft %s.
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.
launch.advice.newer_java=You are using the old Java to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance or even be unable to launch the game.
launch.advice.require_newer_java_version=The current game version requires Java %s, but we could not find one. Do you want to download one now?
launch.advice.too_large_memory_for_32bit=You have allocated a memory size larger than the memory limitation of the 32-bit Java installation. You may be unable to launch the game.
launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or previous only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com or install OpenJDK 8.
launch.advice.vanilla_x86.translation=Minecraft is not fully supported for your platform, so you may experience missing functionality or even be unable to launch the game.\nYou can download Java for the <b>x86-64</b> architecture <a href="https://learn.microsoft.com/java/openjdk/download">here</a> for a full gaming experience.
launch.advice.unknown=The game cannot be launched for the following reasons:
launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or earlier only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com or install OpenJDK 8.
launch.advice.vanilla_x86.translation=Minecraft is not fully supported on your platform, so you may experience missing features or even be unable to launch the game.\nYou can download Java for the <b>x86-64</b> architecture <a href="https://learn.microsoft.com/java/openjdk/download">here</a> for a full gaming experience.
launch.advice.unknown=The game cannot be launched due to the following reasons:
launch.failed=Failed to launch
launch.failed.cannot_create_jvm=We are unable to create a Java VM. It may be caused by incorrect Java VM arguments. You can try resolving it by removing all arguments you added in "Global/Instance-specific Settings → Advanced Settings → Java VM Options".
launch.failed.creating_process=We are unable to create a new process. Please check your Java path.\n
launch.failed.command_too_long=The command length exceeds the maximum length of a bat script. Please try exporting it as a PowerShell script.
launch.failed.command_too_long=The command length exceeds the maximum length of a batch script. Please try exporting it as a PowerShell script.
launch.failed.decompressing_natives=Failed to extract native libraries.\n
launch.failed.download_library=Failed to download libraries "%s".
launch.failed.executable_permission=Failed to make the launch script executable.
@@ -844,28 +851,29 @@ modpack.completion=Downloading dependencies
modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported.
modpack.description=Modpack Description
modpack.download=Download Modpacks
modpack.download.title=Download Modpack - %1s
modpack.enter_name=Enter a name for this modpack.
modpack.export=Export as Modpack
modpack.export.as=Export Modpack As...
modpack.file_api=Modpack URL Prefix
modpack.files.blueprints=BuildCraft Blueprints
modpack.files.config=Mod Configs
modpack.files.dumps=NEI Debug Output File
modpack.files.config=Mod Configuration Files
modpack.files.dumps=NEI Debug Output Files
modpack.files.hmclversion_cfg=Launcher Configuration File
modpack.files.liteconfig=LiteLoader Related Files
modpack.files.mods=Mods
modpack.files.mods.voxelmods=VoxelMods Options
modpack.files.options_txt=Minecraft Options File
modpack.files.optionsshaders_txt=Shaders Settings File
modpack.files.options_txt=Minecraft Option File
modpack.files.optionsshaders_txt=Shader Option File
modpack.files.resourcepacks=Resource/Texture Packs
modpack.files.saves=Game Saves
modpack.files.saves=Worlds
modpack.files.scripts=MineTweaker Configuration File
modpack.files.servers_dat=Server List File
modpack.install=Install Modpack %s
modpack.installing=Installing Modpack
modpack.introduction=Curse, Modrinth, MultiMC, and MCBBS modpacks are currently supported.
modpack.invalid=Invalid modpack, you can try downloading it again.
modpack.mismatched_type=Modpack type mismatched, the current instance is a(an) %s type, but the provided one is %s type.
modpack.mismatched_type=Modpack type mismatched, the current instance is a(n) %s type, but the provided one is %s type.
modpack.name=Modpack Name
modpack.not_a_valid_name=Invalid modpack name.
modpack.origin=Source
@@ -885,7 +893,7 @@ modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
modpack.type.server=Auto-Update Modpack from Server
modpack.type.server.export=Allows server owner to update the game instance remotely
modpack.type.server.export=Allows server owner to remotely update the game instance
modpack.type.server.malformed=Invalid modpack manifest. Please contact the modpack maker to resolve this problem.
modpack.unsupported=Unsupported modpack format
modpack.update=Updating modpack
@@ -896,14 +904,14 @@ modpack.wizard.step.2=Choose Files
modpack.wizard.step.2.title=Choose files you wanted to add to the modpack.
modpack.wizard.step.3=Modpack Type
modpack.wizard.step.3.title=Choose modpack type you wanted to export as.
modpack.wizard.step.initialization.exported_version=Game version to export
modpack.wizard.step.initialization.exported_version=Game Instance to Export
modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server)
modpack.wizard.step.initialization.include_launcher=Include the launcher
modpack.wizard.step.initialization.save=Export to...
modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game launches normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\
modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game can be launched normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\
\n\
Keep in mind that you are not allowed to add mods and resource packs that are explicitly said not to be distributed or put in a modpack.
modpack.wizard.step.initialization.server=Click here for more tutorials for making a server modpack that can be automatically updated.
Keep in mind that you are not allowed to add mods and resource packs that explicitly state they could not to be distributed or put in a modpack.
modpack.wizard.step.initialization.server=Click here for more information on how to make a server modpack that can be automatically updated.
modrinth.category.adventure=Adventure
modrinth.category.audio=Audio
@@ -979,7 +987,7 @@ mods.add=Add Mod
mods.add.failed=Failed to add mod %s.
mods.add.success=%s was successfully added.
mods.broken_dependency.title=Broken dependency
mods.broken_dependency.desc=This dependency existed before, but it does not exist now. Try using another download source.
mods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source.
mods.category=Category
mods.channel.alpha=Alpha
mods.channel.beta=Beta
@@ -995,18 +1003,19 @@ mods.check_updates.target_version=Target Version
mods.check_updates.update=Update
mods.choose_mod=Choose mod
mods.curseforge=CurseForge
mods.dependency.embedded=Built-in Pre-mod (Already packaged in the mod file by the author. No need to download separately)
mods.dependency.optional=Optional Pre-mod (If missing, the game will run normally, but the mod features may be missing)
mods.dependency.required=Required Pre-mod (Must be downloaded separately. Missing may cause the game to fail to launch)
mods.dependency.tool=Required Library (Must be downloaded separately. Missing may cause the game to fail to launch)
mods.dependency.include=Built-in Pre-mod (Already packaged in the mod file by the author, no need to download separately)
mods.dependency.incompatible=Incompatible Mod (Installing these mods at the same time will cause the game to fail to launch)
mods.dependency.broken=Broken Pre-mod (This mod existed before, but it does not exist now. Try using another download source.)
mods.dependency.embedded=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)
mods.dependency.optional=Optional Dependencies (If missing, the game will run normally, but the mod features may be missing)
mods.dependency.required=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)
mods.dependency.tool=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)
mods.dependency.include=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)
mods.dependency.incompatible=Incompatible Mods (Installing these mods at the same time will prevent the game from launching)
mods.dependency.broken=Broken Dependencies (This mod existed before, but it does not exist anymore. Try using another download source.)
mods.disable=Disable
mods.download=Mod Download
mods.download.title=Mod Download - %1s
mods.download=Download Mod
mods.download.title=Download Mod - %1s
mods.download.recommend=Recommended Mod Version - Minecraft %1s
mods.enable=Enable
mods.game.version=Game Version
mods.manage=Mods
mods.mcbbs=MCBBS
mods.mcmod=MCMod
@@ -1038,9 +1047,16 @@ web.view_in_browser=View in browser
world=Worlds
world.add=Add World
world.backup=Backup World
world.backup.create.failed=Failed to create backup.\n%s
world.backup.create.locked=The world is currently in use. Please close the game and try again.
world.backup.create.success=Successfully created a new backup: %s
world.backup.delete=Delete this backup
world.backup.reveal=Show in folder
world.backup.title=World [%s] - Backups
world.datapack=Manage Datapacks
world.datapack.1_13=Only Minecraft 1.13 or later supports datapacks.
world.description=%s | Last played on %s | Game Version: %s
world.datetime=Last played on %s
world.download=Download World
world.export=Export the World
world.export.title=Choose the directory for this exported world
@@ -1052,6 +1068,7 @@ world.import.already_exists=This world already exists.
world.import.choose=Choose world archive you want to import
world.import.failed=Failed to import this world: %s
world.import.invalid=Failed to parse the world.
world.info=World Information
world.info.title=World [%s] - Information
world.info.basic=Basic Information
world.info.allow_cheats=Allow Commands/Cheats
@@ -1081,12 +1098,12 @@ world.info.player.xp_level=Experience Level
world.info.random_seed=Seed
world.info.time=Game Time
world.info.time.format=%s days
world.locked=In use
world.manage=Worlds
world.name=World Name
world.name.enter=Enter the world name
world.reveal=Reveal in Explorer
world.show_all=Show All
world.time=h\:mm\:ss a, EEE, MMM d yyyy
profile=Game Directories
profile.already_exists=This name already exists. Please use a different name.
@@ -1170,7 +1187,7 @@ settings.advanced.natives_directory=Native Library Path
settings.advanced.natives_directory.choose=Choose the location of the desired native library
settings.advanced.natives_directory.custom=Custom
settings.advanced.natives_directory.default=Default
settings.advanced.natives_directory.hint=This option is intended only for users of Apple M1 or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
settings.advanced.natives_directory.hint=This option is intended only for users of Apple silicon or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
\n\
Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\
Note: It is recommended to use a fully English-letters path for the specified local library file. Otherwise it may lead to game launch failure.
@@ -1202,6 +1219,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
settings.custom=Custom
settings.game=Settings
settings.game.copy_global=Copy from Global Settings
settings.game.copy_global.copy_all=Copy All
settings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!
settings.game.current=Game
settings.game.dimension=Resolution
settings.game.exploration=Explore
@@ -1238,7 +1258,7 @@ settings.launcher.font=Font
settings.launcher.general=General
settings.launcher.language=Language (Applies After Restart)
settings.launcher.launcher_log.export=Export Launcher Logs
settings.launcher.launcher_log.reveal=Reveal Logs in Explorer
settings.launcher.launcher_log.reveal=Reveal Logs in File Manager
settings.launcher.launcher_log.export.failed=Failed to export logs.
settings.launcher.launcher_log.export.success=Logs have been exported to "%s".
settings.launcher.log=Logging
@@ -1251,7 +1271,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=No Proxy
settings.launcher.proxy.password=Password
settings.launcher.proxy.port=Port
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Username
settings.launcher.theme=Theme
settings.launcher.title_transparent=Transparent Titlebar
@@ -1268,7 +1288,6 @@ settings.memory.lower_bound=Minimum Memory
settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total
settings.physical_memory=Physical Memory Size
settings.show_log=Show Logs
settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer.
settings.tabs.installers=Loaders
settings.take_effect_after_restart=Applies After Restart
settings.type=Settings Type of Instance
@@ -1292,14 +1311,14 @@ update=Update
update.accept=Update
update.changelog=Changelog
update.channel.dev=Beta
update.channel.dev.hint=You are currently using a Beta channel build of the launcher, which may include some extra features but is also sometimes more unstable than the Release channel.\n\
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
\n\
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
update.channel.dev.title=Beta Channel Notice
update.channel.nightly=Nightly
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher, which may include some extra features but is also always more unstable than the other channels.\n\
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\
\n\
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
update.channel.nightly.title=Nightly Channel Notice
update.channel.stable=Release
update.checking=Checking for Updates
@@ -1350,7 +1369,7 @@ version.manage.remove_assets=Delete All Assets
version.manage.remove_libraries=Delete All Libraries
version.manage.rename=Rename Instance
version.manage.rename.message=Enter New Instance Name
version.manage.rename.fail=Failed to rename the instance, some files might be in use, or the name contains an invalid character.
version.manage.rename.fail=Failed to rename the instance. Some files might be in use, or the name contains an invalid character.
version.settings=Settings
version.update=Update Modpack
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
about=について
about.copyright=著作権
about.copyright.statement=Copyright © 2024 huangyuhui
about.copyright.statement=Copyright © 2025 huangyuhui
about.author=著者
about.author.statement=bilibili @huanghongxun
about.claim=EULA
@@ -283,6 +283,8 @@ curse.sort.name=名前
curse.sort.popularity=人気
curse.sort.total_downloads=合計ダウンロード数
datetime.format=yyyy/MM/dd H:mm:ss
download=ダウンロード
download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップをダウンロードします
download.code.404=リモートサーバーにファイルが見つかりません:%s
@@ -700,7 +702,7 @@ world=マップ
world.add=マップを追加(.zip)
world.datapack=データパックの管理
world.datapack.1_13=Minecraft1.13以降のバージョンのみがデータパックをサポートします。
world.description=%s。最終ゲーム時刻:%s。ゲームバージョン:%s。
world.datetime=最終ゲーム時刻:%s
world.download=ダウンロード
world.export=このマップをエクスポートする
world.export.title=保存する場所を選択してください
@@ -717,7 +719,6 @@ world.name=マップ名
world.name.enter=マップ名を入力してください
world.reveal=フォルダを表示
world.show_all=すべて表示
world.time=EEE、MMM d、yyyy HH:mm:ss
profile=ゲームディレクトリ
profile.already_exists=この名前はすでに存在します。別の名前を使用してください。
@@ -857,7 +858,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=プロキシなし
settings.launcher.proxy.password=パスワード
settings.launcher.proxy.port=ポート
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=アカウント
settings.launcher.theme=テーマ
settings.launcher.title_transparent=タイトルの透明性
@@ -873,7 +874,6 @@ settings.memory.lower_bound=最小メモリ
settings.memory.used_per_total=%1$.1f GB使用済み/%2$.1fGB合計
settings.physical_memory=物理メモリサイズ
settings.show_log=ログを表示
settings.skin=オフラインアカウントのスキンの変更がサポートされるようになりました!アカウントページに移動して、今すぐ肌を変えてください!
settings.tabs.installers=Installers
settings.take_effect_after_restart=再起動後に有効になります
settings.type=バージョン設定タイプ
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
about=關於
about.copyright=著作權
about.copyright.statement=著作權所有 © 2024 huangyuhui
about.copyright.statement=著作權所有 © 2025 huangyuhui
about.author=作者
about.author.statement=bilibili @huanghongxun
about.claim=使用者協議
@@ -129,9 +129,12 @@ account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,請
account.methods.microsoft.snapshot.website=官方網站
account.methods.offline=離線模式
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名
account.methods.offline.name.invalid=遊戲使用者名稱通常僅允許使用英文字母、數字及底線,且長度不能超過 16 個字元。\n\
account.methods.offline.name.invalid.tip=遊戲使用者名稱建議僅使用英文字母、數字及底線,且長度不超過 16 個字元。
account.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文字母、數字及底線,且長度不超過 16 個字元。\n\
\n\
\ · 一些有效的使用者名稱:HuangYu、huang_Yu、Huang_Yu_123;\n\
\ · 一些無效的使用者名稱:黃魚,Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\n\
\n\
如果你相信伺服器端有相應的模組或插件來解除此限制,你可以忽略本警告。
account.methods.offline.uuid=UUID
account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一標識符,每個啟動器生成 UUID 的方式可能不同。\n透過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。\nUUID 選項為進階選項,除非你知道你在做什麼,否則你不需要調整該選項。
@@ -148,12 +151,14 @@ account.skin.file=外觀圖片檔案
account.skin.model=模型
account.skin.model.default=寬型
account.skin.model.slim=纖細
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing Skin 伺服器
account.skin.type.csl_api.location=伺服器位址
account.skin.type.csl_api.location.hint=CustomSkinAPI 位址
account.skin.type.little_skin=LittleSkin 皮膚站
account.skin.type.little_skin.hint=你需要在皮膚站中加入並使用和該離線帳戶同名角色,此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。
account.skin.type.local_file=本機外觀圖片檔案
account.skin.type.steve=Steve
account.skin.upload=上傳/編輯外觀
account.skin.upload.failed=外觀上傳失敗
account.skin.invalid_skin=無法識別的外觀檔案
@@ -319,6 +324,8 @@ curse.sort.name=名稱
curse.sort.popularity=熱度
curse.sort.total_downloads=下載量
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
download=下載
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
download.code.404=遠端伺服器沒有需要下載的檔案:%s
@@ -418,7 +425,7 @@ game.crash.reason.mod_name=目前遊戲由於模組檔案名稱問題,無法
game.crash.reason.incomplete_forge_installation=目前遊戲由於 Forge 安裝不完整,無法繼續執行。\n請在「實例管理 - 自動安裝」中移除 Forge 並重新安裝。
game.crash.reason.file_already_exists=目前遊戲由於檔案「%1$s」已經存在,無法繼續執行。\n如果你認為這個檔案可以刪除,你可以在備份這個檔案後嘗試刪除它,並重新啟動遊戲。
game.crash.reason.file_changed=目前遊戲由於檔案校驗失敗,無法繼續執行。\n如果你手動修改了 Minecraft.jar 檔案,你需要回退修改,或者重新下載遊戲。
game.crash.reason.gl_operation_failure=目前遊戲由於你使用的某些模組/光影包/資源包出現問題,無法繼續執行。\n請先嘗試禁用你所使用的模組/光影包/資源包再試。
game.crash.reason.gl_operation_failure=目前遊戲由於你使用的某些模組/光影包/資源包/紋理包出現問題,無法繼續執行。\n請先嘗試禁用你所使用的模組/光影包/資源包/紋理包再試。
game.crash.reason.graphics_driver=目前遊戲由於你的顯示卡驅動存在問題崩潰了,請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\
如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用整合/核芯顯示卡啟動。如果是,請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。\n\
如果你確實需要使用核芯顯示卡,請檢查你電腦的 CPU 是否為 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更低版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及更低版本,否則你需要更換獨立顯示卡或新電腦。
@@ -456,14 +463,14 @@ game.crash.reason.no_such_method_error=目前遊戲由於程式碼不完整,
game.crash.reason.opengl_not_supported=目前遊戲由於你的顯示卡驅動存在問題,無法繼續執行。\n原因是 OpenGL 不受支援,你現在是否在遠端桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用整合/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。
game.crash.reason.openj9=目前遊戲無法執行在 OpenJ9 虛擬機上,請你在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 Hotspot Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。
game.crash.reason.out_of_memory=目前遊戲由於記憶體不足,無法繼續執行。\n這可能是記憶體分配太小,或者模組數量過多導致的。\n你可以在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下執行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。
game.crash.reason.resolution_too_high=目前遊戲由於資源包解析度過高,無法繼續執行。\n你可以更換一個解析度更低的資源包,或者更換一個視訊記憶體更大的顯示卡。
game.crash.reason.resolution_too_high=目前遊戲由於資源包/紋理包解析度過高,無法繼續執行。\n你可以更換一個解析度更低的資源包/紋理包,或者更換一個視訊記憶體更大的顯示卡。
game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含模組名稱,你可以透過搜尋的方式尋找有關訊息。\n%s
game.crash.reason.too_old_java=目前遊戲由於 Java 虛擬機版本過低,無法繼續執行。\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 %1$s 或更高版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 <a href="https://learn.microsoft.com/java/openjdk/download">此處</a> 下載 Microsoft JDK。
game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。
game.crash.reason.unsatisfied_link_error=目前遊戲由於缺少本機庫,無法繼續執行。\n這些本機庫缺失:%1$s。\n如果你在「(全域/實例特定) 遊戲設定 → 進階設定」中修改了本機庫路徑選項,請你修改回預設模式。\n如果你正在使用預設模式,請檢查遊戲目錄路徑是否只包含英文字母、數字和底線,\n如果是,那麼請檢查是否為模組或 HMCL 導致了本機庫缺失的問題。如果你確定是 HMCL 引起的,建議你向我們回報。\n<b>對於 Windows 使用者,你還可以嘗試在「控制台 → 時鐘和區域 → 地區 → 系統管理 → 變更系統區域設定」中,關閉「Beta:使用 Unicode UTF-8 提供全球語言支援」選項;</b>\n<b>或將遊戲目錄路徑中的所有非英文字母的名稱 (例如中文、空格等) 修改為英文字母。</b>\n如果你確實需要自訂本機庫路徑,你需要保證其中包含缺失的本機庫!
game.crash.title=遊戲意外退出
game.directory=遊戲目錄路徑
game.version=遊戲版本
game.version=遊戲實例
help=說明
help.doc=Hello Minecraft! Launcher 說明文件
@@ -510,9 +517,10 @@ install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換]
install.modpack=安裝模組包
install.name.invalid=名稱中包含非 ASCII 字元(如 Emoji 表情或中文字元)。\n建議修改名稱,名稱建議僅包含英文字母、數字和底線,以防啟動遊戲時出現問題。是否繼續安裝?
install.new_game=安裝新實例
install.new_game.already_exists=此實例已經存在,請重新命名
install.new_game.current_game_version=目前遊戲版本
install.new_game.current_game_version=目前遊戲實例
install.new_game.malformed=名稱無效
install.select=請選取安裝方式
install.success=安裝成功
@@ -627,7 +635,7 @@ logwindow.terminate_game=結束遊戲處理程式
logwindow.title=日誌
logwindow.help=你可以前往 HMCL 社群,向他人尋求幫助
logwindow.autoscroll=自動滾動
logwindow.export_game_crash_logs=匯出遊戲崩潰訊息
logwindow.export_game_crash_logs=匯出遊戲崩潰資訊
logwindow.export_dump=匯出遊戲執行堆疊
logwindow.export_dump.no_dependency=你的 Java 不包含用於建立遊戲執行堆疊的相依元件。請前往 Discord 或 HMCL QQ 群尋求幫助。
@@ -659,13 +667,14 @@ modpack.completion=下載模組包相關檔案
modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新紀錄,支援 Markdown (圖片請上傳至網路)。
modpack.description=模組包描述
modpack.download=下載模組包
modpack.download.title=模組包下載 - %1s
modpack.enter_name=給遊戲取個你喜歡的名稱
modpack.export=匯出模組包
modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。
modpack.file_api=模組包下載連結前綴
modpack.files.blueprints=BuildCraft 藍圖
modpack.files.config=模組設定檔案
modpack.files.dumps=NEI 調校輸出
modpack.files.dumps=NEI 除錯輸出檔案
modpack.files.hmclversion_cfg=啟動器設定檔案
modpack.files.liteconfig=LiteLoader 相關檔案
modpack.files.mods=模組
@@ -711,7 +720,7 @@ modpack.wizard.step.2=選取檔案
modpack.wizard.step.2.title=選中你想加到模組包中的檔案或目錄
modpack.wizard.step.3=模組包類型
modpack.wizard.step.3.title=選取模組包匯出類型
modpack.wizard.step.initialization.exported_version=要匯出的遊戲版本
modpack.wizard.step.initialization.exported_version=要匯出的遊戲實例
modpack.wizard.step.initialization.force_update=強制升級模組包至最新版本 (需要自建伺服器)
modpack.wizard.step.initialization.include_launcher=包含啟動器
modpack.wizard.step.initialization.save=選取要匯出到的遊戲模組包位置
@@ -812,6 +821,7 @@ mods.download=模組下載
mods.download.title=模組下載 - %1s
mods.download.recommend=推薦版本 - Minecraft %1s
mods.enable=啟用
mods.game.version=遊戲版本
mods.manage=模組管理
mods.mcbbs=MCBBS
mods.mcmod=MC 百科
@@ -843,10 +853,16 @@ web.view_in_browser=在瀏覽器中查看
world=世界
world.add=加入世界
world.backup=備份世界
world.backup.create.failed=創建備份失敗。\n%s
world.backup.create.locked=該世界正在使用中,請關閉遊戲後重試。
world.backup.create.success=成功創建新備份:%s
world.backup.delete=删除此備份
world.backup.title=世界 [%s] - 備份
world.datapack=管理資料包
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支援資料包
world.description=%s | 上一次遊戲時間: %s | 遊戲版本: %s
world.download=存檔下載
world.datetime=上一次遊戲時間: %s
world.download=下載世界
world.export=匯出此世界
world.export.title=選取該世界的儲存位置
world.export.location=儲存到
@@ -856,6 +872,7 @@ world.import.already_exists=此世界已經存在
world.import.choose=選取要匯入的存檔壓縮檔
world.import.failed=無法匯入此世界: %s
world.import.invalid=無法識別的存檔壓縮檔
world.info=世界資訊
world.info.title=世界 [%s] - 世界資訊
world.info.basic=基本資訊
world.info.allow_cheats=允許指令(作弊)
@@ -885,13 +902,13 @@ world.info.player.xp_level=經驗等級
world.info.random_seed=種子碼
world.info.time=遊戲內時間
world.info.time.format=%s 天
world.locked=使用中
world.game_version=遊戲版本
world.manage=世界/資料包
world.name=世界名稱
world.name.enter=輸入世界名稱
world.reveal=開啟目錄
world.show_all=全部顯示
world.time=yyyy 年 M 月 d 日, HH:mm:ss
profile=遊戲目錄
profile.already_exists=該名稱已存在
@@ -975,7 +992,7 @@ settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受
settings.advanced.no_jvm_args=不加入預設的 JVM 參數
settings.advanced.precall_command=遊戲啟動前執行指令
settings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用
settings.advanced.process_priority=處理程序優先度
settings.advanced.process_priority=處理程式優先度
settings.advanced.process_priority.low=低 (節省遊戲占用資源,可能會造成遊戲卡頓)
settings.advanced.process_priority.below_normal=較低 (節省遊戲占用資源,可能會造成遊戲卡頓)
settings.advanced.process_priority.normal=中 (平衡)
@@ -1000,6 +1017,9 @@ settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指
settings.custom=自訂
settings.game=遊戲設定
settings.game.copy_global=複製全域遊戲設定
settings.game.copy_global.copy_all=複製全部
settings.game.copy_global.copy_all.confirm=你確定要覆寫目前實例特定遊戲設定嗎?該操作無法復原!
settings.game.current=遊戲
settings.game.dimension=遊戲介面解析度大小
settings.game.exploration=瀏覽
@@ -1047,7 +1067,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=不使用代理
settings.launcher.proxy.password=密碼
settings.launcher.proxy.port=連線埠
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=帳戶
settings.launcher.theme=主題
settings.launcher.title_transparent=標題欄透明
@@ -1064,7 +1084,6 @@ settings.memory.lower_bound=最低分配
settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB
settings.physical_memory=實體記憶體大小
settings.show_log=查看日誌
settings.skin=現已支援離線帳戶更換外觀,你可以到帳戶頁面更改離線帳戶的外觀和披風 (多人遊戲下其他玩家無法看到你的外觀)。
settings.tabs.installers=自動安裝
settings.take_effect_after_restart=重啟後生效
settings.type=實例遊戲設定類型
@@ -1138,15 +1157,15 @@ version.manage.duplicate.confirm=新的遊戲將複製該實例目錄 (".minecra
version.manage.manage=實例管理
version.manage.manage.title=實例管理 - %1s
version.manage.redownload_assets_index=更新遊戲資源檔案
version.manage.remove=刪除該版本
version.manage.remove=刪除該實例
version.manage.remove.confirm=真的要刪除實例「%s」嗎? 你將無法找回被刪除的檔案!!!
version.manage.remove.confirm.trash=真的要刪除實例「%s」嗎? 你可以在系統的資源回收筒 (或垃圾桶) 中還原目錄「%s」來找回該實例。
version.manage.remove.confirm.independent=由於該實例啟用了「(全域/實例特定) 遊戲設定 → 執行路徑 → 各實例獨立」設定,所以刪除該實例將導致該遊戲的存檔等資料一同被刪除,真的要刪除實例「%s」嗎?
version.manage.remove_assets=刪除所有遊戲資源檔案
version.manage.remove_libraries=刪除所有支援庫檔案
version.manage.rename=重新命名該版本
version.manage.rename=重新命名該實例
version.manage.rename.message=請輸入新名稱
version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元。
version.manage.rename.fail=重新命名實例失敗,可能檔案被佔用或者名稱有特殊字元。
version.settings=遊戲設定
version.update=更新模組包
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
about=关于
about.copyright=版权
about.copyright.statement=版权所有 © 2024 huangyuhui
about.copyright.statement=版权所有 © 2025 huangyuhui
about.author=作者
about.author.statement=bilibili @huanghongxun
about.claim=用户协议
@@ -137,9 +137,12 @@ account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,请
account.methods.microsoft.snapshot.website=官方网站
account.methods.offline=离线模式
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名
account.methods.offline.name.invalid=游戏用户名通常仅允许使用英文字母、数字及下划线,且长度不能超过 16 个字符。\n\
account.methods.offline.name.invalid.tip=游戏用户名建议仅使用英文字母、数字及下划线,且长度不超过 16 个字符。
account.methods.offline.name.invalid=游戏用户名建议仅使用英文字母、数字及下划线,且长度不超过 16 个字符。\n\
\n\
\ · 一些合法用户名:HuangYu、huang_Yu、Huang_Yu_123;\n\
\ · 一些非法用户名:黄鱼、Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\n\
\n\
如果你相信服务端有相应模组或插件解除此限制,可以忽略本警告。\n\
如遇到问题,你可以点击右上角帮助按钮进行求助。
account.methods.offline.uuid=UUID
@@ -157,12 +160,14 @@ account.skin.file=皮肤图片文件
account.skin.model=模型
account.skin.model.default=宽型
account.skin.model.slim=纤细
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing Skin 服务器
account.skin.type.csl_api.location=服务器地址
account.skin.type.csl_api.location.hint=CustomSkinAPI 地址
account.skin.type.little_skin=LittleSkin 皮肤站
account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色,此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。
account.skin.type.local_file=本地皮肤图片文件
account.skin.type.steve=Steve
account.skin.upload=上传/编辑皮肤
account.skin.upload.failed=皮肤上传失败
account.skin.invalid_skin=无法识别的皮肤文件
@@ -328,6 +333,8 @@ curse.sort.name=名称
curse.sort.popularity=热度
curse.sort.total_downloads=下载量
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
download=下载
download.hint=安装游戏和整合包或下载模组、资源包和地图
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
@@ -420,7 +427,7 @@ game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本与当
game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s
game.crash.reason.file_already_exists=当前游戏由于文件“%1$s”已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。
game.crash.reason.file_changed=当前游戏由于文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。
game.crash.reason.gl_operation_failure=当前游戏由于你使用的某些模组/光影包/资源包有问题,导致无法继续运行。\n请先尝试禁用你所使用的模组/光影包/资源包再试。
game.crash.reason.gl_operation_failure=当前游戏由于你使用的某些模组/光影包/资源包/纹理包有问题,导致无法继续运行。\n请先尝试禁用你所使用的模组/光影包/资源包/纹理包再试。
game.crash.reason.graphics_driver=当前游戏由于显卡驱动问题而崩溃,请尝试以下操作:\n\
\ · 如果你的电脑存在独立显卡,请尝试使用独立显卡而非 Intel 核显启动 HMCL 与游戏;<a href="https://www.bing.com/search?q=使用独立显卡运行Minecraft">详情</a>\n\
\ · <a href="https://minecrafthopper.net/help/pixel-format-not-accelerated/">尝试升级你的显卡驱动到最新版本</a>,或回退到出厂版本;\n\
@@ -467,7 +474,7 @@ game.crash.reason.no_such_method_error=当前游戏由于代码不完整,无
game.crash.reason.opengl_not_supported=当前游戏由于你的显卡驱动存在问题,无法继续运行。\n原因是 OpenGL 不受支持,你现在是否在远程桌面或者串流模式下?如果是,请直接使用原电脑启动游戏。\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核心显卡启动,如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。
game.crash.reason.openj9=当前游戏无法在 OpenJ9 虚拟机上运行,请你在“(全局/版本特定) 游戏设置 → 游戏 Java”中更换 Hotspot Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在网上自行下载。
game.crash.reason.out_of_memory=当前游戏由于内存不足,无法继续运行。\n这可能是内存分配太小,或者模组数量过多导致的。\n你可以在全局(特定)游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。
game.crash.reason.resolution_too_high=当前游戏由于材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。
game.crash.reason.resolution_too_high=当前游戏由于资源包/纹理包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的资源包/纹理包,或者更换一个显存更大的显卡。
game.crash.reason.stacktrace=原因未知,请点击日志按钮查看详细信息。\n下面是一些关键词,其中可能包含模组名称,你可以通过搜索的方式查找有关信息。\n%s
game.crash.reason.too_old_java=当前游戏由于 Java 虚拟机版本过低,无法继续运行。\n你需要在“(全局/版本特定) 游戏设置 → 游戏 Java”中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以点击 <a href="https://learn.microsoft.com/java/openjdk/download">此处</a> 下载 Microsoft JDK。
game.crash.reason.unknown=原因未知,请点击日志按钮查看详细信息。
@@ -521,6 +528,7 @@ install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s (由外部安装的版本,无法卸载或更换)
install.modpack=安装整合包
install.name.invalid=名称中包含非 ASCII 字符(如 Emoji 表情或中文字符)。\n建议修改名称,名称建议仅包含英文字母、数字和下划线,以防启动游戏时出现问题。是否继续安装?
install.new_game=安装新游戏
install.new_game.already_exists=此版本已经存在,请换一个名字
install.new_game.current_game_version=当前游戏版本
@@ -670,13 +678,14 @@ modpack.completion=下载整合包相关文件
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown (图片请用网络链接)
modpack.description=整合包描述
modpack.download=下载整合包
modpack.download.title=整合包下载 - %1s
modpack.enter_name=给游戏起个你喜欢的名字
modpack.export=导出整合包
modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”)
modpack.file_api=整合包下载链接前缀
modpack.files.blueprints=BuildCraft 蓝图
modpack.files.config=模组配置文件
modpack.files.dumps=NEI 调试输出
modpack.files.dumps=NEI 调试输出文件
modpack.files.hmclversion_cfg=启动器配置文件
modpack.files.liteconfig=LiteLoader 相关文件
modpack.files.mods=模组
@@ -823,6 +832,7 @@ mods.download=模组下载
mods.download.title=模组下载 - %1s
mods.download.recommend=推荐版本 - Minecraft %1s
mods.enable=启用
mods.game.version=游戏版本
mods.manage=模组管理
mods.mcbbs=MCBBS
mods.mcmod=MC 百科
@@ -854,10 +864,16 @@ web.view_in_browser=在浏览器中查看
world=世界
world.add=添加世界
world.backup=备份世界
world.backup.create.failed=创建备份失败。\n%s
world.backup.create.locked=该世界正在使用中,请关闭游戏后重试。
world.backup.create.success=成功创建新备份:%s
world.backup.delete=删除此备份
world.backup.title=世界 [%s] - 备份
world.datapack=管理数据包
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
world.description=%s | 上一次游戏时间: %s | 游戏版本: %s
world.download=存档下载
world.datetime=上一次游戏时间: %s
world.download=下载世界
world.export=导出此世界
world.export.title=选择该世界的存储位置
world.export.location=保存到
@@ -868,6 +884,7 @@ world.import.already_exists=此世界已经存在
world.import.choose=选择要导入的存档压缩包
world.import.failed=无法导入此世界:%s
world.import.invalid=无法识别该存档压缩包
world.info=世界信息
world.info.title=世界 [%s] - 世界信息
world.info.basic=基本信息
world.info.allow_cheats=允许命令(作弊)
@@ -897,12 +914,12 @@ world.info.player.xp_level=经验等级
world.info.random_seed=种子
world.info.time=游戏内时间
world.info.time.format=%s 天
world.locked=使用中
world.manage=世界/数据包
world.name=世界名称
world.name.enter=输入世界名称
world.reveal=打开文件夹
world.show_all=显示全部
world.time=yyyy 年 MM 月 dd 日, HH:mm:ss
profile=游戏文件夹
profile.already_exists=该名称已存在
@@ -1011,6 +1028,9 @@ settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命
settings.custom=自定义
settings.game=游戏设置
settings.game.copy_global=复制全局游戏设置
settings.game.copy_global.copy_all=复制全部
settings.game.copy_global.copy_all.confirm=你确定要覆盖当前版本特定游戏设置吗?此操作无法撤销!
settings.game.current=游戏
settings.game.dimension=游戏窗口分辨率
settings.game.exploration=浏览
@@ -1058,7 +1078,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=不使用代理
settings.launcher.proxy.password=密码
settings.launcher.proxy.port=端口
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=账户
settings.launcher.theme=主题
settings.launcher.title_transparent=标题栏透明
@@ -1075,7 +1095,6 @@ settings.memory.lower_bound=最低内存分配
settings.memory.used_per_total=设备中已使用 %1$.1f GB / 设备总内存 %2$.1f GB
settings.physical_memory=物理内存大小
settings.show_log=查看日志
settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风 (多人游戏下其他玩家无法看到你的皮肤)。
settings.tabs.installers=自动安装
settings.take_effect_after_restart=重启后生效
settings.type=版本游戏设置类型
File diff suppressed because it is too large. Load diff
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more