Compare commits

...
58 Commits
Author SHA1 Message Date
Glavo d9e3816b8e 优化账户列表页面 (#3736) 2025-03-13 22:18:23 +08:00
GlavoandZkitefly bdaf72fe2f 修复 ModrinthRemoteModRepository::getRemoteVersionByLocalFile 未捕获 NoSuchFileException 的问题 (#3735)
---------

Co-authored-by: Zkitefly <2573874409@qq.com>
2025-03-11 21:58:18 +08:00
Glavo 7efbbefdcf HTMLRenderer 应当避免插入过多换行符 (#3733) 2025-03-10 22:14:46 +08:00
Glavo ad71216bfc 为部分页面添加半透明背景遮罩 (#3730) 2025-03-09 13:02:00 +08:00
Wulian233 57903da09b Close #3728: 为更新日志对话框添加平滑滚动 (#3729) 2025-03-08 22:35:51 +08:00
3gf8jv4dv cf56c8035b fix: add scrollbar for GameCrashWindow (#3723)
Shows a scroll bar when needed to prevent user misinterpretation
when there is too much information.
2025-03-08 16:32:54 +08:00
Glavo dbf519b435 Fix #3720: 修复备份文件路径被错误地设置为源世界的问题 (#3722) 2025-03-08 16:18:49 +08:00
Glavo 696b9bd7ef Fix #3694: 显示对话框时隐藏焦点 (#3717) 2025-03-08 08:47:19 +08:00
3gf8jv4dv ec5236368a chore(locale): add new translations of world parts for es and ru (#3714) 2025-03-07 21:39:39 +08:00
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
88 changed files with 2437 additions and 1199 deletions

No files matched your search

@@ -24,7 +24,9 @@ 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;
@@ -57,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();
@@ -172,10 +186,10 @@ public class Theme {
Pair<String, String> fontStyle = null;
if (fontFamily == null) {
Optional<Font> font = tryLoadFont();
if (font.isPresent()) {
fontFamily = font.get().getFamily();
fontStyle = parseFontStyle(font.get().getStyle());
Font font = FONT.get();
if (font != null) {
fontFamily = font.getFamily();
fontStyle = parseFontStyle(font.getStyle());
}
}
@@ -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;
}
@@ -31,7 +31,6 @@ import javafx.beans.value.*;
import javafx.event.Event;
import javafx.event.EventDispatcher;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
@@ -255,20 +254,6 @@ public final class FXUtils {
});
}
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
column.setCellValueFactory(param -> {
if (column.validateValue(param))
return mapper.apply(param.getValue().getValue());
else
return column.getComputedValue(param);
});
}
public static Node wrapMargin(Node node, Insets insets) {
StackPane.setMargin(node, insets);
return new StackPane(node);
}
public static void setValidateWhileTextChanged(Node field, boolean validate) {
if (field instanceof JFXTextField) {
if (validate) {
@@ -622,22 +607,6 @@ public final class FXUtils {
}
}
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().bindBidirectional(property);
}
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().unbindBidirectional(property);
}
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().bindBidirectional(property);
}
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().unbindBidirectional(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;
@@ -1085,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) {
@@ -1103,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);
@@ -400,7 +400,7 @@ public class GameCrashWindow extends Stage {
ScrollPane reasonPane = new ScrollPane(reasonTextFlow);
reasonPane.setFitToWidth(true);
reasonPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
gameDirPane.setPadding(new Insets(8));
VBox.setVgrow(gameDirPane, Priority.ALWAYS);
@@ -151,6 +151,12 @@ public final class HTMLRenderer {
children.add(textNode);
}
private void appendAutoLineBreak(String text) {
AutoLineBreak textNode = new AutoLineBreak(text);
applyStyle(textNode);
children.add(textNode);
}
private void appendImage(Node node) {
String src = node.absUrl("src");
URI imageUri = null;
@@ -230,7 +236,7 @@ public final class HTMLRenderer {
case "h6":
case "tr":
if (!children.isEmpty())
appendText("\n\n");
appendAutoLineBreak("\n\n");
break;
}
@@ -252,15 +258,62 @@ public final class HTMLRenderer {
case "h4":
case "h5":
case "h6":
appendText("\n");
appendAutoLineBreak("\n");
break;
}
}
private static boolean isSpacing(String text) {
if (text == null)
return true;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch != ' ' && ch != '\t')
return false;
}
return true;
}
public void mergeLineBreaks() {
for (int i = 0; i < this.children.size(); i++) {
javafx.scene.Node child = this.children.get(i);
if (child instanceof AutoLineBreak) {
int lastAutoLineBreak = -1;
for (int j = i + 1; j < this.children.size(); j++) {
javafx.scene.Node otherChild = this.children.get(j);
if (otherChild instanceof AutoLineBreak) {
lastAutoLineBreak = j;
} else if (otherChild instanceof Text && isSpacing(((Text) otherChild).getText())) {
// do nothing
} else {
break;
}
}
if (lastAutoLineBreak > 0) {
this.children.subList(i + 1, lastAutoLineBreak + 1).clear();
if (((Text) child).getText().length() == 1) {
((Text) child).setText("\n\n");
}
}
}
}
}
public TextFlow render() {
TextFlow textFlow = new TextFlow();
textFlow.getStyleClass().add("html");
textFlow.getChildren().setAll(children);
return textFlow;
}
private static final class AutoLineBreak extends Text {
public AutoLineBreak(String text) {
super(text);
}
}
}
@@ -378,7 +378,7 @@ public class InstallerItem extends Control {
pane.getChildren().add(buttonsContainer);
JFXButton removeButton = new JFXButton();
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1));
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1));
removeButton.getStyleClass().add("toggle-icon4");
if (control.id.equals(MINECRAFT.getPatchId())) {
removeButton.setVisible(false);
@@ -396,8 +396,8 @@ public class InstallerItem extends Control {
JFXButton installButton = new JFXButton();
installButton.graphicProperty().bind(Bindings.createObjectBinding(() ->
control.resolvedStateProperty.get() instanceof InstallableState ?
SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), -1, -1) :
SVG.UPDATE.createIcon(Theme.blackFill(), -1, -1),
SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), -1) :
SVG.UPDATE.createIcon(Theme.blackFill(), -1),
control.resolvedStateProperty
));
installButton.getStyleClass().add("toggle-icon4");
@@ -69,7 +69,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
FXUtils.setLimitHeight(btnAdd, 40);
btnAdd.getStyleClass().add("jfx-button-raised-round");
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
btnAdd.setGraphic(SVG.PLUS.createIcon(Theme.whiteFill(), -1, -1));
btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1));
btnAdd.setOnAction(e -> skinnable.add());
JFXButton btnRefresh = new JFXButton();
@@ -77,7 +77,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
FXUtils.setLimitHeight(btnRefresh, 40);
btnRefresh.getStyleClass().add("jfx-button-raised-round");
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1, -1));
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1));
btnRefresh.setOnAction(e -> skinnable.refresh());
vBox.getChildren().setAll(btnAdd);
@@ -25,94 +25,88 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.SVGPath;
/**
* All vector icons used in the launcher.
* <p>
* Unless otherwise stated,
* these icons are from <a href="https://fonts.google.com/icons?icon.size=24&icon.color=%235f6368&icon.query=list&icon.set=Material+Symbols">Material Symbols</a>,
* with a style of outlined, a weight of 400, a grade of 0, and an optical size of 24 px.
* The view boxes of all icons are normalized to {@code 0 0 24 24}.
*/
public enum SVG {
GEAR("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"),
GEAR_OUTLINE("M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"),
BACK("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"),
CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"),
CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"),
CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"),
COPY("M15 16H7V6h8m.4-2H6.6A1.6 1.6 90 005 5.6V16.4a1.6 1.6 90 001.6 1.6h8.8A1.6 1.6 90 0017 16.4V5.6A1.6 1.6 90 0015.4 4M13 1H3.6A1.6 1.6 90 002 2.6V13h2V3H13V1Z"),
DOTS_VERTICAL("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"),
DOTS_HORIZONTAL("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"),
DELETE("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"),
DELETE_OUTLINE("M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8,9H16V19H8V9M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z"),
ACCOUNT_EDIT("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z"),
EXPAND("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"),
COLLAPSE("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"),
NAVIGATE("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
ROCKET_LAUNCH_OUTLINE("M13.13 22.19L11.5 18.36C13.07 17.78 14.54 17 15.9 16.09L13.13 22.19M5.64 12.5L1.81 10.87L7.91 8.1C7 9.46 6.22 10.93 5.64 12.5M19.22 4C19.5 4 19.75 4 19.96 4.05C20.13 5.44 19.94 8.3 16.66 11.58C14.96 13.29 12.93 14.6 10.65 15.47L8.5 13.37C9.42 11.06 10.73 9.03 12.42 7.34C15.18 4.58 17.64 4 19.22 4M19.22 2C17.24 2 14.24 2.69 11 5.93C8.81 8.12 7.5 10.53 6.65 12.64C6.37 13.39 6.56 14.21 7.11 14.77L9.24 16.89C9.62 17.27 10.13 17.5 10.66 17.5C10.89 17.5 11.13 17.44 11.36 17.35C13.5 16.53 15.88 15.19 18.07 13C23.73 7.34 21.61 2.39 21.61 2.39S20.7 2 19.22 2M14.54 9.46C13.76 8.68 13.76 7.41 14.54 6.63S16.59 5.85 17.37 6.63C18.14 7.41 18.15 8.68 17.37 9.46C16.59 10.24 15.32 10.24 14.54 9.46M8.88 16.53L7.47 15.12L8.88 16.53M6.24 22L9.88 18.36C9.54 18.27 9.21 18.12 8.91 17.91L4.83 22H6.24M2 22H3.41L8.18 17.24L6.76 15.83L2 20.59V22M2 19.17L6.09 15.09C5.88 14.79 5.73 14.47 5.64 14.12L2 17.76V19.17Z"),
LAUNCH_OUTLINE("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
SCRIPT("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z"),
PENCIL("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z"),
PENCIL_OUTLINE("M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z"),
REFRESH("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"),
FOLDER_OPEN("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z"),
FOLDER_OUTLINE("M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z"),
UPDATE("M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z"),
CLOSE_CIRCLE("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z"),
CHECK_CIRCLE("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"),
INFO_CIRCLE("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
HELP_CIRCLE("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"),
HELP_CIRCLE_OUTLINE("M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z"),
ALERT("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z"),
ALERT_OUTLINE("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"),
PLUS("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"),
PLUS_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"),
MINUS("M19 13H5V11H19V13Z"),
IMPORT_ICON("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z"),
EXPORT("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z"),
OPEN_IN_NEW("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
TRIANGLE("M1,21H23L12,2"),
HOME("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"),
VIEW_LIST("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z"),
CHECK("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"),
ARROW_RIGHT("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"),
WRENCH("M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z"),
WRENCH_OUTLINE("M22.61,19L13.53,9.91C14.46,7.57 14,4.81 12.09,2.91C9.79,0.61 6.21,0.4 3.66,2.26L7.5,6.11L6.08,7.5L2.25,3.69C0.39,6.23 0.6,9.82 2.9,12.11C4.76,13.97 7.47,14.46 9.79,13.59L18.9,22.7C19.29,23.09 19.92,23.09 20.31,22.7L22.61,20.4C23,20 23,19.39 22.61,19M19.61,20.59L10.15,11.13C9.54,11.58 8.86,11.85 8.15,11.95C6.79,12.15 5.36,11.74 4.32,10.7C3.37,9.76 2.93,8.5 3,7.26L6.09,10.35L10.33,6.11L7.24,3C8.5,2.95 9.73,3.39 10.68,4.33C11.76,5.41 12.17,6.9 11.92,8.29C11.8,9 11.5,9.66 11.04,10.25L20.5,19.7L19.61,20.59Z"),
UPLOAD("M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z"),
HANGER("M12 4A3.5 3.5 0 0 0 8.5 7.5H10.5A1.5 1.5 0 0 1 12 6A1.5 1.5 0 0 1 13.5 7.5A1.5 1.5 0 0 1 12 9C11.45 9 11 9.45 11 10V11.75L2.4 18.2A1 1 0 0 0 3 20H21A1 1 0 0 0 21.6 18.2L13 11.75V10.85A3.5 3.5 0 0 0 15.5 7.5A3.5 3.5 0 0 0 12 4M12 13.5L18 18H6Z"),
PUZZLE("M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z"),
CUBE("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"),
PACK("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"),
TEXTURE_BOX("M20 2H4C2.9 2 2 2.9 2 4V20C2 21.11 2.9 22 4 22H20C21.11 22 22 21.11 22 20V4C22 2.9 21.11 2 20 2M4 6L6 4H10.9L4 10.9V6M4 13.7L13.7 4H18.6L4 18.6V13.7M20 18L18 20H13.1L20 13.1V18M20 10.3L10.3 20H5.4L20 5.4V10.3Z"),
GAMEPAD("M6,9H8V11H10V13H8V15H6V13H4V11H6V9M18.5,9A1.5,1.5 0 0,1 20,10.5A1.5,1.5 0 0,1 18.5,12A1.5,1.5 0 0,1 17,10.5A1.5,1.5 0 0,1 18.5,9M15.5,12A1.5,1.5 0 0,1 17,13.5A1.5,1.5 0 0,1 15.5,15A1.5,1.5 0 0,1 14,13.5A1.5,1.5 0 0,1 15.5,12M17,5A7,7 0 0,1 24,12A7,7 0 0,1 17,19C15.04,19 13.27,18.2 12,16.9C10.73,18.2 8.96,19 7,19A7,7 0 0,1 0,12A7,7 0 0,1 7,5H17M7,7A5,5 0 0,0 2,12A5,5 0 0,0 7,17C8.64,17 10.09,16.21 11,15H13C13.91,16.21 15.36,17 17,17A5,5 0 0,0 22,12A5,5 0 0,0 17,7H7Z"),
FIRE("M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08 13.88 8.04 13.83 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2M14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5H14.5Z"),
MONITOR_SCREENSHOT("M9,6H5V10H7V8H9M19,10H17V12H15V14H19M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2"),
TEXTURE("M9.29,21H12.12L21,12.12V9.29M19,21C19.55,21 20.05,20.78 20.41,20.41C20.78,20.05 21,19.55 21,19V17L17,21M5,3A2,2 0 0,0 3,5V7L7,3M11.88,3L3,11.88V14.71L14.71,3M19.5,3.08L3.08,19.5C3.17,19.85 3.35,20.16 3.59,20.41C3.84,20.65 4.15,20.83 4.5,20.92L20.93,4.5C20.74,3.8 20.2,3.26 19.5,3.08Z"),
ALPHA_CIRCLE_OUTLINE("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z"),
BETA_CIRCLE_OUTLINE("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"),
RELEASE_CIRCLE_OUTLINE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"),
INFORMATION_OUTLINE("M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"),
HAND_HEAR_OUTLINE("M16 3.23C16.71 2.41 17.61 2 18.7 2C19.61 2 20.37 2.33 21 3C21.63 3.67 21.96 4.43 22 5.3C22 6 21.67 6.81 21 7.76S19.68 9.5 19.03 10.15C18.38 10.79 17.37 11.74 16 13C14.61 11.74 13.59 10.79 12.94 10.15S11.63 8.71 10.97 7.76C10.31 6.81 10 6 10 5.3C10 4.39 10.32 3.63 10.97 3C11.62 2.37 12.4 2.04 13.31 2C14.38 2 15.27 2.41 16 3.23M22 19V20L14 22.5L7 20.56V22H1V11H8.97L15.13 13.3C16.25 13.72 17 14.8 17 16H19C20.66 16 22 17.34 22 19M5 20V13H3V20H5M19.9 18.57C19.74 18.24 19.39 18 19 18H13.65C13.11 18 12.58 17.92 12.07 17.75L9.69 16.96L10.32 15.06L12.7 15.85C13 15.95 15 16 15 16C15 15.63 14.77 15.3 14.43 15.17L8.61 13H7V18.5L13.97 20.41L19.9 18.57Z"),
MOJANG("m 13.965847,0 c -1.010612,0.82802228 -1.197232,2.1950303 -1.265172,3.4179557 0.02123,1.0021189 1.341654,1.2994159 1.953117,0.590289 C 15.091158,2.6579315 14.369242,1.2738804 13.965847,0 Z M 10.913012,2.9296764 C 10.755901,3.6982508 10.628413,4.4668999 10.492533,5.2354744 8.9893533,3.9913178 7.1504705,3.0824837 5.1419856,3.2947971 3.4944341,3.4646478 0.94293227,2.6961479 0.14038761,4.645185 -0.12288102,8.3139606 0.07223999,12.01236 0.03402357,15.689629 c -0.12314178,1.222925 0.86170213,2.420422 2.14407513,2.280295 4.2207899,0.03397 8.4502143,0.04723 12.6710043,-0.0038 1.265389,0.135918 1.957646,-1.010748 2.13599,-2.0893 C 13.269608,16.437357 9.1760813,16.929609 5.7111265,15.129192 2.5986124,13.58355 2.246023,8.3138817 5.5581114,6.7979639 9.3203049,5.1758896 13.859607,8.0382886 14.942405,11.787743 15.613316,12.11046 16.284433,12.424684 16.95959,12.743154 16.624135,10.348258 16.653651,7.800456 15.579346,5.5881508 15.057054,4.7473897 14.097531,6.2714182 13.379911,5.6217388 12.416008,4.865903 11.749527,3.8128948 10.913012,2.9296713 Z"),
MICROSOFT("M2,3H11V12H2V3M11,22H2V13H11V22M21,3V12H12V3H21M21,22H12V13H21V22Z"),
ACCOUNT_OUTLINE("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z"),
ACCOUNT_GROUP_OUTLINE("M12,5A3.5,3.5 0 0,0 8.5,8.5A3.5,3.5 0 0,0 12,12A3.5,3.5 0 0,0 15.5,8.5A3.5,3.5 0 0,0 12,5M12,7A1.5,1.5 0 0,1 13.5,8.5A1.5,1.5 0 0,1 12,10A1.5,1.5 0 0,1 10.5,8.5A1.5,1.5 0 0,1 12,7M5.5,8A2.5,2.5 0 0,0 3,10.5C3,11.44 3.53,12.25 4.29,12.68C4.65,12.88 5.06,13 5.5,13C5.94,13 6.35,12.88 6.71,12.68C7.08,12.47 7.39,12.17 7.62,11.81C6.89,10.86 6.5,9.7 6.5,8.5C6.5,8.41 6.5,8.31 6.5,8.22C6.2,8.08 5.86,8 5.5,8M18.5,8C18.14,8 17.8,8.08 17.5,8.22C17.5,8.31 17.5,8.41 17.5,8.5C17.5,9.7 17.11,10.86 16.38,11.81C16.5,12 16.63,12.15 16.78,12.3C16.94,12.45 17.1,12.58 17.29,12.68C17.65,12.88 18.06,13 18.5,13C18.94,13 19.35,12.88 19.71,12.68C20.47,12.25 21,11.44 21,10.5A2.5,2.5 0 0,0 18.5,8M12,14C9.66,14 5,15.17 5,17.5V19H19V17.5C19,15.17 14.34,14 12,14M4.71,14.55C2.78,14.78 0,15.76 0,17.5V19H3V17.07C3,16.06 3.69,15.22 4.71,14.55M19.29,14.55C20.31,15.22 21,16.06 21,17.07V19H24V17.5C24,15.76 21.22,14.78 19.29,14.55M12,16C13.53,16 15.24,16.5 16.23,17H7.77C8.76,16.5 10.47,16 12,16Z"),
ACCOUNT_ARROW_RIGHT_OUTLINE("M19,21V19H15V17H19V15L22,18L19,21M13,18C13,18.71 13.15,19.39 13.42,20H2V17C2,14.79 5.58,13 10,13C11,13 11.96,13.09 12.85,13.26C13.68,13.42 14.44,13.64 15.11,13.92C13.83,14.83 13,16.32 13,18M4,17V18H11C11,16.96 11.23,15.97 11.64,15.08L10,15C6.69,15 4,15.9 4,17M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,6A2,2 0 0,0 8,8A2,2 0 0,0 10,10A2,2 0 0,0 12,8A2,2 0 0,0 10,6Z"),
STYLE_OUTLINE("M2.5 19.6L3.8 20.2V11.2L1.4 17C1 18.1 1.5 19.2 2.5 19.6M15.2 4.8L20.2 16.8L12.9 19.8L7.9 7.9V7.8L15.2 4.8M15.3 2.8C15 2.8 14.8 2.8 14.5 2.9L7.1 6C6.4 6.3 5.9 7 5.9 7.8C5.9 8 5.9 8.3 6 8.6L11 20.5C11.3 21.3 12 21.7 12.8 21.7C13.1 21.7 13.3 21.7 13.6 21.6L21 18.5C22 18.1 22.5 16.9 22.1 15.9L17.1 4C16.8 3.2 16 2.8 15.3 2.8M10.5 9.9C9.9 9.9 9.5 9.5 9.5 8.9S9.9 7.9 10.5 7.9C11.1 7.9 11.5 8.4 11.5 8.9S11.1 9.9 10.5 9.9M5.9 19.8C5.9 20.9 6.8 21.8 7.9 21.8H9.3L5.9 13.5V19.8Z"),
DOWNLOAD_OUTLINE("M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z"),
APPLICATION_OUTLINE("M21 2H3C1.9 2 1 2.9 1 4V20C1 21.1 1.9 22 3 22H21C22.1 22 23 21.1 23 20V4C23 2.9 22.1 2 21 2M21 20H3V6H21V20Z"),
EARTH("M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
BELL("M21,19V20H3V19L5,17V11C5,7.9 7.03,5.17 10,4.29C10,4.19 10,4.1 10,4A2,2 0 0,1 12,2A2,2 0 0,1 14,4C14,4.1 14,4.19 14,4.29C16.97,5.17 19,7.9 19,11V17L21,19M14,21A2,2 0 0,1 12,23A2,2 0 0,1 10,21"),
CONTENT_SAVE_MOVE_OUTLINE("M13 17H17V14L22 18.5L17 23V20H13V17M14 12.8C13.5 12.31 12.78 12 12 12C10.34 12 9 13.34 9 15C9 16.31 9.84 17.41 11 17.82C11.07 15.67 12.27 13.8 14 12.8M11.09 19H5V5H16.17L19 7.83V12.35C19.75 12.61 20.42 13 21 13.54V7L17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H11.81C11.46 20.39 11.21 19.72 11.09 19M6 10H15V6H6V10Z"),
ACCOUNT("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"),
SERVER("M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z"),
MESSAGE_ALERT_OUTLINE("M13,10H11V6H13V10M13,12H11V14H13V12M22,4V16A2,2 0 0,1 20,18H6L2,22V4A2,2 0 0,1 4,2H20A2,2 0 0,1 22,4M20,4H4V17.2L5.2,16H20V4Z"),
CHECK_CIRCLE_OUTLINE("M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z"),
CLOSE_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z"),
CLOCK_OUTLINE("M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z"),
MAGNIFY("M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"),
MENU_UP("M7,15L12,10L17,15H7Z"),
MENU_DOWN("M7,10L12,15L17,10H7Z"),
RESTORE("M13,3A9,9 0 0,0 4,12H1L4.89,15.89L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3Z"),
BUG("M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z"),
DISCORD("M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z"),
LAN("M10,2C8.89,2 8,2.89 8,4V7C8,8.11 8.89,9 10,9H11V11H2V13H6V15H5C3.89,15 3,15.89 3,17V20C3,21.11 3.89,22 5,22H9C10.11,22 11,21.11 11,20V17C11,15.89 10.11,15 9,15H8V13H16V15H15C13.89,15 13,15.89 13,17V20C13,21.11 13.89,22 15,22H19C20.11,22 21,21.11 21,20V17C21,15.89 20.11,15 19,15H18V13H22V11H13V9H14C15.11,9 16,8.11 16,7V4C16,2.89 15.11,2 14,2H10M10,4H14V7H10V4M5,17H9V20H5V17M15,17H19V20H15V17Z"),
THUMB_UP_OUTLINE("M5,9V21H1V9H5M9,21A2,2 0 0,1 7,19V9C7,8.45 7.22,7.95 7.59,7.59L14.17,1L15.23,2.06C15.5,2.33 15.67,2.7 15.67,3.11L15.64,3.43L14.69,8H21C22.11,8 23,8.9 23,10V12C23,12.26 22.95,12.5 22.86,12.73L19.84,19.78C19.54,20.5 18.83,21 18,21H9M9,19H18.03L21,12V10H12.21L13.34,4.68L9,9.03V19Z"),
THUMB_DOWN_OUTLINE("M19,15V3H23V15H19M15,3A2,2 0 0,1 17,5V15C17,15.55 16.78,16.05 16.41,16.41L9.83,23L8.77,21.94C8.5,21.67 8.33,21.3 8.33,20.88L8.36,20.57L9.31,16H3C1.89,16 1,15.1 1,14V12C1,11.74 1.05,11.5 1.14,11.27L4.16,4.22C4.46,3.5 5.17,3 6,3H15M15,5H5.97L3,12V14H11.78L10.65,19.32L15,14.97V5Z"),
SELECT_ALL("M9,9H15V15H9M7,17H17V7H7M15,5H17V3H15M15,21H17V19H15M19,17H21V15H19M19,9H21V7H19M19,21A2,2 0 0,0 21,19H19M19,13H21V11H19M11,21H13V19H11M9,3H7V5H9M3,17H5V15H3M5,21V19H3A2,2 0 0,0 5,21M19,3V5H21A2,2 0 0,0 19,3M13,3H11V5H13M3,9H5V7H3M7,21H9V19H7M3,13H5V11H3M3,5H5V3A2,2 0 0,0 3,5Z"),
JAVA("m 12.400746,23.498132 c 0,0 -1.047682,0.609185 0.745753,0.816159 2.173248,0.247515 3.284943,0.212301 5.680103,-0.241112 0,0 0.629462,0.394742 1.509642,0.737207 -5.369636,2.301245 -12.1518319,-0.133351 -7.934431,-1.312254 z m -0.656135,-3.003246 c 0,0 -1.175708,0.870569 0.619861,1.056206 2.321545,0.238977 4.155521,0.25925 7.32844,-0.35207 0,0 0.43849,0.444887 1.128765,0.688135 -6.492002,1.897965 -13.7233551,0.149351 -9.077066,-1.392271 z m 5.531806,-5.094318 c 1.322937,1.523496 -0.347807,2.894427 -0.347807,2.894427 0,0 3.359626,-1.733668 1.816908,-3.905821 -1.441364,-2.024926 -2.545591,-3.030987 3.43644,-6.5004569 0,0 -9.389665,2.3449859 -4.905541,7.5129179 z m 7.101193,10.318794 c 0,0 0.775626,0.639057 -0.854578,1.133019 C 20.42373,27.79123 10.623314,28.075018 7.9006204,26.88973 6.9222859,26.464048 8.75733,25.873001 9.3345153,25.749244 9.9362392,25.619093 10.280844,25.642568 10.280844,25.642568 9.1926195,24.875486 3.2457961,27.147927 7.2604887,27.798718 18.208875,29.573994 27.21766,26.999632 24.37761,25.718316 Z M 12.904316,17.383886 c 0,0 -4.9855587,1.184229 -1.765696,1.614178 1.359213,0.182426 4.069103,0.140825 6.594422,-0.07042 2.063359,-0.173899 4.135252,-0.544104 4.135252,-0.544104 0,0 -0.727616,0.311527 -1.253592,0.671064 -5.062375,1.331456 -14.8414526,0.711604 -12.02594,-0.649727 2.38129,-1.151156 4.316621,-1.019929 4.316621,-1.019929 z m 8.943706,4.998298 c 5.146658,-2.673583 2.766435,-5.24368 1.106361,-4.898013 -0.407551,0.08428 -0.58892,0.1579 -0.58892,0.1579 0,0 0.151501,-0.236845 0.439557,-0.339265 3.284942,-1.155424 5.812393,3.406524 -1.060485,5.213807 0,0 0.08003,-0.07147 0.103478,-0.134425 z M 18.744451,2.2855 c 0,0 2.849653,2.8506848 -2.703489,7.2344656 -4.453183,3.5164124 -1.015676,5.5221324 -0.0021,7.8127104 C 13.439893,14.987688 11.532301,12.92329 12.811497,11.001852 14.690284,8.1810394 19.893487,6.8143757 18.743384,2.2855 Z M 13.41002,29.628384 c 4.939684,0.315794 12.525242,-0.174974 12.70448,-2.512485 0,0 -0.345672,0.886571 -4.081906,1.589641 -4.216334,0.793754 -9.416337,0.700936 -12.4996379,0.192025 0,0 0.6315969,0.522768 3.8781309,0.730807 z");
ADD("M11 13H5V11H11V5H13V11H19V13H13V19H11V13Z"),
ADD_CIRCLE("M11 17H13V13H17V11H13V7H11V11H7V13H11V17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
ALPHA_CIRCLE("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z"), // Not Material
ARCHIVE("M12 18 16 14 14.6 12.6 13 14.2V10H11V14.2L9.4 12.6 8 14 12 18ZM5 8V19H19V8H5ZM5 21Q4.175 21 3.5875 20.4125T3 19V6.525Q3 6.175 3.1125 5.85T3.45 5.25L4.7 3.725Q4.975 3.375 5.3875 3.1875T6.25 3H17.75Q18.2 3 18.6125 3.1875T19.3 3.725L20.55 5.25Q20.775 5.525 20.8875 5.85T21 6.525V19Q21 19.825 20.4125 20.4125T19 21H5ZM5.4 6H18.6L17.75 5H6.25L5.4 6ZM12 13.5Z"),
ARROW_BACK("M7.825 13 13.425 18.6 12 20 4 12 12 4 13.425 5.4 7.825 11H20V13H7.825Z"),
ARROW_DROP_DOWN("M12 15 7 10H17L12 15Z"),
ARROW_DROP_UP("M7 14 12 9 17 14H7Z"),
ARROW_FORWARD("M16.175 13H4V11H16.175L10.575 5.4 12 4 20 12 12 20 10.575 18.6 16.175 13Z"),
BETA_CIRCLE("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
CANCEL("M8.4 17 12 13.4 15.6 17 17 15.6 13.4 12 17 8.4 15.6 7 12 10.6 8.4 7 7 8.4 10.6 12 7 15.6 8.4 17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
CHAT("M6 14H14V12H6V14ZM6 11H18V9H6V11ZM6 8H18V6H6V8ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
CHECK("M9.55 18 3.85 12.3 5.275 10.875 9.55 15.15 18.725 5.975 20.15 7.4 9.55 18Z"),
CHECKROOM("M3 20Q2.575 20 2.2875 19.7125T2 19Q2 18.75 2.1 18.5375T2.4 18.2L11 11.75V10Q11 9.575 11.3 9.2875T12.025 9Q12.65 9 13.075 8.55T13.5 7.475Q13.5 6.85 13.0625 6.425T12 6Q11.375 6 10.9375 6.4375T10.5 7.5H8.5Q8.5 6.05 9.525 5.025T12 4Q13.45 4 14.475 5.0125T15.5 7.475Q15.5 8.65 14.8125 9.575T13 10.85V11.75L21.6 18.2Q21.8 18.325 21.9 18.5375T22 19Q22 19.425 21.7125 19.7125T21 20H3ZM6 18H18L12 13.5 6 18Z"),
CHECK_CIRCLE("M10.6 16.6 17.65 9.55 16.25 8.15 10.6 13.8 7.75 10.95 6.35 12.35 10.6 16.6ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
CLOSE("M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z"),
CONTENT_COPY("M9 18Q8.175 18 7.5875 17.4125T7 16V4Q7 3.175 7.5875 2.5875T9 2H18Q18.825 2 19.4125 2.5875T20 4V16Q20 16.825 19.4125 17.4125T18 18H9ZM9 16H18V4H9V16ZM5 22Q4.175 22 3.5875 21.4125T3 20V6H5V20H16V22H5ZM9 16V4 16Z"),
DELETE("M7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17ZM7 6V19 6Z"),
DELETE_FOREVER("M9.4 16.5 12 13.9 14.6 16.5 16 15.1 13.4 12.5 16 9.9 14.6 8.5 12 11.1 9.4 8.5 8 9.9 10.6 12.5 8 15.1 9.4 16.5ZM7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM7 6V19 6Z"),
DEPLOYED_CODE("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM12 10.85 17.925 7.425 12 4 6.075 7.425 12 10.85ZM4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725L4 17.7ZM12 12Z"),
DOWNLOAD("M12 16 7 11 8.4 9.55 11 12.15V4H13V12.15L15.6 9.55 17 11 12 16ZM6 20Q5.175 20 4.5875 19.4125T4 18V15H6V18H18V15H20V18Q20 18.825 19.4125 19.4125T18 20H6Z"),
DRESSER("M4 21V5Q4 4.175 4.5875 3.5875T6 3H18Q18.825 3 19.4125 3.5875T20 5V21H18V19H6V21H4ZM6 11H11V5H6V11ZM13 7H18V5H13V7ZM13 11H18V9H13V11ZM10 16H14V14H10V16ZM6 13V17H18V13H6ZM6 13V17 13Z"),
EDIT("M5 19H6.425L16.2 9.225 14.775 7.8 5 17.575V19ZM3 21V16.75L16.2 3.575Q16.5 3.3 16.8625 3.15T17.625 3Q18.025 3 18.4 3.15T19.05 3.6L20.425 5Q20.725 5.275 20.8625 5.65T21 6.4Q21 6.8 20.8625 7.1625T20.425 7.825L7.25 21H3ZM19 6.4 17.6 5 19 6.4ZM15.475 8.525 14.775 7.8 16.2 9.225 15.475 8.525Z"),
ERROR("M12 17Q12.425 17 12.7125 16.7125T13 16Q13 15.575 12.7125 15.2875T12 15Q11.575 15 11.2875 15.2875T11 16Q11 16.425 11.2875 16.7125T12 17ZM11 13H13V7H11V13ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
EXTENSION("M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z"),
FEEDBACK("M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
FOLDER("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8V18Q22 18.825 21.4125 19.4125T20 20H4ZM4 18H20V8H11.175L9.175 6H4V18ZM4 18V6 18Z"),
FOLDER_COPY("M3 21Q2.175 21 1.5875 20.4125T1 19V6H3V19H20V21H3ZM7 17Q6.175 17 5.5875 16.4125T5 15V4Q5 3.175 5.5875 2.5875T7 2H12L14 4H21Q21.825 4 22.4125 4.5875T23 6V15Q23 15.825 22.4125 16.4125T21 17H7ZM7 15H21V6H13.175L11.175 4H7V15ZM7 15V4 15Z"),
FOLDER_OPEN("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8H11.175L9.175 6H4V18L6.4 10H23.5L20.925 18.575Q20.725 19.225 20.1875 19.6125T19 20H4ZM6.1 18H19L20.8 12H7.9L6.1 18ZM6.1 18 7.9 12 6.1 18ZM4 8V6 8Z"),
FORMAT_LIST_BULLETED("M9 19V17H21V19H9ZM9 13V11H21V13H9ZM9 7V5H21V7H9ZM5 20Q4.175 20 3.5875 19.4125T3 18Q3 17.175 3.5875 16.5875T5 16Q5.825 16 6.4125 16.5875T7 18Q7 18.825 6.4125 19.4125T5 20ZM5 14Q4.175 14 3.5875 13.4125T3 12Q3 11.175 3.5875 10.5875T5 10Q5.825 10 6.4125 10.5875T7 12Q7 12.825 6.4125 13.4125T5 14ZM5 8Q4.175 8 3.5875 7.4125T3 6Q3 5.175 3.5875 4.5875T5 4Q5.825 4 6.4125 4.5875T7 6Q7 6.825 6.4125 7.4125T5 8Z"),
FOR_YOU("M12 12Q14.025 12 16.225 11.5875T20 10.5V20.5Q18.5 21.175 16.35 21.5875T12 22Q9.8 22 7.65 21.5875T4 20.5V10.5Q5.575 11.175 7.775 11.5875T12 12ZM18 19V13.25Q16.75 13.6 15.1125 13.8T12 14Q10.525 14 8.8875 13.8T6 13.25V19Q7.25 19.45 8.875 19.725T12 20Q13.5 20 15.125 19.725T18 19ZM12 2Q13.65 2 14.825 3.175T16 6Q16 7.65 14.825 8.825T12 10Q10.35 10 9.175 8.825T8 6Q8 4.35 9.175 3.175T12 2ZM12 8Q12.825 8 13.4125 7.4125T14 6Q14 5.175 13.4125 4.5875T12 4Q11.175 4 10.5875 4.5875T10 6Q10 6.825 10.5875 7.4125T12 8ZM12 6ZM12 16.625Z"),
GAMEPAD("M12 7.65ZM16.35 12ZM7.65 12ZM12 16.35ZM12 10.5 9 7.5V2H15V7.5L12 10.5ZM16.5 15 13.5 12 16.5 9H22V15H16.5ZM2 15V9H7.5L10.5 12 7.5 15H2ZM9 22V16.5L12 13.5 15 16.5V22H9ZM12 7.65 13 6.65V4H11V6.65L12 7.65ZM4 13H6.65L7.65 12 6.65 11H4V13ZM11 20H13V17.35L12 16.35 11 17.35V20ZM17.35 13H20V11H17.35L16.35 12 17.35 13Z"),
GLOBE_BOOK("M3.075 13Q3.05 12.75 3.0375 12.5T3.025 12Q3.025 10.125 3.725 8.4875T5.65 5.6375Q6.875 4.425 8.5 3.7125T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 12.25 20.9875 12.5T20.95 13H18.925Q18.975 12.75 18.9875 12.5T19 12Q19 11.75 18.9875 11.5T18.925 11H15.975Q16 11.25 16 11.5V12.5Q16 12.75 15.975 13H14V12.175Q14 11.875 13.9875 11.575T13.95 11H10.075Q10.05 11.275 10.0375 11.575T10.025 12.175V13H8.05Q8.025 12.75 8.025 12.5V11.5Q8.025 11.25 8.05 11H5.1Q5.05 11.25 5.0375 11.5T5.025 12Q5.025 12.25 5.0375 12.5T5.1 13H3.075ZM5.7 9H8.275Q8.475 7.925 8.775 7.0625T9.425 5.5Q8.225 5.95 7.25 6.8625T5.7 9ZM10.35 9H13.65Q13.4 7.925 13.025 6.9T12 5Q11.35 5.875 10.9625 6.9T10.35 9ZM15.75 9H18.325Q17.75 7.775 16.7625 6.8625T14.575 5.5Q14.925 6.25 15.2375 7.0875T15.75 9ZM11 21V20Q11 18.75 10.125 17.875T8 17H2V15H8Q9.2 15 10.2375 15.525T12 17Q12.725 16.05 13.7625 15.525T16 15H22V17H16Q14.75 17 13.875 17.875T13 20V21H11Z"),
HELP("M11.95 18Q12.475 18 12.8375 17.6375T13.2 16.75Q13.2 16.225 12.8375 15.8625T11.95 15.5Q11.425 15.5 11.0625 15.8625T10.7 16.75Q10.7 17.275 11.0625 17.6375T11.95 18ZM11.05 14.15H12.9Q12.9 13.325 13.0875 12.85T14.15 11.55Q14.8 10.9 15.175 10.3125T15.55 8.9Q15.55 7.5 14.525 6.75T12.1 6Q10.675 6 9.7875 6.75T8.55 8.55L10.2 9.2Q10.325 8.75 10.7625 8.225T12.1 7.7Q12.9 7.7 13.3 8.1375T13.7 9.1Q13.7 9.6 13.4 10.0375T12.65 10.85Q11.55 11.825 11.3 12.325T11.05 14.15ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
HOME("M6 19H9V13H15V19H18V10L12 5.5 6 10V19ZM4 21V9L12 3 20 9V21H13V15H11V21H4ZM12 12.25Z"),
HOST("M4 21Q3.175 21 2.5875 20.4125T2 19V5Q2 4.175 2.5875 3.5875T4 3H9Q9.825 3 10.4125 3.5875T11 5V19Q11 19.825 10.4125 20.4125T9 21H4ZM15 21Q14.175 21 13.5875 20.4125T13 19V5Q13 4.175 13.5875 3.5875T15 3H20Q20.825 3 21.4125 3.5875T22 5V19Q22 19.825 21.4125 20.4125T20 21H15ZM4 19H9V5H4V19ZM15 19H20V5H15V19ZM5 15H8V13H5V15ZM16 15H19V13H16V15ZM5 12H8V10H5V12ZM16 12H19V10H16V12ZM5 9H8V7H5V9ZM16 9H19V7H16V9ZM4 19H9 4ZM15 19H20 15Z"),
INFO("M11 17H13V11H11V17ZM12 9Q12.425 9 12.7125 8.7125T13 8Q13 7.575 12.7125 7.2875T12 7Q11.575 7 11.2875 7.2875T11 8Q11 8.425 11.2875 8.7125T12 9ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
KEYBOARD_ARROW_DOWN("M12 15.4 6 9.4 7.4 8 12 12.6 16.6 8 18 9.4 12 15.4Z"),
KEYBOARD_ARROW_UP("M12 10.8 7.4 15.4 6 14 12 8 18 14 16.6 15.4 12 10.8Z"),
LIST("M7 9V7H21V9H7ZM7 13V11H21V13H7ZM7 17V15H21V17H7ZM4 9Q3.575 9 3.2875 8.7125T3 8Q3 7.575 3.2875 7.2875T4 7Q4.425 7 4.7125 7.2875T5 8Q5 8.425 4.7125 8.7125T4 9ZM4 13Q3.575 13 3.2875 12.7125T3 12Q3 11.575 3.2875 11.2875T4 11Q4.425 11 4.7125 11.2875T5 12Q5 12.425 4.7125 12.7125T4 13ZM4 17Q3.575 17 3.2875 16.7125T3 16Q3 15.575 3.2875 15.2875T4 15Q4.425 15 4.7125 15.2875T5 16Q5 16.425 4.7125 16.7125T4 17Z"),
LISTS("M2 20V16H6V20H2ZM8 20V16H22V20H8ZM2 14V10H6V14H2ZM8 14V10H22V14H8ZM2 8V4H6V8H2ZM8 8V4H22V8H8Z"),
LOCAL_CAFE("M4 21V19H20V21H4ZM8 17Q6.35 17 5.175 15.825T4 13V3H20Q20.825 3 21.4125 3.5875T22 5V8Q22 8.825 21.4125 9.4125T20 10H18V13Q18 14.65 16.825 15.825T14 17H8ZM8 15H14Q14.825 15 15.4125 14.4125T16 13V5H6V13Q6 13.825 6.5875 14.4125T8 15ZM18 8H20V5H18V8ZM8 15H6 16 8Z"),
MENU("M3 18V16H21V18H3ZM3 13V11H21V13H3ZM3 8V6H21V8H3Z"),
MICROSOFT("M4 20H22v2H4V13H20v7h2V4H20v7H4V4h7V20h2V4h9V2H2V22H4"), // Not Material
MINIMIZE("M6 21V19H18V21H6Z"),
MOJANG("M13.9658 0C12.9552.828 12.7686 2.195 12.7007 3.418 12.7219 4.4201 14.0423 4.7174 14.6538 4.0082 15.0912 2.6579 14.3692 1.2739 13.9658 0ZM10.913 2.9297C10.7559 3.6983 10.6284 4.4669 10.4925 5.2355 8.9894 3.9913 7.1505 3.0825 5.142 3.2948 3.4944 3.4646.9429 2.6961.1404 4.6452-.1229 8.314.0722 12.0124.034 15.6896-.0891 16.9126.8957 18.1101 2.1781 17.9699 6.3989 18.0039 10.6283 18.0172 14.8491 17.9661 16.1145 18.102 16.8067 16.9554 16.9851 15.8768 13.2696 16.4374 9.1761 16.9296 5.7111 15.1292 2.5986 13.5836 2.246 8.3139 5.5581 6.798 9.3203 5.1759 13.8596 8.0383 14.9424 11.7877 15.6133 12.1105 16.2844 12.4247 16.9596 12.7432 16.6241 10.3483 16.6537 7.8005 15.5793 5.5882 15.0571 4.7474 14.0975 6.2714 13.3799 5.6217 12.416 4.8659 11.7495 3.8129 10.913 2.9297Z"), // Not Material
MORE_HORIZ("M6 14Q5.175 14 4.5875 13.4125T4 12Q4 11.175 4.5875 10.5875T6 10Q6.825 10 7.4125 10.5875T8 12Q8 12.825 7.4125 13.4125T6 14ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM18 14Q17.175 14 16.5875 13.4125T16 12Q16 11.175 16.5875 10.5875T18 10Q18.825 10 19.4125 10.5875T20 12Q20 12.825 19.4125 13.4125T18 14Z"),
MORE_VERT("M12 20Q11.175 20 10.5875 19.4125T10 18Q10 17.175 10.5875 16.5875T12 16Q12.825 16 13.4125 16.5875T14 18Q14 18.825 13.4125 19.4125T12 20ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM12 8Q11.175 8 10.5875 7.4125T10 6Q10 5.175 10.5875 4.5875T12 4Q12.825 4 13.4125 4.5875T14 6Q14 6.825 13.4125 7.4125T12 8Z"),
OPEN_IN_NEW("M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H12V5H5V19H19V12H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM9.7 15.7 8.3 14.3 17.6 5H14V3H21V10H19V6.4L9.7 15.7Z"),
OUTPUT("M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V7H19V5H5V19H19V17H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM17 17 15.6 15.6 18.175 13H9V11H18.175L15.6 8.4 17 7 22 12 17 17Z"),
PACKAGE("M10 9.75 12 8.75 14 9.75V5H10V9.75ZM7 17V15H12V17H7ZM5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V19Q21 19.825 20.4125 20.4125T19 21H5ZM5 5V19 5ZM5 19H19V5H16V13L12 11 8 13V5H5V19Z"),
PACKAGE2("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM11 21.725 4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725ZM16 8.525 17.925 7.425 12 4 10.05 5.125 16 8.525ZM12 10.85 13.95 9.725 8.025 6.3 6.075 7.425 12 10.85Z"),
PERSON("M12 12Q10.35 12 9.175 10.825T8 8Q8 6.35 9.175 5.175T12 4Q13.65 4 14.825 5.175T16 8Q16 9.65 14.825 10.825T12 12ZM4 20V17.2Q4 16.35 4.4375 15.6375T5.6 14.55Q7.15 13.775 8.75 13.3875T12 13Q13.65 13 15.25 13.3875T18.4 14.55Q19.125 14.925 19.5625 15.6375T20 17.2V20H4ZM6 18H18V17.2Q18 16.925 17.8625 16.7T17.5 16.35Q16.15 15.675 14.775 15.3375T12 15Q10.6 15 9.225 15.3375T6.5 16.35Q6.275 16.475 6.1375 16.7T6 17.2V18ZM12 10Q12.825 10 13.4125 9.4125T14 8Q14 7.175 13.4125 6.5875T12 6Q11.175 6 10.5875 6.5875T10 8Q10 8.825 10.5875 9.4125T12 10ZM12 8ZM12 18Z"),
PUBLIC("M12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM11 19.95V18Q10.175 18 9.5875 17.4125T9 16V15L4.2 10.2Q4.125 10.65 4.0625 11.1T4 12Q4 15.025 5.9875 17.3T11 19.95ZM17.9 17.4Q18.925 16.275 19.4625 14.8875T20 12Q20 9.55 18.6375 7.525T15 4.6V5Q15 5.825 14.4125 6.4125T13 7H11V9Q11 9.425 10.7125 9.7125T10 10H8V12H14Q14.425 12 14.7125 12.2875T15 13V16H16Q16.65 16 17.175 16.3875T17.9 17.4Z"),
REFRESH("M12 20Q8.65 20 6.325 17.675T4 12Q4 8.65 6.325 6.325T12 4Q13.725 4 15.3 4.7125T18 6.75V4H20V11H13V9H17.2Q16.4 7.6 15.0125 6.8T12 6Q9.5 6 7.75 7.75T6 12Q6 14.5 7.75 16.25T12 18Q13.925 18 15.475 16.9T17.65 14H19.75Q19.05 16.65 16.9 18.325T12 20Z"),
RELEASE_CIRCLE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
RESTORE("M12 21Q8.55 21 5.9875 18.7125T3.05 13H5.1Q5.45 15.6 7.4125 17.3T12 19Q14.925 19 16.9625 16.9625T19 12Q19 9.075 16.9625 7.0375T12 5Q10.275 5 8.775 5.8T6.25 8H9V10H3V4H5V6.35Q6.275 4.75 8.1125 3.875T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 13.875 20.2875 15.5125T18.3625 18.3625Q17.15 19.575 15.5125 20.2875T12 21Z"), // Not Material
ROCKET_LAUNCH("M5.65 10.025 7.6 10.85Q7.95 10.15 8.325 9.5T9.15 8.2L7.75 7.925 5.65 10.025ZM9.2 12.1 12.05 14.925Q13.1 14.525 14.3 13.7T16.55 11.825Q18.3 10.075 19.2875 7.9375T20.15 4Q18.35 3.875 16.2 4.8625T12.3 7.6Q11.25 8.65 10.425 9.85T9.2 12.1ZM13.65 10.475Q13.075 9.9 13.075 9.0625T13.65 7.65Q14.225 7.075 15.075 7.075T16.5 7.65Q17.075 8.225 17.075 9.0625T16.5 10.475Q15.925 11.05 15.075 11.05T13.65 10.475ZM14.125 18.5 16.225 16.4 15.95 15Q15.3 15.45 14.65 15.8125T13.3 16.525L14.125 18.5ZM21.95 2.175Q22.425 5.2 21.3625 8.0625T17.7 13.525L18.2 16Q18.3 16.5 18.15 16.975T17.65 17.8L13.45 22 11.35 17.075 7.075 12.8 2.15 10.7 6.325 6.5Q6.675 6.15 7.1625 6T8.15 5.95L10.625 6.45Q13.225 3.85 16.075 2.775T21.95 2.175ZM3.925 15.975Q4.8 15.1 6.0625 15.0875T8.2 15.95Q9.075 16.825 9.0625 18.0875T8.175 20.225Q7.55 20.85 6.0875 21.3T2.05 22.1Q2.4 19.525 2.85 18.0625T3.925 15.975ZM5.35 17.375Q5.1 17.625 4.85 18.2875T4.5 19.625Q5.175 19.525 5.8375 19.2875T6.75 18.8Q7.05 18.5 7.075 18.075T6.8 17.35Q6.5 17.05 6.075 17.0625T5.35 17.375Z"),
SCREENSHOT_MONITOR("M15 16H19V12H17.5V14.5H15V16ZM5 10H6.5V7.5H9V6H5V10ZM8 21V19H4Q3.175 19 2.5875 18.4125T2 17V5Q2 4.175 2.5875 3.5875T4 3H20Q20.825 3 21.4125 3.5875T22 5V17Q22 17.825 21.4125 18.4125T20 19H16V21H8ZM4 17H20V5H4V17ZM4 17V5 17Z"),
SCRIPT("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z"), // Not Material
SEARCH("M19.6 21 13.3 14.7Q12.55 15.3 11.575 15.65T9.5 16Q6.775 16 4.8875 14.1125T3 9.5Q3 6.775 4.8875 4.8875T9.5 3Q12.225 3 14.1125 4.8875T16 9.5Q16 10.6 15.65 11.575T14.7 13.3L21 19.6 19.6 21ZM9.5 14Q11.375 14 12.6875 12.6875T14 9.5Q14 7.625 12.6875 6.3125T9.5 5Q7.625 5 6.3125 6.3125T5 9.5Q5 11.375 6.3125 12.6875T9.5 14Z"),
SELECT_ALL("M7 17V7H17V17H7ZM9 15H15V9H9V15ZM5 19V21Q4.175 21 3.5875 20.4125T3 19H5ZM3 17V15H5V17H3ZM3 13V11H5V13H3ZM3 9V7H5V9H3ZM5 5H3Q3 4.175 3.5875 3.5875T5 3V5ZM7 21V19H9V21H7ZM7 5V3H9V5H7ZM11 21V19H13V21H11ZM11 5V3H13V5H11ZM15 21V19H17V21H15ZM15 5V3H17V5H15ZM19 21V19H21Q21 19.825 20.4125 20.4125T19 21ZM19 17V15H21V17H19ZM19 13V11H21V13H19ZM19 9V7H21V9H19ZM19 5V3Q19.825 3 20.4125 3.5875T21 5H19Z"),
SETTINGS("M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12 19.5 11.66 19.47 11.34 19.43 11.02L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.57 5.11 19.4 5.02 19.22 5.02 19.16 5.02 19.1 5.03 19.05 5.05L16.56 6.05C16.04 5.65 15.48 5.32 14.87 5.07L14.49 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.89 5.03 4.83 5.02 4.77 5.02 4.6 5.02 4.43 5.11 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11.02C4.53 11.34 4.5 11.67 4.5 12 4.5 12.33 4.53 12.66 4.57 12.98L2.46 14.63C2.27 14.78 2.22 15.05 2.34 15.27L4.34 18.73C4.43 18.89 4.6 18.98 4.78 18.98 4.84 18.98 4.9 18.97 4.95 18.95L7.44 17.95C7.96 18.35 8.52 18.68 9.13 18.93L9.51 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.49 21.58L14.87 18.93C15.48 18.68 16.04 18.34 16.56 17.95L19.05 18.95C19.11 18.97 19.17 18.98 19.23 18.98 19.4 18.98 19.57 18.89 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.98ZM17.45 11.27C17.49 11.58 17.5 11.79 17.5 12 17.5 12.21 17.48 12.43 17.45 12.73L17.31 13.86 18.2 14.56 19.28 15.4 18.58 16.61 17.31 16.1 16.27 15.68 15.37 16.36C14.94 16.68 14.53 16.92 14.12 17.09L13.06 17.52 12.9 18.65 12.7 20H11.3L11.11 18.65 10.95 17.52 9.89 17.09C9.46 16.91 9.06 16.68 8.66 16.38L7.75 15.68 6.69 16.11 5.42 16.62 4.72 15.41 5.8 14.57 6.69 13.87 6.55 12.74C6.52 12.43 6.5 12.2 6.5 12S6.52 11.57 6.55 11.27L6.69 10.14 5.8 9.44 4.72 8.6 5.42 7.39 6.69 7.9 7.73 8.32 8.63 7.64C9.06 7.32 9.47 7.08 9.88 6.91L10.94 6.48 11.1 5.35 11.3 4H12.69L12.88 5.35 13.04 6.48 14.1 6.91C14.53 7.09 14.93 7.32 15.33 7.62L16.24 8.32 17.3 7.89 18.57 7.38 19.27 8.59 18.2 9.44 17.31 10.14 17.45 11.27ZM12 8C9.79 8 8 9.79 8 12S9.79 16 12 16 16 14.21 16 12 14.21 8 12 8ZM12 14C10.9 14 10 13.1 10 12S10.9 10 12 10 14 10.9 14 12 13.1 14 12 14Z"), // Material Icons
STADIA_CONTROLLER("M4.725 20Q3.225 20 2.1625 18.925T1.05 16.325Q1.05 16.1 1.075 15.875T1.15 15.425L3.25 7.025Q3.6 5.675 4.675 4.8375T7.125 4H16.875Q18.25 4 19.325 4.8375T20.75 7.025L22.85 15.425Q22.9 15.65 22.9375 15.8875T22.975 16.35Q22.975 17.875 21.8875 18.9375T19.275 20Q18.225 20 17.325 19.45T15.975 17.95L15.275 16.5Q15.15 16.25 14.9 16.125T14.375 16H9.625Q9.35 16 9.1 16.125T8.725 16.5L8.025 17.95Q7.575 18.9 6.675 19.45T4.725 20ZM4.8 18Q5.275 18 5.6625 17.75T6.25 17.075L6.95 15.65Q7.325 14.875 8.05 14.4375T9.625 14H14.375Q15.225 14 15.95 14.45T17.075 15.65L17.775 17.075Q17.975 17.5 18.3625 17.75T19.225 18Q19.925 18 20.425 17.5375T20.95 16.375Q20.95 16.4 20.9 15.9L18.8 7.525Q18.625 6.85 18.1 6.425T16.875 6H7.125Q6.425 6 5.8875 6.425T5.2 7.525L3.1 15.9Q3.05 16.05 3.05 16.35 3.05 17.05 3.5625 17.525T4.8 18ZM13.5 11Q13.925 11 14.2125 10.7125T14.5 10Q14.5 9.575 14.2125 9.2875T13.5 9Q13.075 9 12.7875 9.2875T12.5 10Q12.5 10.425 12.7875 10.7125T13.5 11ZM15.5 9Q15.925 9 16.2125 8.7125T16.5 8Q16.5 7.575 16.2125 7.2875T15.5 7Q15.075 7 14.7875 7.2875T14.5 8Q14.5 8.425 14.7875 8.7125T15.5 9ZM15.5 13Q15.925 13 16.2125 12.7125T16.5 12Q16.5 11.575 16.2125 11.2875T15.5 11Q15.075 11 14.7875 11.2875T14.5 12Q14.5 12.425 14.7875 12.7125T15.5 13ZM17.5 11Q17.925 11 18.2125 10.7125T18.5 10Q18.5 9.575 18.2125 9.2875T17.5 9Q17.075 9 16.7875 9.2875T16.5 10Q16.5 10.425 16.7875 10.7125T17.5 11ZM8.5 12.5Q8.825 12.5 9.0375 12.2875T9.25 11.75V10.75H10.25Q10.575 10.75 10.7875 10.5375T11 10Q11 9.675 10.7875 9.4625T10.25 9.25H9.25V8.25Q9.25 7.925 9.0375 7.7125T8.5 7.5Q8.175 7.5 7.9625 7.7125T7.75 8.25V9.25H6.75Q6.425 9.25 6.2125 9.4625T6 10Q6 10.325 6.2125 10.5375T6.75 10.75H7.75V11.75Q7.75 12.075 7.9625 12.2875T8.5 12.5ZM12 12Z"),
STYLE("M3.975 19.8 3.125 19.45Q2.35 19.125 2.0875 18.325T2.175 16.75L3.975 12.85V19.8ZM7.975 22Q7.15 22 6.5625 21.4125T5.975 20V14L8.625 21.35Q8.7 21.525 8.775 21.6875T8.975 22H7.975ZM13.125 21.9Q12.325 22.2 11.575 21.825T10.525 20.65L6.075 8.45Q5.775 7.65 6.125 6.8875T7.275 5.85L14.825 3.1Q15.625 2.8 16.375 3.175T17.425 4.35L21.875 16.55Q22.175 17.35 21.825 18.1125T20.675 19.15L13.125 21.9ZM10.975 10Q11.4 10 11.6875 9.7125T11.975 9Q11.975 8.575 11.6875 8.2875T10.975 8Q10.55 8 10.2625 8.2875T9.975 9Q9.975 9.425 10.2625 9.7125T10.975 10ZM12.425 20 19.975 17.25 15.525 5 7.975 7.75 12.425 20ZM7.975 7.75 15.525 5 7.975 7.75Z"),
TEXTURE("M4.4-3Q3.925-3.1 3.5125-3.5125T3-4.4L19.6-21Q20.125-20.875 20.5-20.4875T21.025-19.6L4.4-3ZM3-9.3V-12.1L11.9-21H14.7L3-9.3ZM3-17V-19Q3-19.825 3.5875-20.4125T5-21H7L3-17ZM17-3 21-7V-5Q21-4.175 20.4125-3.5875T19-3H17ZM9.3-3 21-14.7V-11.9L12.1-3H9.3Z"),
TRIP("M4 21Q3.175 21 2.5875 20.4125T2 19V8Q2 7.175 2.5875 6.5875T4 6H8V4Q8 3.175 8.5875 2.5875T10 2H14Q14.825 2 15.4125 2.5875T16 4V6H20Q20.825 6 21.4125 6.5875T22 8V19Q22 19.825 21.4125 20.4125T20 21H4ZM10 6H14V4H10V6ZM6 8H4V19H6V8ZM16 19V8H8V19H16ZM18 8V19H20V8H18ZM12 13.5Z"),
TUNE("M11 21V15H13V17H21V19H13V21H11ZM3 19V17H9V19H3ZM7 15V13H3V11H7V9H9V15H7ZM11 13V11H21V13H11ZM15 9V3H17V5H21V7H17V9H15ZM3 7V5H13V7H3Z"),
UPDATE("M12 21Q10.125 21 8.4875 20.2875T5.6375 18.3625Q4.425 17.15 3.7125 15.5125T3 12Q3 10.125 3.7125 8.4875T5.6375 5.6375Q6.85 4.425 8.4875 3.7125T12 3Q14.05 3 15.8875 3.875T19 6.35V4H21V10H15V8H17.75Q16.725 6.6 15.225 5.8T12 5Q9.075 5 7.0375 7.0375T5 12Q5 14.925 7.0375 16.9625T12 19Q14.625 19 16.5875 17.3T18.9 13H20.95Q20.575 16.425 18.0125 18.7125T12 21ZM14.8 16.2 11 12.4V7H13V11.6L16.2 14.8 14.8 16.2Z"),
WARNING("M1 21 12 2 23 21H1ZM4.45 19H19.55L12 6 4.45 19ZM12 18Q12.425 18 12.7125 17.7125T13 17Q13 16.575 12.7125 16.2875T12 16Q11.575 16 11.2875 16.2875T11 17Q11 17.425 11.2875 17.7125T12 18ZM11 15H13V10H11V15ZM12 12.5Z"),
WB_SUNNY("M11 4V1H13V4H11ZM11 23V20H13V23H11ZM20 13V11H23V13H20ZM1 13V11H4V13H1ZM18.7 6.7 17.3 5.3 19.05 3.5 20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3 6.7 18.7 4.95 20.5ZM19.05 20.5 17.3 18.7 18.7 17.3 20.5 19.05 19.05 20.5ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18Q9.5 18 7.75 16.25T6 12Q6 9.5 7.75 7.75T12 6Q14.5 6 16.25 7.75T18 12Q18 14.5 16.25 16.25T12 18ZM12 16Q13.675 16 14.8375 14.8375T16 12Q16 10.325 14.8375 9.1625T12 8Q10.325 8 9.1625 9.1625T8 12Q8 13.675 9.1625 14.8375T12 16ZM12 12Z"),
;
public static final double DEFAULT_SIZE = 24;
private final String path;
@@ -124,39 +118,38 @@ public enum SVG {
return path;
}
private static Node createIcon(SVGPath path, double width, double height) {
if (width < 0 || height < 0) {
private static Node createIcon(SVGPath path, double size) {
if (size < 0) {
StackPane pane = new StackPane(path);
pane.setAlignment(Pos.CENTER);
return pane;
}
Group svg = new Group(path);
double scale = Math.min(width / 24, height / 24);
double scale = size / 24;
svg.setScaleX(scale);
svg.setScaleY(scale);
return svg;
}
public Node createIcon(ObservableValue<? extends Paint> fill, double width, double height) {
public Node createIcon(ObservableValue<? extends Paint> fill, double size) {
SVGPath p = new SVGPath();
p.getStyleClass().add("svg");
p.setContent(path);
if (fill != null)
p.fillProperty().bind(fill);
return createIcon(p, width, height);
return createIcon(p, size);
}
public Node createIcon(Paint fill, double width, double height) {
public Node createIcon(Paint fill, double size) {
SVGPath p = new SVGPath();
p.getStyleClass().add("svg");
p.setContent(path);
if (fill != null)
p.fillProperty().set(fill);
return createIcon(p, width, height);
return createIcon(p, size);
}
}
@@ -89,7 +89,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-tool-bar-button");
ret.textFillProperty().bind(Theme.foregroundFillBinding());
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
ret.setText(text);
ret.setOnAction(e -> onClick.run());
return ret;
@@ -98,7 +98,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) {
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-tool-bar-button");
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1, -1)));
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1)));
ret.setText(text);
ret.setOnAction(e -> onClick.run());
return ret;
@@ -108,7 +108,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-decorator-button");
ret.textFillProperty().bind(Theme.foregroundFillBinding());
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1)));
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
FXUtils.installFastTooltip(ret, tooltip);
ret.setOnAction(e -> onClick.run());
return ret;
@@ -64,11 +64,13 @@ public final class UpgradeDialog extends JFXDialogLayout {
node = node.nextSibling();
} while (node != null);
renderer.mergeLineBreaks();
return renderer.render();
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
ScrollPane scrollPane = new ScrollPane(result);
scrollPane.setFitToWidth(true);
FXUtils.smoothScrolling(scrollPane);
setBody(scrollPane);
} else {
LOG.warning("Failed to load update log, trying to open it in browser");
@@ -53,6 +53,7 @@ public final class WebPage extends SpinnerPane implements DecoratorPage {
}, null);
});
renderer.appendNode(document);
renderer.mergeLineBreaks();
return renderer.render();
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
@@ -20,7 +20,9 @@ package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
@@ -50,13 +52,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
super(skinnable);
BorderPane root = new BorderPane();
root.setCursor(Cursor.HAND);
FXUtils.onClicked(root, skinnable::fire);
JFXRadioButton chkSelected = new JFXRadioButton() {
@Override
public void fire() {
skinnable.fire();
}
};
JFXRadioButton chkSelected = new JFXRadioButton();
chkSelected.setMouseTransparent(true);
BorderPane.setAlignment(chkSelected, Pos.CENTER);
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
root.setLeft(chkSelected);
@@ -116,10 +116,10 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
});
btnMove.getStyleClass().add("toggle-icon4");
if (skinnable.getAccount().isPortable()) {
btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
btnMove.setGraphic(SVG.PUBLIC.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
} else {
btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1));
btnMove.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
}
spinnerMove.setContent(btnMove);
@@ -141,7 +141,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
.start();
});
btnRefresh.getStyleClass().add("toggle-icon4");
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1));
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
spinnerRefresh.setContent(btnRefresh);
right.getChildren().add(spinnerRefresh);
@@ -158,10 +158,9 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
}
});
btnUpload.getStyleClass().add("toggle-icon4");
btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1));
btnUpload.setGraphic(SVG.CHECKROOM.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty());
spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin());
btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));
spinnerUpload.setContent(btnUpload);
spinnerUpload.getStyleClass().add("small-spinner-pane");
right.getChildren().add(spinnerUpload);
@@ -171,7 +170,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
btnUpload.getStyleClass().add("toggle-icon4");
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
btnCopyUUID.setGraphic(SVG.COPY.createIcon(Theme.blackFill(), -1, -1));
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
spinnerCopyUUID.setContent(btnCopyUUID);
right.getChildren().add(spinnerCopyUUID);
@@ -180,7 +179,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1));
btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
right.getChildren().add(btnRemove);
root.setRight(right);
@@ -103,7 +103,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG.ACCOUNT));
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
boxMethods.getChildren().add(offlineItem);
@@ -119,7 +119,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setLeftGraphic(wrap(SVG.SERVER));
item.setLeftGraphic(wrap(SVG.DRESSER));
item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));
JFXButton btnRemove = new JFXButton();
@@ -130,7 +130,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
e.consume();
});
btnRemove.getStyleClass().add("toggle-icon4");
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
item.setRightGraphic(btnRemove);
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
@@ -158,7 +158,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
addAuthServerItem.setTitle(i18n("account.injector.add"));
addAuthServerItem.setSubtitle(i18n("account.methods.authlib_injector"));
addAuthServerItem.setActionButtonVisible(false);
addAuthServerItem.setLeftGraphic(wrap(SVG.PLUS_CIRCLE_OUTLINE));
addAuthServerItem.setLeftGraphic(wrap(SVG.ADD_CIRCLE));
addAuthServerItem.setOnAction(e -> Controllers.dialog(new AddAuthlibInjectorServerPane()));
VBox.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0));
}
@@ -262,7 +262,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
MessageDialogPane.MessageType.WARNING,
doCreate,
() -> {
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid"));
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid.tip"));
body.setDisable(false);
spinner.hideSpinner();
}
@@ -460,7 +460,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
linksContainer.setMinWidth(USE_PREF_SIZE);
JFXButton btnAddServer = new JFXButton();
btnAddServer.setGraphic(SVG.PLUS.createIcon(Theme.blackFill(), 20, 20));
btnAddServer.setGraphic(SVG.ADD.createIcon(Theme.blackFill(), 20));
btnAddServer.getStyleClass().add("toggle-icon4");
btnAddServer.setOnAction(e -> {
Controllers.dialog(new AddAuthlibInjectorServerPane());
@@ -101,6 +101,9 @@ public class OfflineAccountSkinPane extends StackPane {
VBox optionPane = new VBox(skinItem, skinOptionPane);
pane.setRight(optionPane);
skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
layout.setBody(pane);
cslApiField.setPromptText(i18n("account.skin.type.csl_api.location.hint"));
@@ -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);
@@ -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();
@@ -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");
@@ -58,7 +58,7 @@ public class FileItem extends BorderPane {
setLeft(left);
JFXButton right = new JFXButton();
right.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 15, 15));
right.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 16));
right.getStyleClass().add("toggle-icon4");
right.setOnAction(e -> onExplore());
FXUtils.installFastTooltip(right, i18n("button.edit"));
@@ -81,7 +81,7 @@ public class FileSelector extends HBox {
FXUtils.bindString(customField, valueProperty());
JFXButton selectButton = new JFXButton();
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15, 15));
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15));
selectButton.setOnAction(e -> {
if (directory) {
DirectoryChooser chooser = new DirectoryChooser();
@@ -49,25 +49,25 @@ public class HintPane extends VBox {
SVG svg;
switch (type) {
case INFO:
svg = SVG.INFORMATION_OUTLINE;
svg = SVG.INFO;
break;
case ERROR:
svg = SVG.CLOSE_CIRCLE_OUTLINE;
svg = SVG.ERROR;
break;
case SUCCESS:
svg = SVG.CHECK_CIRCLE_OUTLINE;
svg = SVG.CHECK_CIRCLE;
break;
case WARNING:
svg = SVG.ALERT_OUTLINE;
svg = SVG.WARNING;
break;
case QUESTION:
svg = SVG.HELP_CIRCLE_OUTLINE;
svg = SVG.HELP;
break;
default:
throw new IllegalArgumentException("Unrecognized message box message type " + type);
}
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16, 16), new Text(type.getDisplayName()));
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16), new Text(type.getDisplayName()));
hbox.setAlignment(Pos.CENTER_LEFT);
flow.getChildren().setAll(label);
getChildren().setAll(hbox, flow);
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.ui.SVG;
public class IconedMenuItem extends IconedItem {
public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) {
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14, 14), 14, 14) : null, text);
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14), 14, 14) : null, text);
getStyleClass().setAll("iconed-menu-item");
@@ -110,7 +110,7 @@ public class IconedTwoLineListItem extends HBox {
if (externalLinkButton == null) {
externalLinkButton = new JFXButton();
externalLinkButton.getStyleClass().add("toggle-icon4");
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1));
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
}
return externalLinkButton;
@@ -54,12 +54,12 @@ public final class ImagePickerItem extends BorderPane {
imageView.setPreserveRatio(true);
JFXButton selectButton = new JFXButton();
selectButton.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 20, 20));
selectButton.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 20));
selectButton.onActionProperty().bind(onSelectButtonClicked);
selectButton.getStyleClass().add("toggle-icon4");
JFXButton deleteButton = new JFXButton();
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20));
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
deleteButton.getStyleClass().add("toggle-icon4");
@@ -20,24 +20,28 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Stack;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class StackContainerPane extends StackPane {
private final Stack<Node> stack = new Stack<>();
public class JFXDialogPane extends StackPane {
private final ArrayList<Node> stack = new ArrayList<>();
public int size() {
return stack.size();
}
public Optional<Node> peek() {
if (stack.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(stack.peek());
return Optional.of(stack.get(stack.size() - 1));
}
}
public void push(Node node) {
stack.push(node);
stack.add(node);
getChildren().setAll(node);
LOG.info(this + " " + stack);
@@ -48,7 +52,7 @@ public class StackContainerPane extends StackPane {
if (stack.isEmpty())
getChildren().setAll();
else
getChildren().setAll(stack.peek());
getChildren().setAll(stack.get(stack.size() - 1));
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
}
@@ -22,18 +22,12 @@ import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
public class JFXHyperlink extends Hyperlink {
public JFXHyperlink() {
super();
setGraphic(SVG.LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 16, 16));
}
public final class JFXHyperlink extends Hyperlink {
public JFXHyperlink(String text) {
super(text);
setGraphic(SVG.LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 16, 16));
setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16));
}
public void setExternalLink(String externalLink) {
@@ -81,8 +81,8 @@ public class MenuUpDownButton extends Control {
label.setStyle("-fx-text-fill: black;");
label.textProperty().bind(control.text);
Node up = SVG.MENU_UP.createIcon(Theme.blackFill(), 16, 16);
Node down = SVG.MENU_DOWN.createIcon(Theme.blackFill(), 16, 16);
Node up = SVG.ARROW_DROP_UP.createIcon(Theme.blackFill(), 16);
Node down = SVG.ARROW_DROP_DOWN.createIcon(Theme.blackFill(), 16);
JFXButton button = new JFXButton();
button.setGraphic(content);
@@ -74,24 +74,24 @@ public final class MessageDialogPane extends HBox {
SVG svg;
switch (type) {
case INFO:
svg = SVG.INFO_CIRCLE;
svg = SVG.INFO;
break;
case ERROR:
svg = SVG.CLOSE_CIRCLE;
svg = SVG.ERROR;
break;
case SUCCESS:
svg = SVG.CHECK_CIRCLE;
break;
case WARNING:
svg = SVG.ALERT;
svg = SVG.WARNING;
break;
case QUESTION:
svg = SVG.HELP_CIRCLE;
svg = SVG.HELP;
break;
default:
throw new IllegalArgumentException("Unrecognized message box message type " + type);
}
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40, 40));
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40));
VBox vbox = new VBox();
HBox.setHgrow(vbox, Priority.ALWAYS);
@@ -53,10 +53,9 @@ public final class MultiFileItem<T> extends VBox {
setSpacing(8);
group.selectedToggleProperty().addListener((a, b, newValue) -> {
selectedData.set(newValue != null ? (T) newValue.getUserData() : null);
if (toggleSelectedListener != null)
toggleSelectedListener.accept(newValue);
selectedData.set(newValue != null ? (T) newValue.getUserData() : null);
});
selectedData.addListener((a, b, newValue) -> {
Optional<Toggle> selecting = group.getToggles().stream()
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.util.Lang;
import java.util.List;
@@ -46,6 +47,7 @@ import java.util.List;
@DefaultProperty("container")
public class RipplerContainer extends StackPane {
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
private static final Duration DURATION = Duration.millis(200);
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
@@ -103,11 +105,10 @@ public class RipplerContainer extends StackPane {
rectangle.heightProperty().bind(heightProperty());
}));
setOnMouseEntered(e -> {
new Transition() {
if (AnimationUtils.isAnimationEnabled()) {
setOnMouseEntered(e -> new Transition() {
{
setCycleCount(1);
setCycleDuration(Duration.millis(200));
setCycleDuration(DURATION);
setInterpolator(Interpolator.EASE_IN);
}
@@ -115,14 +116,11 @@ public class RipplerContainer extends StackPane {
protected void interpolate(double frac) {
interpolateBackground(frac);
}
}.play();
});
}.play());
setOnMouseExited(e -> {
new Transition() {
setOnMouseExited(e -> new Transition() {
{
setCycleCount(1);
setCycleDuration(Duration.millis(200));
setCycleDuration(DURATION);
setInterpolator(Interpolator.EASE_OUT);
}
@@ -130,8 +128,11 @@ public class RipplerContainer extends StackPane {
protected void interpolate(double frac) {
interpolateBackground(1 - frac);
}
}.play();
});
}.play());
} else {
setOnMouseEntered(e -> interpolateBackground(1));
setOnMouseExited(e -> interpolateBackground(0));
}
}
private void interpolateBackground(double frac) {
@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
}
}
public interface State {}
public static class LoadedState implements State {}
public static class LoadingState implements State {}
public static class FailedState implements State {
private final String reason;
public FailedState(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
}
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
}
@@ -270,21 +270,21 @@ public final class TaskListPane extends StackPane {
BorderPane.setMargin(title, new Insets(0, 0, 0, 8));
setPadding(new Insets(0, 0, 8, 4));
setCenter(title);
setLeft(FXUtils.limitingSize(SVG.DOTS_HORIZONTAL.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.MORE_HORIZ.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void begin() {
if (started) return;
started = true;
setLeft(FXUtils.limitingSize(SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void fail() {
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void succeed() {
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14, 14), 14, 14));
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14), 14, 14));
}
public void count() {
@@ -18,6 +18,9 @@
package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXSnackbar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.wizard.Navigation;
public class Decorator extends Control {
@@ -61,12 +67,40 @@ public class Decorator extends Control {
private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();
private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();
private boolean playRestoreMinimizeAnimation = false;
public Decorator(Stage primaryStage) {
this.primaryStage = primaryStage;
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
primaryStage.initStyle(StageStyle.UNDECORATED);
if (AnimationUtils.playWindowAnimation()) {
FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {
if (playRestoreMinimizeAnimation && !iconified) {
playRestoreMinimizeAnimation = false;
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
)
);
timeline.play();
}
});
}
}
public Stage getPrimaryStage() {
@@ -239,7 +273,29 @@ public class Decorator extends Control {
}
public void minimize() {
primaryStage.setIconified(true);
if (AnimationUtils.playWindowAnimation()) {
playRestoreMinimizeAnimation = true;
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
)
);
timeline.setOnFinished(event -> primaryStage.setIconified(true));
timeline.play();
} else {
primaryStage.setIconified(true);
}
}
public void close() {
@@ -31,7 +31,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DecoratorAnimationProducer implements AnimationProducer {
public final class DecoratorAnimationProducer implements AnimationProducer {
@Override
public void init(AnimationHandler handler) {
}
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXSnackbar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
@@ -35,6 +38,7 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
@@ -42,11 +46,12 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.construct.DialogAware;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jetbrains.annotations.Nullable;
@@ -77,11 +82,32 @@ public class DecoratorController {
private final Navigator navigator;
private JFXDialog dialog;
private StackContainerPane dialogPane;
private JFXDialogPane dialogPane;
public DecoratorController(Stage stage, Node mainPage) {
decorator = new Decorator(stage);
decorator.setOnCloseButtonAction(Launcher::stopApplication);
decorator.setOnCloseButtonAction(() -> {
if (AnimationUtils.playWindowAnimation()) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
)
);
timeline.setOnFinished(event -> Launcher.stopApplication());
timeline.play();
} else {
Launcher.stopApplication();
}
});
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
navigator = new Navigator();
@@ -354,7 +380,7 @@ public class DecoratorController {
return;
}
dialog = new JFXDialog();
dialogPane = new StackContainerPane();
dialogPane = new JFXDialogPane();
dialog.setContent(dialogPane);
decorator.capableDraggingWindow(dialog);
@@ -371,21 +397,22 @@ public class DecoratorController {
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
if (node instanceof DialogAware) {
DialogAware dialogAware = (DialogAware) node;
if (dialog.isVisible()) {
dialogAware.onDialogShown();
} else {
dialog.visibleProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
dialogAware.onDialogShown();
observable.removeListener(this);
}
if (dialog.isVisible()) {
dialog.requestFocus();
if (node instanceof DialogAware)
((DialogAware) node).onDialogShown();
} else {
dialog.visibleProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
dialog.requestFocus();
if (node instanceof DialogAware)
((DialogAware) node).onDialogShown();
observable.removeListener(this);
}
});
}
}
});
}
}
@@ -397,18 +424,21 @@ public class DecoratorController {
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
if (dialog != null) {
dialogPane.pop(node);
JFXDialogPane pane = dialogPane;
if (node instanceof DialogAware) {
((DialogAware) node).onDialogClosed();
}
if (dialogPane.getChildren().isEmpty()) {
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
dialog.setOnDialogClosed(e -> pane.pop(node));
dialog.close();
dialog = null;
dialogPane = null;
navigator.setDisable(false);
} else {
pane.pop(node);
}
if (node instanceof DialogAware) {
((DialogAware) node).onDialogClosed();
}
}
}
@@ -187,17 +187,20 @@ public class DecoratorSkin extends SkinBase<Decorator> {
buttonsContainer.setMaxHeight(40);
{
JFXButton btnHelp = new JFXButton();
btnHelp.setGraphic(SVG.HELP_CIRCLE_OUTLINE.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnHelp.setFocusTraversable(false);
btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1));
btnHelp.getStyleClass().add("jfx-decorator-button");
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
JFXButton btnMin = new JFXButton();
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnMin.setFocusTraversable(false);
btnMin.setGraphic(SVG.MINIMIZE.createIcon(Theme.foregroundFillBinding(), -1));
btnMin.getStyleClass().add("jfx-decorator-button");
btnMin.setOnAction(e -> skinnable.minimize());
JFXButton btnClose = new JFXButton();
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnClose.setFocusTraversable(false);
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
btnClose.getStyleClass().add("jfx-decorator-button");
btnClose.setOnAction(e -> skinnable.close());
@@ -224,7 +227,8 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (canBack) {
JFXButton backNavButton = new JFXButton();
backNavButton.setGraphic(SVG.BACK.createIcon(Theme.foregroundFillBinding(), -1, -1));
backNavButton.setFocusTraversable(false);
backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Theme.foregroundFillBinding(), -1));
backNavButton.getStyleClass().add("jfx-decorator-button");
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
@@ -235,14 +239,15 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (canClose) {
JFXButton closeNavButton = new JFXButton();
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.setFocusTraversable(false);
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
closeNavButton.getStyleClass().add("jfx-decorator-button");
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
if (showCloseAsHome)
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1));
else
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
navLeft.getChildren().add(closeNavButton);
}
@@ -258,7 +263,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
titleLabel.getStyleClass().add("jfx-decorator-title");
if (titleNode == null) {
titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(
() -> skinnable.getWidth() - 100 - navLeft.getWidth(),
() -> skinnable.getWidth() - 150 - navLeft.getWidth(),
skinnable.widthProperty(), navLeft.widthProperty()));
} else {
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
@@ -281,7 +286,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
HBox navRight = new HBox();
navRight.setAlignment(Pos.CENTER_RIGHT);
JFXButton refreshNavButton = new JFXButton();
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1, -1));
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1));
refreshNavButton.getStyleClass().add("jfx-decorator-button");
refreshNavButton.ripplerFillProperty().set(Theme.whiteFill());
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
@@ -101,12 +101,12 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.GAMEPAD)
.addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACK)
.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.PUZZLE)
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE_BOX)
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.EARTH);
.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);
@@ -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);
@@ -157,7 +157,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
JFXButton btnSearch = new JFXButton(i18n("search"));
btnSearch.getStyleClass().add("jfx-tool-bar-button");
btnSearch.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
btnSearch.setGraphic(wrap(SVG.SEARCH.createIcon(Theme.blackFill(), -1)));
searchBar = new HBox();
{
@@ -170,7 +170,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
JFXButton closeSearchBar = new JFXButton();
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)));
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1)));
closeSearchBar.setOnAction(e -> {
searchField.clear();
rightToolbarPane.setContent(refreshPane, ContainerAnimations.FADE);
@@ -271,7 +271,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
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));
@@ -373,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"));
}
@@ -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);
@@ -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"));
@@ -62,23 +62,23 @@ 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()
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.GAMEPAD)
.addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.WRENCH_OUTLINE)
.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.APPLICATION_OUTLINE)
.addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE_OUTLINE)
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD_OUTLINE)
.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_CIRCLE_OUTLINE)
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.MESSAGE_ALERT_OUTLINE)
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFORMATION_OUTLINE);
.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);
@@ -120,7 +120,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
titleBar.getStyleClass().add("title");
titleBar.setLeft(new Label(title));
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20);
hideNode.setCursor(Cursor.HAND);
titleBar.setRight(hideNode);
FXUtils.onClicked(hideNode, () -> {
@@ -166,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"));
@@ -178,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));
@@ -247,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);
@@ -204,7 +204,7 @@ public class PersonalizationPage extends StackPane {
JFXButton clearButton = new JFXButton();
clearButton.getStyleClass().add("toggle-icon4");
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1, -1));
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
clearButton.setOnAction(e -> config().setLauncherFontFamily(null));
hBox.getChildren().setAll(cboFont, clearButton);
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
if (mainPage == null) {
MainPage mainPage = new MainPage();
FXUtils.applyDragListener(mainPage,
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file),
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
modpacks -> {
File file = modpacks.get(0);
if (ModpackHelper.isFileModpackByExtension(file)) {
Controllers.getDecorator().startWizard(
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
i18n("install.modpack"));
} else if (NBTHelper.isNBTFileByExtension(file)) {
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
try {
Controllers.navigate(new NBTEditorPage(file));
Controllers.navigate(new NBTEditorPage(file.toPath()));
} catch (Throwable e) {
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
@@ -154,14 +154,14 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
// third item in left sidebar
AdvancedListItem gameItem = new AdvancedListItem();
gameItem.setLeftGraphic(wrap(SVG.VIEW_LIST));
gameItem.setLeftGraphic(wrap(SVG.FORMAT_LIST_BULLETED));
gameItem.setActionButtonVisible(false);
gameItem.setTitle(i18n("version.manage"));
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
// forth item in left sidebar
AdvancedListItem downloadItem = new AdvancedListItem();
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD));
downloadItem.setActionButtonVisible(false);
downloadItem.setTitle(i18n("download"));
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
@@ -169,7 +169,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
// fifth item in left sidebar
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
launcherSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
launcherSettingsItem.setActionButtonVisible(false);
launcherSettingsItem.setTitle(i18n("settings"));
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
@@ -123,7 +123,7 @@ public abstract class SettingsView extends StackPane {
btnUpdate = new JFXButton();
btnUpdate.setOnAction(e -> onUpdate());
btnUpdate.getStyleClass().add("toggle-icon4");
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20, 20));
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20));
updatePane.setHeaderRight(btnUpdate);
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.jfoenix.controls.JFXButton;
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.*;
import java.util.concurrent.CompletableFuture;
import java.nio.file.Path;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTEditorPage extends BorderPane implements DecoratorPage {
/**
* @author Glavo
*/
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state;
private final File file;
private final Path file;
private final NBTFileType type;
public NBTEditorPage(File file) throws IOException {
private final BorderPane root = new BorderPane();
public NBTEditorPage(Path file) throws IOException {
getStyleClass().add("gray-background");
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath())));
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
this.file = file;
this.type = NBTFileType.ofFile(file);
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
throw new IOException("Unknown type of file " + file);
}
setCenter(new ProgressIndicator());
setContent(root);
setLoading(true);
HBox actions = new HBox(8);
actions.setPadding(new Insets(8));
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
actions.getChildren().setAll(saveButton, cancelButton);
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file)))
.thenAcceptAsync(tree -> {
setCenter(new NBTTreeView(tree));
// setBottom(actions);
}, Schedulers.javafx())
.handleAsync((result, e) -> {
if (e != null) {
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
Task.supplyAsync(() -> type.readAsTree(file))
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
setLoading(false);
root.setCenter(new NBTTreeView(result));
} else {
LOG.warning("Fail to open nbt file", exception);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
}
return null;
}, Schedulers.javafx());
}).start();
}
public void save() throws IOException {
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.NBTIO;
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* @author Glavo
*/
public enum NBTFileType {
COMPRESSED("dat", "dat_old") {
@Override
public Tag read(File file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) {
public Tag read(Path file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
fileInputStream.mark(3);
byte[] header = new byte[3];
if (fileInputStream.read(header) < 3) {
@@ -42,25 +64,28 @@ public enum NBTFileType {
},
ANVIL("mca") {
@Override
public Tag read(File file) throws IOException {
public Tag read(Path file) throws IOException {
return REGION.read(file);
}
@Override
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
return REGION.readAsTree(file);
}
},
REGION("mcr") {
@Override
public Tag read(File file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file, "r")) {
public Tag read(Path file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
if (r.length() == 0) {
return tag;
}
byte[] header = new byte[4096];
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
Inflater inflater = new Inflater();
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
r.readFully(header);
for (int i = 0; i < 4096; i += 4) {
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
@@ -109,7 +134,7 @@ public enum NBTFileType {
}
@Override
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
for (Tag tag : ((ListTag) item.getValue())) {
@@ -135,7 +160,11 @@ public enum NBTFileType {
static final NBTFileType[] types = values();
public static NBTFileType ofFile(File file) {
public static boolean isNBTFileByExtension(Path file) {
return NBTFileType.ofFile(file) != null;
}
public static NBTFileType ofFile(Path file) {
String ext = FileUtils.getExtension(file);
for (NBTFileType type : types) {
for (String extension : type.extensions) {
@@ -153,11 +182,11 @@ public enum NBTFileType {
this.extensions = extensions;
}
public abstract Tag read(File file) throws IOException;
public abstract Tag read(Path file) throws IOException;
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
root.setName(file.getName());
root.setName(file.getFileName().toString());
return root;
}
}
@@ -1,12 +0,0 @@
package org.jackhuang.hmcl.ui.nbt;
import java.io.File;
public final class NBTHelper {
private NBTHelper() {
}
public static boolean isNBTFileByExtension(File file) {
return NBTFileType.ofFile(file) != null;
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.Tag;
@@ -6,6 +23,9 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author Glavo
*/
public enum NBTTagType {
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
@@ -14,7 +31,10 @@ import java.util.EnumMap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTTreeView extends TreeView<Tag> {
/**
* @author Glavo
*/
public final class NBTTreeView extends TreeView<Tag> {
public NBTTreeView(NBTTreeView.Item tree) {
this.setRoot(tree);
@@ -48,7 +48,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true));
Node left = VersionPage.wrap(SVG.FOLDER_OUTLINE);
Node left = VersionPage.wrap(SVG.FOLDER);
root.setLeft(left);
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
@@ -64,7 +64,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
btnRemove.setOnAction(e -> skinnable.remove());
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14));
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
right.getChildren().add(btnRemove);
root.setRight(right);
@@ -59,6 +59,8 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
versionId == null ? i18n("settings.advanced") : i18n("settings.advanced.title", versionId)
));
this.getStyleClass().add("gray-background");
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
@@ -60,7 +60,7 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
toolbar.setPickOnBounds(false);
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh));
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.PLUS, skinnable::add));
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.ADD, skinnable::add));
toolbar.getChildren().add(createToolbarButton(i18n("button.remove"), SVG.DELETE, () -> {
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
@@ -390,15 +390,15 @@ public class DownloadPage extends Control implements DecoratorPage {
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;
}
@@ -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,10 +87,10 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
}
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
.addNavigationDrawerItem(i18n("install.new_game"), SVG.PLUS_CIRCLE_OUTLINE, Versions::addNewGame)
.addNavigationDrawerItem(i18n("install.modpack"), SVG.PACK, Versions::importModpack)
.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.GEAR_OUTLINE, this::modifyGlobalGameSettings);
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.SETTINGS, this::modifyGlobalGameSettings);
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
setLeft(pane, bottomLeftCornerList);
}
@@ -172,7 +172,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
@Override
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
return Collections.singletonList(
createToolbarButton2(i18n("install.installer.install_offline"), SVG.PLUS, skinnable::installOffline)
createToolbarButton2(i18n("install.installer.install_offline"), SVG.ADD, skinnable::installOffline)
);
}
}
@@ -141,11 +141,11 @@ class ModListPageSkin extends SkinBase<ModListPage> {
// Toolbar Normal
toolbarNormal.getChildren().setAll(
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
createToolbarButton2(i18n("mods.add"), SVG.PLUS, skinnable::add),
createToolbarButton2(i18n("mods.add"), SVG.ADD, skinnable::add),
createToolbarButton2(i18n("folder.mod"), SVG.FOLDER_OPEN, skinnable::openModFolder),
createToolbarButton2(i18n("mods.check_updates"), SVG.UPDATE, skinnable::checkUpdates),
createToolbarButton2(i18n("download"), SVG.DOWNLOAD_OUTLINE, skinnable::download),
createToolbarButton2(i18n("search"), SVG.MAGNIFY, () -> changeToolbar(searchBar))
createToolbarButton2(i18n("download"), SVG.DOWNLOAD, skinnable::download),
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar))
);
// Toolbar Selecting
@@ -290,7 +290,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
StringBuilder message = new StringBuilder(localModFile.getFileName());
if (isNotBlank(localModFile.getGameVersion()))
message.append(", ").append(i18n("game.version")).append(": ").append(localModFile.getGameVersion());
message.append(", ").append(i18n("mods.game.version")).append(": ").append(localModFile.getGameVersion());
if (isNotBlank(localModFile.getAuthors()))
message.append(", ").append(i18n("archive.author")).append(": ").append(localModFile.getAuthors());
this.message = message.toString();
@@ -398,6 +398,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
setHeading(titleContainer);
Label description = new Label(modInfo.getModInfo().getDescription().toString());
FXUtils.copyOnDoubleClick(description);
setBody(description);
if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {
@@ -519,15 +520,15 @@ class ModListPageSkin extends SkinBase<ModListPage> {
setSelectable();
restoreButton.getStyleClass().add("toggle-icon4");
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(Theme.blackFill(), 24), 24, 24));
infoButton.getStyleClass().add("toggle-icon4");
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFORMATION_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(Theme.blackFill(), 24), 24, 24));
container.getChildren().setAll(checkBox, content, restoreButton, revealButton, infoButton);
@@ -90,7 +90,7 @@ public class VersionIconDialog extends DialogPane {
}
private Node createCustomIcon() {
Node shape = SVG.PLUS_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 32, 32);
Node shape = SVG.ADD_CIRCLE.createIcon(Theme.blackFill(), 32);
shape.setMouseTransparent(true);
RipplerContainer container = new RipplerContainer(shape);
FXUtils.setLimitWidth(container, 36);
@@ -66,7 +66,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
private String preferredVersionName = null;
{
public VersionPage() {
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
@@ -77,7 +77,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
tab.select(versionSettingsTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
@@ -241,7 +242,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem versionSettingsItem = new AdvancedListItem();
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
versionSettingsItem.setTitle(i18n("settings.game"));
versionSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
versionSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
versionSettingsItem.setActionButtonVisible(false);
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab));
@@ -249,7 +250,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem installerListItem = new AdvancedListItem();
installerListItem.getStyleClass().add("navigation-drawer-item");
installerListItem.setTitle(i18n("settings.tabs.installers"));
installerListItem.setLeftGraphic(wrap(SVG.CUBE));
installerListItem.setLeftGraphic(wrap(SVG.DEPLOYED_CODE));
installerListItem.setActionButtonVisible(false);
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
installerListItem.setOnAction(e -> control.tab.select(control.installerListTab));
@@ -257,7 +258,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem modListItem = new AdvancedListItem();
modListItem.getStyleClass().add("navigation-drawer-item");
modListItem.setTitle(i18n("mods.manage"));
modListItem.setLeftGraphic(wrap(SVG.PUZZLE));
modListItem.setLeftGraphic(wrap(SVG.EXTENSION));
modListItem.setActionButtonVisible(false);
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
modListItem.setOnAction(e -> control.tab.select(control.modListTab));
@@ -265,7 +266,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
AdvancedListItem worldListItem = new AdvancedListItem();
worldListItem.getStyleClass().add("navigation-drawer-item");
worldListItem.setTitle(i18n("world.manage"));
worldListItem.setLeftGraphic(wrap(SVG.EARTH));
worldListItem.setLeftGraphic(wrap(SVG.PUBLIC));
worldListItem.setActionButtonVisible(false);
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
worldListItem.setOnAction(e -> control.tab.select(control.worldListTab));
@@ -280,26 +281,26 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
PopupMenu browseList = new PopupMenu();
JFXPopup browsePopup = new JFXPopup(browseList);
browseList.getContent().setAll(
new IconedMenuItem(SVG.GAMEPAD, i18n("folder.game"), () -> control.onBrowse(""), browsePopup),
new IconedMenuItem(SVG.PUZZLE, i18n("folder.mod"), () -> control.onBrowse("mods"), browsePopup),
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
new IconedMenuItem(SVG.STADIA_CONTROLLER, i18n("folder.game"), () -> control.onBrowse(""), browsePopup),
new IconedMenuItem(SVG.EXTENSION, i18n("folder.mod"), () -> control.onBrowse("mods"), browsePopup),
new IconedMenuItem(SVG.SETTINGS, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
new IconedMenuItem(SVG.TEXTURE, i18n("folder.resourcepacks"), () -> control.onBrowse("resourcepacks"), browsePopup),
new IconedMenuItem(SVG.APPLICATION_OUTLINE, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
new IconedMenuItem(SVG.MONITOR_SCREENSHOT, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
new IconedMenuItem(SVG.EARTH, i18n("folder.saves"), () -> control.onBrowse("saves"), browsePopup),
new IconedMenuItem(SVG.WB_SUNNY, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
new IconedMenuItem(SVG.SCREENSHOT_MONITOR, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
new IconedMenuItem(SVG.PUBLIC, i18n("folder.saves"), () -> control.onBrowse("saves"), browsePopup),
new IconedMenuItem(SVG.SCRIPT, i18n("folder.logs"), () -> control.onBrowse("logs"), browsePopup)
);
PopupMenu managementList = new PopupMenu();
JFXPopup managementPopup = new JFXPopup(managementList);
managementList.getContent().setAll(
new IconedMenuItem(SVG.ROCKET_LAUNCH_OUTLINE, i18n("version.launch.test"), control::testGame, managementPopup),
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), control::testGame, managementPopup),
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), control::generateLaunchScript, managementPopup),
new MenuSeparator(),
new IconedMenuItem(SVG.PENCIL, i18n("version.manage.rename"), control::rename, managementPopup),
new IconedMenuItem(SVG.COPY, i18n("version.manage.duplicate"), control::duplicate, managementPopup),
new IconedMenuItem(SVG.DELETE_OUTLINE, i18n("version.manage.remove"), control::remove, managementPopup),
new IconedMenuItem(SVG.EXPORT, i18n("modpack.export"), control::export, managementPopup),
new IconedMenuItem(SVG.EDIT, i18n("version.manage.rename"), control::rename, managementPopup),
new IconedMenuItem(SVG.FOLDER_COPY, i18n("version.manage.duplicate"), control::duplicate, managementPopup),
new IconedMenuItem(SVG.DELETE, i18n("version.manage.remove"), control::remove, managementPopup),
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), control::export, managementPopup),
new MenuSeparator(),
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), control::redownloadAssetIndex, managementPopup),
new IconedMenuItem(null, i18n("version.manage.remove_assets"), control::clearAssets, managementPopup),
@@ -311,11 +312,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
.addNavigationDrawerItem(i18n("version.update"), SVG.UPDATE, control::updateGame, upgradeItem -> {
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
})
.addNavigationDrawerItem(i18n("version.launch.test"), SVG.ROCKET_LAUNCH_OUTLINE, control::testGame)
.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OUTLINE, null, browseMenuItem -> {
.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(i18n("settings.game.management"), SVG.WRENCH_OUTLINE, null, managementItem -> {
.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");
@@ -345,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 {
@@ -551,8 +551,8 @@ 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, lastVersionSetting.launcherVisibilityProperty());
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
@@ -586,8 +586,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());
gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty());
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
@@ -0,0 +1,88 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Task;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author Glavo
*/
public final class WorldBackupTask extends Task<Path> {
private final World world;
private final Path backupsDir;
public WorldBackupTask(World world, Path backupsDir) {
this.world = world;
this.backupsDir = backupsDir;
}
@Override
public void execute() throws Exception {
try (FileChannel lockChannel = world.lock()) {
Files.createDirectories(backupsDir);
String time = LocalDateTime.now().format(WorldBackupsPage.TIME_FORMATTER);
String baseName = time + "_" + world.getFileName();
Path backupFile = null;
OutputStream outputStream = null;
int count;
for (count = 0; count < 256; count++) {
try {
backupFile = backupsDir.resolve(baseName + (count == 0 ? "" : " " + count) + ".zip").toAbsolutePath();
outputStream = Files.newOutputStream(backupFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
break;
} catch (FileAlreadyExistsException ignored) {
}
}
if (outputStream == null)
throw new IOException("Too many attempts");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(outputStream))) {
String rootName = world.getFileName();
Path rootDir = this.world.getFile();
Files.walkFileTree(this.world.getFile(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (path.endsWith("session.lock")) {
return FileVisitResult.CONTINUE;
}
zipOutputStream.putNextEntry(new ZipEntry(rootName + "/" + rootDir.relativize(path).toString().replace('\\', '/')));
Files.copy(path, zipOutputStream);
zipOutputStream.closeEntry();
return FileVisitResult.CONTINUE;
}
});
}
setResult(backupFile);
}
}
}
@@ -0,0 +1,281 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.game.WorldLockedException;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
*/
public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.BackupInfo> implements DecoratorPage {
static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
private final World world;
private final Path backupsDir;
private final Pattern backupFileNamePattern;
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
public WorldBackupsPage(World world, Path backupsDir) {
this.backupsDir = backupsDir;
this.world = world;
this.backupFileNamePattern = Pattern.compile("(?<datetime>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_" + Pattern.quote(world.getFileName()) + "( (?<count>[0-9]+))?\\.zip");
this.state.set(State.fromTitle(i18n("world.backup.title", world.getWorldName())));
loadBackups();
}
@Override
public ReadOnlyObjectProperty<State> stateProperty() {
return state;
}
@Override
public void refresh() {
loadBackups();
}
private void loadBackups() {
setLoading(true);
Task.supplyAsync(() -> {
if (Files.isDirectory(backupsDir)) {
try (Stream<Path> paths = Files.list(backupsDir)) {
ArrayList<BackupInfo> result = new ArrayList<>();
paths.forEach(path -> {
if (Files.isRegularFile(path)) {
try {
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
if (matcher.matches()) {
LocalDateTime time = LocalDateTime.parse(matcher.group("datetime"), TIME_FORMATTER);
int count = 0;
if (matcher.group("count") != null) {
count = Integer.parseInt(matcher.group("count"));
}
result.add(new BackupInfo(path, new World(path), time, count));
}
} catch (Throwable e) {
LOG.warning("Failed to load backup file " + path, e);
}
}
});
result.sort(Comparator.naturalOrder());
return result;
}
} else {
return new ArrayList<BackupInfo>();
}
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
this.setLoading(false);
if (exception == null) {
this.setItems(FXCollections.observableList(result));
} else {
LOG.warning("Failed to load backups", exception);
}
}).start();
}
@Override
protected Skin<?> createDefaultSkin() {
return new WorldBackupsPageSkin();
}
void createBackup() {
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup")).thenApplyAsync(path -> {
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
if (!matcher.matches()) {
throw new AssertionError("Wrong backup file name" + path);
}
LocalDateTime time = LocalDateTime.parse(matcher.group("datetime"), TIME_FORMATTER);
int count = 0;
if (matcher.group("count") != null) {
count = Integer.parseInt(matcher.group("count"));
}
return Pair.pair(path, new BackupInfo(path, new World(path), time, count));
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
WorldBackupsPage.this.getItems().add(result.getValue());
WorldBackupsPage.this.getItems().sort(Comparator.naturalOrder());
Controllers.dialog(i18n("world.backup.create.success", result.getKey()), null, MessageDialogPane.MessageType.INFO);
} else if (exception instanceof WorldLockedException) {
Controllers.dialog(i18n("world.backup.create.locked"), null, MessageDialogPane.MessageType.WARNING);
} else {
LOG.warning("Failed to create backup", exception);
Controllers.dialog(i18n("world.backup.create.failed", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);
}
}), i18n("world.backup"), null);
}
private final class WorldBackupsPageSkin extends ToolbarListPageSkin<WorldBackupsPage> {
WorldBackupsPageSkin() {
super(WorldBackupsPage.this);
}
@Override
protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup"), SVG.ARCHIVE, skinnable::createBackup));
}
}
public final class BackupInfo extends Control implements Comparable<BackupInfo> {
private final Path file;
private final World backupWorld;
private final LocalDateTime backupTime;
private final int count;
public BackupInfo(Path file, World backupWorld, LocalDateTime backupTime, int count) {
this.file = file;
this.backupWorld = backupWorld;
this.backupTime = backupTime;
this.count = count;
}
public World getBackupWorld() {
return backupWorld;
}
public LocalDateTime getBackupTime() {
return backupTime;
}
@Override
protected Skin<?> createDefaultSkin() {
return new BackupInfoSkin(this);
}
void onReveal() {
FXUtils.showFileInExplorer(file);
}
void onDelete() {
WorldBackupsPage.this.getItems().remove(this);
Task.runAsync(() -> Files.delete(file)).start();
}
@Override
public int compareTo(@NotNull WorldBackupsPage.BackupInfo that) {
int c = this.backupTime.compareTo(that.backupTime);
return c != 0 ? c : Integer.compare(this.count, that.count);
}
}
private static final class BackupInfoSkin extends SkinBase<BackupInfo> {
BackupInfoSkin(BackupInfo skinnable) {
super(skinnable);
World world = skinnable.getBackupWorld();
BorderPane root = new BorderPane();
root.getStyleClass().add("md-list-cell");
root.setPadding(new Insets(8));
{
StackPane left = new StackPane();
root.setLeft(left);
left.setPadding(new Insets(0, 8, 0, 0));
ImageView imageView = new ImageView();
left.getChildren().add(imageView);
FXUtils.limitSize(imageView, 32, 32);
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
}
{
TwoLineListItem item = new TwoLineListItem();
root.setCenter(item);
if (skinnable.getBackupWorld().getWorldName() != null)
item.setTitle(parseColorEscapes(skinnable.getBackupWorld().getWorldName()));
item.setSubtitle(formatDateTime(skinnable.getBackupTime()) + (skinnable.count == 0 ? "" : " (" + skinnable.count + ")"));
if (world.getGameVersion() != null) item.getTags().add(world.getGameVersion());
}
{
HBox right = new HBox(8);
root.setRight(right);
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnReveal = new JFXButton();
right.getChildren().add(btnReveal);
FXUtils.installFastTooltip(btnReveal, i18n("world.reveal"));
btnReveal.getStyleClass().add("toggle-icon4");
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
btnReveal.setOnAction(event -> skinnable.onReveal());
JFXButton btnDelete = new JFXButton();
right.getChildren().add(btnDelete);
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
btnDelete.getStyleClass().add("toggle-icon4");
btnDelete.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
}
getChildren().setAll(new RipplerContainer(root));
}
}
}
@@ -27,22 +27,19 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.DoubleValidator;
import org.jackhuang.hmcl.ui.construct.NumberValidator;
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.time.Instant;
import java.util.Arrays;
@@ -55,30 +52,70 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author Glavo
*/
public final class WorldInfoPage extends StackPane implements DecoratorPage {
public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
private final World world;
private CompoundTag levelDat;
private final ObjectProperty<State> stateProperty;
private FileChannel sessionLockChannel;
@Override
public boolean back() {
closePage();
return true;
}
@Override
public void closePage() {
if (sessionLockChannel != null) {
try {
sessionLockChannel.close();
} catch (IOException e) {
LOG.warning("Failed to close session lock channel", e);
}
sessionLockChannel = null;
}
}
public WorldInfoPage(World world) {
this.world = world;
this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName())));
this.getChildren().add(new ProgressIndicator());
Task.supplyAsync(world::readLevelDat)
this.getStyleClass().add("gray-background");
this.setLoading(true);
Task.supplyAsync(this::loadWorldInfo)
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
this.levelDat = result;
loadWorldInfo();
updateControls();
setLoading(false);
} else {
LOG.warning("Failed to load level.dat", exception);
this.getChildren().setAll(new Label(i18n("world.info.failed")));
setFailedReason(i18n("world.info.failed"));
}
})).start();
}
private void loadWorldInfo() {
private CompoundTag loadWorldInfo() throws IOException {
if (!Files.isDirectory(world.getFile()))
throw new IOException("Not a valid world directory");
try {
sessionLockChannel = world.lock();
} catch (IOException ignored) {
}
return world.readLevelDat();
}
private boolean isReadOnly() {
return sessionLockChannel == null;
}
private void updateControls() {
CompoundTag dataTag = levelDat.get("Data");
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
@@ -86,7 +123,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
getChildren().setAll(scrollPane);
setContent(scrollPane);
VBox rootPane = new VBox();
rootPane.setFillWidth(true);
@@ -103,6 +140,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
Label worldNameLabel = new Label();
FXUtils.copyOnDoubleClick(worldNameLabel);
worldNameLabel.setText(world.getWorldName());
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
worldNamePane.setRight(worldNameLabel);
@@ -115,6 +153,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
gameVersionPane.setLeft(label);
Label gameVersionLabel = new Label();
FXUtils.copyOnDoubleClick(gameVersionLabel);
gameVersionLabel.setText(world.getGameVersion());
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
gameVersionPane.setRight(gameVersionLabel);
@@ -127,6 +166,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
randomSeedPane.setLeft(label);
Label randomSeedLabel = new Label();
FXUtils.copyOnDoubleClick(randomSeedLabel);
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
randomSeedPane.setRight(randomSeedLabel);
@@ -143,6 +183,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
lastPlayedPane.setLeft(label);
Label lastPlayedLabel = new Label();
FXUtils.copyOnDoubleClick(lastPlayedLabel);
lastPlayedLabel.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
lastPlayedPane.setRight(lastPlayedLabel);
@@ -155,6 +196,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
timePane.setLeft(label);
Label timeLabel = new Label();
FXUtils.copyOnDoubleClick(timeLabel);
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
timePane.setRight(timeLabel);
@@ -168,6 +210,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
OptionToggleButton allowCheatsButton = new OptionToggleButton();
{
allowCheatsButton.setTitle(i18n("world.info.allow_cheats"));
allowCheatsButton.setDisable(isReadOnly());
Tag tag = dataTag.get("allowCommands");
if (tag instanceof ByteTag) {
@@ -190,6 +233,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
OptionToggleButton generateFeaturesButton = new OptionToggleButton();
{
generateFeaturesButton.setTitle(i18n("world.info.generate_features"));
generateFeaturesButton.setDisable(isReadOnly());
Tag tag = worldGenSettings != null ? worldGenSettings.get("generate_features") : dataTag.get("MapFeatures");
if (tag instanceof ByteTag) {
@@ -216,6 +260,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
difficultyPane.setLeft(label);
JFXComboBox<Difficulty> difficultyBox = new JFXComboBox<>(Difficulty.items);
difficultyBox.setDisable(isReadOnly());
BorderPane.setAlignment(difficultyBox, Pos.CENTER_RIGHT);
difficultyPane.setRight(difficultyBox);
@@ -258,6 +303,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
locationPane.setLeft(label);
Label locationLabel = new Label();
FXUtils.copyOnDoubleClick(locationLabel);
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
locationPane.setRight(locationLabel);
@@ -276,6 +322,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
lastDeathLocationPane.setLeft(label);
Label lastDeathLocationLabel = new Label();
FXUtils.copyOnDoubleClick(lastDeathLocationLabel);
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
lastDeathLocationPane.setRight(lastDeathLocationLabel);
@@ -297,6 +344,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
spawnPane.setLeft(label);
Label spawnLabel = new Label();
FXUtils.copyOnDoubleClick(spawnLabel);
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
spawnPane.setRight(spawnLabel);
@@ -318,6 +366,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
playerGameTypePane.setLeft(label);
JFXComboBox<GameType> gameTypeBox = new JFXComboBox<>(GameType.items);
gameTypeBox.setDisable(isReadOnly());
BorderPane.setAlignment(gameTypeBox, Pos.CENTER_RIGHT);
playerGameTypePane.setRight(gameTypeBox);
@@ -348,6 +397,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
healthPane.setLeft(label);
JFXTextField healthField = new JFXTextField();
healthField.setDisable(isReadOnly());
healthField.setPrefWidth(50);
healthField.setAlignment(Pos.CENTER_RIGHT);
BorderPane.setAlignment(healthField, Pos.CENTER_RIGHT);
@@ -381,6 +431,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
foodLevelPane.setLeft(label);
JFXTextField foodLevelField = new JFXTextField();
foodLevelField.setDisable(isReadOnly());
foodLevelField.setPrefWidth(50);
foodLevelField.setAlignment(Pos.CENTER_RIGHT);
BorderPane.setAlignment(foodLevelField, Pos.CENTER_RIGHT);
@@ -414,6 +465,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
xpLevelPane.setLeft(label);
JFXTextField xpLevelField = new JFXTextField();
xpLevelField.setDisable(isReadOnly());
xpLevelField.setPrefWidth(50);
xpLevelField.setAlignment(Pos.CENTER_RIGHT);
BorderPane.setAlignment(xpLevelField, Pos.CENTER_RIGHT);
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,13 +17,8 @@
*/
package org.jackhuang.hmcl.ui.versions;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
@@ -32,26 +27,17 @@ import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.time.Instant;
import java.nio.file.Path;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItem extends Control {
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
public final class WorldListItem extends Control {
private final World world;
private final Path backupsDir;
public WorldListItem(World world) {
public WorldListItem(World world, Path backupsDir) {
this.world = world;
title.set(parseColorEscapes(world.getWorldName()));
subtitle.set(i18n("world.description", world.getFileName(), formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
FXUtils.onClicked(this, this::showInfo);
this.backupsDir = backupsDir;
}
@Override
@@ -59,16 +45,8 @@ public class WorldListItem extends Control {
return new WorldListItemSkin(this);
}
public StringProperty titleProperty() {
return title;
}
public StringProperty subtitleProperty() {
return subtitle;
}
public ObjectProperty<Image> imageProperty() {
return image;
public World getWorld() {
return world;
}
public void export() {
@@ -85,11 +63,7 @@ public class WorldListItem extends Control {
}
public void reveal() {
try {
FXUtils.openFolder(world.getFile().toFile());
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}
FXUtils.openFolder(world.getFile().toFile());
}
public void manageDatapacks() {
@@ -104,4 +78,8 @@ public class WorldListItem extends Control {
public void showInfo() {
Controllers.navigate(new WorldInfoPage(world));
}
public void showBackupPage() {
Controllers.navigate(new WorldBackupsPage(world, backupsDir));
}
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.SkinBase;
@@ -26,66 +25,96 @@ import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import java.time.Instant;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItemSkin extends SkinBase<WorldListItem> {
public final class WorldListItemSkin extends SkinBase<WorldListItem> {
public WorldListItemSkin(WorldListItem skinnable) {
super(skinnable);
World world = skinnable.getWorld();
BorderPane root = new BorderPane();
HBox center = new HBox();
center.setMouseTransparent(true);
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
StackPane imageViewContainer = new StackPane();
FXUtils.setLimitWidth(imageViewContainer, 32);
FXUtils.setLimitHeight(imageViewContainer, 32);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
imageViewContainer.getChildren().setAll(imageView);
TwoLineListItem item = new TwoLineListItem();
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
root.setCenter(center);
PopupMenu menu = new PopupMenu();
JFXPopup popup = new JFXPopup(menu);
menu.getContent().setAll(
new IconedMenuItem(SVG.GEAR_OUTLINE, i18n("world.datapack"), skinnable::manageDatapacks, popup),
new IconedMenuItem(SVG.EXPORT, i18n("world.export"), skinnable::export, popup),
new IconedMenuItem(SVG.FOLDER_OUTLINE, i18n("world.reveal"), skinnable::reveal, popup));
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnManage = new JFXButton();
btnManage.setOnAction(e -> popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
btnManage.setGraphic(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), -1, -1));
right.getChildren().add(btnManage);
root.setRight(right);
root.getStyleClass().add("md-list-cell");
root.setPadding(new Insets(8));
{
StackPane left = new StackPane();
FXUtils.installSlowTooltip(left, world.getFile().toString());
root.setLeft(left);
left.setPadding(new Insets(0, 8, 0, 0));
ImageView imageView = new ImageView();
left.getChildren().add(imageView);
FXUtils.limitSize(imageView, 32, 32);
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
}
{
TwoLineListItem item = new TwoLineListItem();
root.setCenter(item);
if (world.getWorldName() != null)
item.setTitle(parseColorEscapes(world.getWorldName()));
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
if (world.getGameVersion() != null)
item.getTags().add(world.getGameVersion());
if (world.isLocked())
item.getTags().add(i18n("world.locked"));
}
{
HBox right = new HBox(8);
root.setRight(right);
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnReveal = new JFXButton();
right.getChildren().add(btnReveal);
FXUtils.installFastTooltip(btnReveal, i18n("world.reveal"));
btnReveal.getStyleClass().add("toggle-icon4");
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
btnReveal.setOnAction(event -> skinnable.reveal());
JFXButton btnExport = new JFXButton();
right.getChildren().add(btnExport);
FXUtils.installFastTooltip(btnExport, i18n("world.export"));
btnExport.getStyleClass().add("toggle-icon4");
btnExport.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
btnExport.setOnAction(event -> skinnable.export());
JFXButton btnBackup = new JFXButton();
right.getChildren().add(btnBackup);
FXUtils.installFastTooltip(btnBackup, i18n("world.backup"));
btnBackup.getStyleClass().add("toggle-icon4");
btnBackup.setGraphic(SVG.ARCHIVE.createIcon(Theme.blackFill(), -1));
btnBackup.setOnAction(event -> skinnable.showBackupPage());
JFXButton btnDatapack = new JFXButton();
right.getChildren().add(btnDatapack);
FXUtils.installFastTooltip(btnDatapack, i18n("world.datapack"));
btnDatapack.getStyleClass().add("toggle-icon4");
btnDatapack.setGraphic(SVG.EXTENSION.createIcon(Theme.blackFill(), -1));
btnDatapack.setOnAction(event -> skinnable.manageDatapacks());
JFXButton btnInfo = new JFXButton();
right.getChildren().add(btnInfo);
FXUtils.installFastTooltip(btnInfo, i18n("world.info"));
btnInfo.getStyleClass().add("toggle-icon4");
btnInfo.setGraphic(SVG.INFO.createIcon(Theme.blackFill(), -1));
btnInfo.setOnAction(event -> skinnable.showInfo());
}
getChildren().setAll(new RipplerContainer(root));
}
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXCheckBox;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Node;
@@ -37,17 +36,17 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
private Path savesDir;
private Path backupsDir;
private List<World> worlds;
private Profile profile;
private String id;
@@ -62,7 +61,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
if (worlds != null)
itemsProperty().setAll(worlds.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(WorldListItem::new).collect(Collectors.toList()));
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
});
}
@@ -71,36 +70,39 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
return new WorldListPageSkin();
}
@Override
public void loadVersion(Profile profile, String id) {
this.profile = profile;
this.id = id;
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
this.savesDir = profile.getRepository().getSavesDirectory(id);
this.backupsDir = profile.getRepository().getBackupsDirectory(id);
refresh();
}
public CompletableFuture<?> refresh() {
public void refresh() {
if (profile == null || id == null)
return CompletableFuture.completedFuture(null);
return;
setLoading(true);
return CompletableFuture
.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
.thenApplyAsync(unused -> {
try (Stream<World> stream = World.getWorlds(savesDir)) {
return stream.parallel().collect(Collectors.toList());
}
})
.whenCompleteAsync((result, exception) -> {
.whenComplete(Schedulers.javafx(), (result, exception) -> {
worlds = result;
setLoading(false);
if (exception == null)
itemsProperty().setAll(result.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(WorldListItem::new).collect(Collectors.toList()));
if (exception == null) {
itemsProperty().setAll(result.stream().filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
} else {
LOG.warning("Failed to load world list page", exception);
}
// https://github.com/HMCL-dev/HMCL/issues/938
System.gc();
}, Platform::runLater);
}).start();
}
public void add() {
@@ -126,7 +128,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.runAsync(() -> world.install(savesDir, name))
.whenComplete(Schedulers.javafx(), () -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)), backupsDir));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)
@@ -155,7 +157,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
this.showAll.set(showAll);
}
private class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
private final class WorldListPageSkin extends ToolbarListPageSkin<WorldListPage> {
WorldListPageSkin() {
super(WorldListPage.this);
@@ -169,8 +171,8 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
return Arrays.asList(chkShowAll,
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
createToolbarButton2(i18n("world.add"), SVG.PLUS, skinnable::add),
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD_OUTLINE, skinnable::download));
createToolbarButton2(i18n("world.add"), SVG.ADD, skinnable::add),
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD, skinnable::download));
}
}
}
@@ -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;
@@ -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("datetime.format")).withZone(ZoneId.systemDefault());
}
return formatter.format(instant);
return formatter.format(time);
}
public static boolean hasKey(String key) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -123,10 +123,11 @@ account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Pl
account.methods.microsoft.snapshot.website=Official Website
account.methods.offline=Offline
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
account.methods.offline.name.invalid=Generally, game usernames are limited to English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
account.methods.offline.name.invalid.tip=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.
account.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\n\
\n\
\ · Valid usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Invalid usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\ · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Illegal: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\n\
If you believe there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
account.methods.offline.uuid=UUID
@@ -357,11 +358,11 @@ exception.access_denied=HMCL is unable to access the file "%s". It may be locked
exception.artifact_malformed=Cannot verify the integrity of the downloaded files.
exception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again.
extension.bat=Windows Batch File (.bat)
extension.bat=Windows Batch File
extension.mod=Mod File
extension.png=Image File
extension.ps1=Windows PowerShell Script (.ps1)
extension.sh=Shell Script (.sh)
extension.ps1=Windows PowerShell Script
extension.sh=Shell Script
fatal.javafx.incompatible=Missing JavaFX environment.\n\
Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\
@@ -493,12 +494,12 @@ game.crash.reason.fabric_warnings=The Fabric Loader warned:\n\
game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\
\n\
You can try backing up and deleting that file, then relaunching the game.
game.crash.reason.file_changed=The game crashed because the file "%1$s" already exists.\n\
game.crash.reason.file_changed=The game crashed because the file verification failed.\n\
\n\
You can try backing up and deleting that file, then relaunching the game.
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource packs.\n\
If you modified the Minecraft.jar file, you will need to revert the modification or re-download the game.
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource/texture packs.\n\
\n\
Please disable the mods, shaders, or resource packs you are using and then try again.
Please disable the mods, shaders, or resource/texture packs you are using and then try again.
game.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\n\
\n\
Please try again after updating your graphics driver to the latest version.\n\
@@ -517,7 +518,7 @@ game.crash.reason.install_mixinbootstrap=The game crashed because of the missing
You can try installing <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem.
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\n\
\n\
Please navigate to<a href="https://optifine.net/downloads">the official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
Please navigate to the <a href="https://optifine.net/downloads">official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
\n\
After testing, we believe that too high or too low OptiFine versions may cause crashes.
game.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\n\
@@ -533,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\
@@ -561,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\
@@ -588,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".
@@ -611,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\
@@ -632,9 +633,9 @@ game.crash.reason.out_of_memory=The game crashed because the computer ran out of
Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
\n\
If you still encounter these problems, you may need a better computer.
game.crash.reason.resolution_too_high=The game crashed because the resource pack resolution is too high.\n\
game.crash.reason.resolution_too_high=The game crashed because the resource/texture pack resolution is too high.\n\
\n\
You should switch to a resource pack with lower resolution or consider buying a better graphics card with more VRAM.
You should switch to a resource/texture pack with lower resolution or consider buying a better graphics card with more VRAM.
game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\
\n\
There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\
@@ -653,7 +654,7 @@ game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of m
Otherwise, if you believe this is caused by HMCL, please provide feedback to us.
game.crash.title=Game Crashed
game.directory=Game Path
game.version=Game Version
game.version=Game Instance
help=Help
help.doc=Hello Minecraft! Launcher Documentation
@@ -681,7 +682,7 @@ install.installer.choose=Choose Your %s Version
install.installer.depend=Requires %s
install.installer.fabric=Fabric
install.installer.fabric-api=Fabric API
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API won't function. If you do want to change the directory, you should reinstall it.
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not function. If you do want to change the directory, you should reinstall it.
install.installer.forge=Forge
install.installer.neoforge=NeoForge
install.installer.game=Minecraft
@@ -856,16 +857,16 @@ 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
@@ -903,7 +904,7 @@ modpack.wizard.step.2=Choose Files
modpack.wizard.step.2.title=Choose files you wanted to add to the modpack.
modpack.wizard.step.3=Modpack Type
modpack.wizard.step.3.title=Choose modpack type you wanted to export as.
modpack.wizard.step.initialization.exported_version=Game version to export
modpack.wizard.step.initialization.exported_version=Game Instance to Export
modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server)
modpack.wizard.step.initialization.include_launcher=Include the launcher
modpack.wizard.step.initialization.save=Export to...
@@ -1014,6 +1015,7 @@ mods.download=Download Mod
mods.download.title=Download Mod - %1s
mods.download.recommend=Recommended Mod Version - Minecraft %1s
mods.enable=Enable
mods.game.version=Game Version
mods.manage=Mods
mods.mcbbs=MCBBS
mods.mcmod=MCMod
@@ -1045,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
@@ -1059,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
@@ -1088,6 +1098,7 @@ 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
@@ -1247,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
@@ -1302,12 +1313,12 @@ update.changelog=Changelog
update.channel.dev=Beta
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
\n\
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
update.channel.dev.title=Beta Channel Notice
update.channel.nightly=Nightly
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\
\n\
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
update.channel.nightly.title=Nightly Channel Notice
update.channel.stable=Release
update.checking=Checking for Updates
@@ -123,7 +123,8 @@ account.methods.microsoft.snapshot=Está utilizando una versión no oficial de H
account.methods.microsoft.snapshot.website=Sitio web oficial
account.methods.offline=Sin conexión
account.methods.offline.name.special_characters=Se recomienda utilizar letras en inglés, números y guiones bajos
account.methods.offline.name.invalid=Por lo general, los nombres de usuario de los juegos sólo admiten letras en inglés, números y guiones bajos, y no pueden superar los 16 caracteres de longitud.\n\
account.methods.offline.name.invalid.tip=Se recomienda utilizar sólo letras en inglés, números y guiones bajos para el nombre de usuario, y la longitud no debe superar los 16 caracteres.
account.methods.offline.name.invalid=Se recomienda utilizar sólo letras en inglés, números y guiones bajos para el nombre de usuario, y la longitud no debe superar los 16 caracteres.\n\
\n\
\ · Algunos nombres de usuario legítimos: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Algunos nombres de usuario ilegales: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
@@ -147,12 +148,14 @@ account.skin.file=Archivo de aspecto
account.skin.model=Modelo
account.skin.model.default=Clásico
account.skin.model.slim=Slim
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing skin
account.skin.type.csl_api.location=URL
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
account.skin.type.little_skin=LittleSkin
account.skin.type.little_skin.hint=Tienes que crear un jugador con el mismo nombre de jugador que tu cuenta sin conexión en el sitio web del proveedor de aspectos. Tu aspecto se ajustará ahora al aspecto asignado a tu jugador en el sitio web del proveedor de aspectos.
account.skin.type.local_file=Archivo local de aspecto
account.skin.type.steve=Steve
account.skin.upload=Subir/editar aspecto
account.skin.upload.failed=No se pudo subir el aspecto.
account.skin.invalid_skin=Archivo de aspecto inválido.
@@ -202,14 +205,14 @@ curse.category.4474=Ciencia Ficción
curse.category.4481=Pequeño / Ligero
curse.category.4483=Combate
curse.category.4477=Mini Juego
curse.category.4478=Quests
curse.category.4478=Misiones
curse.category.4484=Multijugador
curse.category.4476=Exploración
curse.category.4736=Skyblock
curse.category.4475=Aventura y RPG
curse.category.4487=FTB
curse.category.4480=Basado en mapa
curse.category.4479=Hardcore
curse.category.4479=Extremo
curse.category.4482=Extra grande
curse.category.4472=Tecnología
curse.category.4473=Magia
@@ -223,10 +226,10 @@ curse.category.5129=Vanilla+
curse.category.5189=Utilidad y calidad de vida
curse.category.5190=Calidad de vida
curse.category.6145=Skyblock
curse.category.6814=Performance
curse.category.6814=Rendimiento
curse.category.6954=Integrated Dynamics
curse.category.6484=Create
curse.category.6821=Bug Fixes
curse.category.6821=Corrección de errores
curse.category.5191=Utilidad y calidad de vida
curse.category.5192=Menú elegante
curse.category.423=Mapa e información
@@ -263,7 +266,7 @@ curse.category.4780=Fabric
curse.category.424=Cosmética
curse.category.406=Generación de mundos
curse.category.435=Utilidades del servidor
curse.category.411=Mobs
curse.category.411=Criaturas
curse.category.407=Biomas
curse.category.427=Thermal Expansion
curse.category.410=Dimensiones
@@ -273,8 +276,8 @@ curse.category.4843=Automatización
curse.category.4906=MCreator
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
curse.category.5244=Fontpack
curse.category.5193=Datapack
curse.category.5244=Paquete de fuentes
curse.category.5193=Paquete de datos
curse.category.399=Steampunk
curse.category.396=128x
curse.category.398=512x y superiores
@@ -355,11 +358,11 @@ exception.access_denied=HMCL no puede acceder al archivo %s. Puede estar bloquea
exception.artifact_malformed=No se puede verificar la integridad de los archivos descargados.
exception.ssl_handshake=No se pudo establecer una conexión SSL porque falta el certificado SSL en la instalación actual de Java. Puede intentar abrir HMCL con otro Java y volver a intentarlo.
extension.bat=Archivo por lotes de Windows (.bat)
extension.mod=Archivo mod.
extension.bat=Archivo por lotes de Windows
extension.mod=Archivo mod
extension.png=Archivo de imagen
extension.ps1=Script de Windows PowerShell (.ps1)
extension.sh=Script de Shell Script (.sh)
extension.ps1=Windows PowerShell Script
extension.sh=Shell Script
fatal.javafx.incompatible=No se encontró un entorno JavaFX.\n\
Hello Minecraft! Launcher no puede instalar automáticamente JavaFX con versiones de Java inferiores a la 11.\n\
@@ -415,9 +418,9 @@ feedback.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios!
file=Archivo
folder.config=Configuraciones
folder.config=Configuraciones de mod
folder.game=Directorio de trabajo
folder.logs=Logs
folder.logs=Registros
folder.mod=Mods
folder.resourcepacks=Paquetes de recursos
folder.shaderpacks=Paquetes de sombreados
@@ -425,180 +428,237 @@ folder.saves=Mundos
folder.screenshots=Capturas de pantalla
game=Juegos
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, por favor haga clic en <b>"Exportar registros de errores"</b> y envíe el archivo exportado a otras personas para su análisis.
game.crash.info=Información sobre el fallo
game.crash.reason=Causa del fallo
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen.
game.crash.info=Información de colgado
game.crash.reason=Motivo del colgado
game.crash.reason.analyzing=Analizando...
game.crash.reason.multiple=Se han detectado varias razones:\n\n
game.crash.reason.block=El juego se ha bloqueado debido a un error.\n\
\n\
Puede tratar de eliminar este bloque utilizando MCEdit o eliminar el mod que lo ha añadido.\n\
\n\
Tipo de bloque: %1$s\n\
Ubicación: %2$s
game.crash.reason.bootstrap_failed=El juego se ha bloqueado debido al mod %1$s.\n\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.config=El juego se ha bloqueado porque un mod %1$s no puede analizar su archivo de configuración %2$s.
game.crash.reason.debug_crash=El juego se ha bloqueado porque lo has activado manualmente.\n\
\n\
Entonces, probablemente sabes por qué.
game.crash.reason.mixin_apply_mod_failed=El juego se ha bloqueado porque Mixin no puede aplicar el mod %1$s, no se puede continuar.\n\
Puedes intentar eliminar o actualizar ese mod para solucionar el problema.
game.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: %1$s.\n\
\n\
%2$s\n\
\n\
Cada mod sólo puede instalarse una vez, por favor, elimine el mod duplicado e inténtelo de nuevo.
game.crash.reason.entity=El juego se ha bloqueado debido a una entidad.\n\
\n\
Puede intentar eliminar esta entidad utilizando MCEdit o eliminar el mod que la ha añadido.\n\
\n\
Tipo de bloque: %1$s\n\
Ubicación: %2$s
game.crash.reason.fabric_version_0_12=La versión de Fabric 0.12 o superior es incompatible con los mods instalados actualmente. Necesitas bajarlo a la versión 0.11.7.
game.crash.reason.fabric_warnings=El cargador de mods de Fabric ha advertido:\n\
%1$s
game.crash.reason.file_already_exists=El juego se ha bloqueado porque el archivo %1$s ya existe.\n\
\n\
Puedes intentar hacer una copia de seguridad y eliminar ese archivo, y luego volver a ejecutar el juego.
game.crash.reason.file_changed=El juego se ha bloqueado porque no ha pasado las comprobaciones de integridad.\n\
\n\
Si has modificado el jar de Minecraft, tendrás que deshacer los cambios, o volver a descargar el juego.
game.crash.reason.gl_operation_failure=El juego se estrelló debido a algunos mods, shaders y/o resource packs.\n\
\n\
Por favor, desactiva los mods/shaders/resource packs que estés utilizando y vuelve a intentarlo.
game.crash.reason.graphics_driver=El juego se ha bloqueado debido a un problema con el controlador gráfico.\n\
\n\
Por favor, inténtelo de nuevo después de actualizar su controlador gráfico a la última versión.\n\
\n\
Si tu ordenador tiene una tarjeta gráfica discreta, tienes que comprobar si el juego utiliza gráficos integrados/núcleo. Si es así, inicie el launcher utilizando su tarjeta gráfica discreta. Si el problema persiste, probablemente debería considerar la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\n\
\n\
Si utiliza su tarjeta gráfica integrada, tenga en cuenta que Minecraft 1.16.5 o anterior requiere <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 o más antiguo</a> para la serie de procesadores Intel(R) Core(TM) 3000 o anteriores.
game.crash.reason.macos_failed_to_find_service_port_for_display=El juego actual no puede continuar debido a un fallo al inicializar la ventana de OpenGL en la plataforma de Apple silicon.\nPara este problema, HMCL no tiene soluciones directas por el momento. Intente abrir cualquier navegador y ponerlo en pantalla completa, luego vuelva a HMCL, inicie el juego y <b>vuelva rápidamente a la página del navegador</b> antes de que aparezca la ventana del juego, espere a que aparezca la ventana del juego y luego vuelva a la ventana del juego.
game.crash.reason.illegal_access_error=El juego se bloquea por culpa de algún(os) mod(s).\n\
\n\
Si sabes: %1$s, puedes actualizar o eliminar los mods y volver a intentarlo.
game.crash.reason.install_mixinbootstrap=El juego actual no se puede seguir ejecutando debido a la falta de MixinBootstrap. \nPuedes intentar instalar <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> para resolver el problema. Si se bloquea tras la instalación, prueba a añadir un "!" delante del nombre de archivo del módulo para intentar solucionar el problema. delante del nombre de archivo del módulo para intentar solucionar el problema.
game.crash.reason.jdk_9=El juego no puede ejecutarse porque la versión de Java es demasiado nueva para esta instancia.\n\
\n\
Es necesario descargar e instalar Java 8 y seleccionarlo en la configuración de la instancia.
game.crash.reason.need_jdk11=El juego actual no puede continuar ejecutándose debido a una versión inapropiada de la máquina virtual Java.\nDebes descargar e instalar Java 11 y configurar Java a una versión que comience con 11 en la configuración global (específica) del juego.
game.crash.reason.jvm_32bit=El juego se ha bloqueado porque la asignación de memoria actual supera el límite de la VM Java de 32 bits.\n\
\n\
Si su sistema operativo es de 64 bits, instale y utilice una versión de 64 bits de Java. De lo contrario, es posible que tenga que volver a instalar un sistema operativo de 64 bits o conseguir un equipo moderno.\n\
\n\
O bien, puede desactivar la asignación automática de memoria y establecer el tamaño máximo de asignación de memoria a 1024MB o menos.
game.crash.reason.loading_crashed_forge=El juego se ha bloqueado debido al mod %1$s (%2$s).\n\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.loading_crashed_fabric=El juego se ha bloqueado debido al mod %1$s.\n\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.memory_exceeded=El juego se ha bloqueado debido a que se ha asignado demasiada memoria para un pequeño archivo de paginación.\n\
\n\
Puede intentar desactivar la opción de asignación automática de memoria en la configuración y ajustar el valor hasta que el juego se inicie.\n\
También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema.
game.crash.reason.mod=El juego se ha bloqueado debido al mod %1$s.\n\
\n\
Puedes actualizar o eliminar el mod y volver a intentarlo.
game.crash.reason.mod_resolution=El juego se ha bloqueado debido a un fallo en la resolución del mod.\n\
\n\
Fabric proporcionó los siguientes detalles\:\n\
%1$s
game.crash.reason.forgemod_resolution=El juego se ha bloqueado debido a un fallo en la resolución del mod.\n\
\n\
Forge proporcionó los siguientes detalles\:\n\
%1$s
game.crash.reason.forge_found_duplicate_mods=El juego no puede continuar debido a un problema de duplicación de mods. Forge proporciona la siguiente información: \n%1$s
game.crash.reason.night_config_fixes=El juego actual no puede seguir ejecutándose debido a algunos problemas con Night Config. \nPuedes intentar instalar el mod <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a>, que puede ayudarte con este problema. \nPara obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
game.crash.reason.mod_resolution_collection=El juego se ha bloqueado porque la versión del mod no es compatible.\n\
\n\
%1$s requiere %2$s.\n\
\n\
Necesitas actualizar o degradar %3$s antes de continuar.
game.crash.reason.mod_resolution_conflict=El juego se ha bloqueado debido a un conflicto de mods.\n\
\n\
%1$s es incompatible con %2$s.
game.crash.reason.mod_resolution_missing=El juego se ha bloqueado porque algunos mods de dependencia no están instalados.\n\
\n\
%1$s requiere mod: %2$s.\n\
\n\
Esto significa que tienes que descargar e instalar %2$s primero para continuar jugando.
game.crash.reason.mod_resolution_missing_minecraft=El juego se ha bloqueado porque un mod es incompatible con la versión actual de Minecraft.\n\
\n\
%1$s requiere la versión de Minecraft %2$s.\n\
\n\
Si quieres jugar con esta versión del mod instalada, debes cambiar la versión de la instancia.\n\
En caso contrario, debes instalar una versión compatible con esta versión de Minecraft.
game.crash.reason.block=El juego se ha colgado debido a un bloqueo en el mundo.\n\
\n\
Puedes intentar eliminar este bloque usando MCEdit o borrando el mod que lo añadió.\n\
\n\
Tipo de bloque: %1$s\n\
Ubicación: %2$s
game.crash.reason.bootstrap_failed=El juego se ha colgado debido al mod "%1$s".\n\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.config=El juego se ha colgado debido a que el mod "%1$s" no ha podido analizar su archivo de configuración "%2$s".
game.crash.reason.debug_crash=El juego se ha colgado porque lo has activado manualmente. Así que probablemente sepas por qué :)
game.crash.reason.mixin_apply_mod_failed=El juego se ha colgado porque no se ha podido aplicar el mixin al mod "%1$s".\n\
\n\
Puedes probar a borrar o actualizar el mod para resolver el problema.
game.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: "%1$s".\n\
\n\
%2$s\n\
\n\
Cada mod sólo puede instalarse una vez, por favor, elimine el mod duplicado e inténtelo de nuevo.
game.crash.reason.entity=El juego se ha colgado debido a una entidad en el mundo.\n\
\n\
Puedes intentar eliminar la entidad usando MCEdit o borrando el mod que la añadió.\n\
\n\
Tipo de entidad: %1$s\n\
Ubicación: %2$s
game.crash.reason.fabric_version_0_12=La versión de Fabric Loader 0.12 o posterior es incompatible con los mods instalados actualmente. Necesitas bajarlo a la versión 0.11.7.
game.crash.reason.fabric_warnings=El cargador de Fabric Loader ha advertido:\n\
\n\
%1$s
game.crash.reason.file_already_exists=El juego se ha colgado porque el archivo "%1$s" ya existe.\n\
\n\
Puedes intentar hacer una copia de seguridad y eliminar ese archivo, y luego volver a ejecutar el juego.
game.crash.reason.file_changed=El juego se ha colgado porque ha fallado la verificación de archivos.\n\
\n\
Si has modificado el jar de Minecraft, tendrás que deshacer los cambios, o volver a descargar el juego.
game.crash.reason.gl_operation_failure=El juego se ha colgado por culpa de algunos mods, shaders o paquetes de recursos/texturas.\n\
\n\
Por favor, desactiva los mods, shaders o paquetes de recursos/texturas que estés utilizando y vuelve a intentarlo.
game.crash.reason.graphics_driver=El juego se ha colgado debido a un problema con el controlador de gráficos.\n\
\n\
Por favor, inténtelo de nuevo después de actualizar su controlador gráfico a la última versión.\n\
\n\
Si tu ordenador tiene una tarjeta gráfica discreta, tienes que comprobar si el juego utiliza gráficos integrados/núcleo. Si es así, inicie el launcher utilizando su tarjeta gráfica discreta. Si el problema persiste, probablemente debería considerar la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\n\
\n\
Si utiliza su tarjeta gráfica integrada, tenga en cuenta que Minecraft 1.16.5 o anterior requiere <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 o más antiguo</a> para la serie de procesadores Intel(R) Core(TM) 3000 o anteriores.
game.crash.reason.macos_failed_to_find_service_port_for_display=El juego no puede continuar debido a un fallo al inicializar la ventana OpenGL en la plataforma Apple silicon.\n\
\n\
Para este problema, HMCL no tiene soluciones directas por el momento. Intente abrir cualquier navegador y ponerlo en pantalla completa, luego vuelva a HMCL, inicie el juego y <b>vuelva rápidamente a la página del navegador</b> antes de que aparezca la ventana del juego, espere a que aparezca la ventana del juego y luego vuelva a la ventana del juego.
game.crash.reason.illegal_access_error=El juego se ha colgado por culpa de algún(os) mod(s).\n\
\n\
Si sabes: %1$s, puedes actualizar o eliminar los mods y volver a intentarlo.
game.crash.reason.install_mixinbootstrap=El juego se ha colgado debido a la falta de MixinBootstrap.\n\
\n\
Puedes intentar instalar <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> para resolver el problema. Si se cuelga después de la instalación, prueba a añadir un signo de exclamación (!) delante del nombre de archivo de este mod para intentar resolver el problema.
game.crash.reason.jdk_9=El juego se ha colgado porque la versión de Java es demasiado nueva para esta instancia.\n\
\n\
Tienes que descargar e instalar Java 8 y elegirlo en "Config. Global/Específica de instancia → Java".
game.crash.reason.need_jdk11=El juego se ha colgado debido a una versión inadecuada de Java.\n\
\n\
Debes descargar e instalar Java 11 y configurarlo en "Config. Global/Específica de instancia → Java".
game.crash.reason.jvm_32bit=El juego se ha colgado porque la asignación de memoria actual excedía el límite de 32 bits JVM.\n\
\n\
Si su sistema operativo es de 64 bits, instale y utilice una versión de Java de 64 bits. De lo contrario, es posible que tenga que volver a instalar un sistema operativo de 64 bits o adquirir un ordenador más moderno.\n\
\n\
O bien, puede desactivar la opción "Asignar automáticamente" en "Config. Global/Específica de instancia → Memoria" y establecer el tamaño máximo de asignación de memoria en 1024 MB o menos.
game.crash.reason.loading_crashed_forge=El juego se ha colgado debido al mod "%1$s" (%2$s).\n\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.loading_crashed_fabric=El juego se ha colgado debido al mod "%1$s".\n\
\n\
Puedes intentar borrarlo o actualizarlo.
game.crash.reason.memory_exceeded=El juego se ha colgado debido a que se ha asignado demasiada memoria a un pequeño archivo de paginación.\n\
\n\
Puedes probar a desactivar la opción "Asignar automáticamente" en "Config. Global/Específica de instancia → Memoria" y ajustar el valor hasta que se inicie el juego.\n\
\n\
También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema.
game.crash.reason.mod=El juego se ha colgado debido al mod "%1$s".\n\
\n\
Puedes actualizar o eliminar el mod y volver a intentarlo.
game.crash.reason.mod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\n\
\n\
Fabric proporcionó los siguientes detalles:\n\
\n\
%1$s
game.crash.reason.forgemod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\n\
\n\
Forge/NeoForge proporcionó los siguientes detalles:\n\
\n\
%1$s
game.crash.reason.forge_found_duplicate_mods=El juego se colgó debido a un problema de mod duplicado. Forge/NeoForge proporcionó la siguiente información:\n\
\n\
%1$s
game.crash.reason.night_config_fixes=El juego se colgó debido a algunos problemas con Night Config.\n\
\n\
Puedes intentar instalar el mod <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a>, que puede ayudarte con este problema.\n\
\n\
Para obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
game.crash.reason.mod_resolution_collection=El juego se colgó porque la versión del mod no es compatible.\n\
\n\
"%1$s" requiere mod "%2$s".\n\
\n\
Necesitas actualizar o degradar "%3$s" antes de continuar.
game.crash.reason.mod_resolution_conflict=El juego se ha colgado debido a mods conflictivos.\n\
\n\
"%1$s" es incompatible con "%2$s".
game.crash.reason.mod_resolution_missing=El juego se ha colgado porque algunos mods dependientes no estaban instalados.\n\
\n\
"%1$s" requiere mod "%2$s".\n\
\n\
Esto significa que tienes que descargar e instalar "%2$s" primero para continuar jugando.
game.crash.reason.mod_resolution_missing_minecraft=El juego se ha colgado porque un mod es incompatible con la versión actual de Minecraft.\n\
\n\
"%1$s" requiere la versión de Minecraft %2$s.\n\
\n\
Si quieres jugar con esta versión del mod instalada, debes cambiar la versión del juego de tu instancia.\n\
\n\
En caso contrario, debes instalar una versión compatible con esta versión de Minecraft.
game.crash.reason.mod_resolution_mod_version=%1$s (Versión: %2$s)
game.crash.reason.mod_resolution_mod_version.any=%1$s (Cualquier versión)
game.crash.reason.forge_repeat_installation=El juego actual no puede continuar ejecutándose debido a una instalación duplicada de Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">Este es un problema conocido</a>\nSe recomienda cargar los comentarios del registro en GitHub para que podamos encontrar más pistas y arreglar esta pregunta. \nActualmente puede ir a la instalación automática para desinstalar Forge y volver a instalarlo.
game.crash.reason.optifine_repeat_installation=El juego actual no puede seguir ejecutándose debido a la instalación repetida de OptiFine. \nElimine OptiFine de la carpeta Mod o vaya a Gestión de juegos-Instalación automática para desinstalar OptiFine que se instala automáticamente.
game.crash.reason.modmixin_failure=El juego actual no puede continuar ejecutándose debido a algunas fallas en la inyección de mods. \nEsto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \nPuede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.mod_repeat_installation=El juego actual tiene múltiples Mods idénticos instalados repetidamente, y cada Mod solo puede aparecer una vez. Elimina los Mods repetidos y luego inicia el juego.
game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s
game.crash.reason.mod_resolution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan.
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática.
game.crash.reason.modlauncher_8=El juego se ha bloqueado porque tu versión actual de Forge no es compatible con tu instalación de Java. Por favor, intente actualizar Forge, o intente utilizar Java 8u312/11.0.13/17.0.1 o versiones anteriores.\n\
\n\
8u312 y anteriores\:\n\
https://oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html\n\
\n\
11.0.13 y anteriores\:\n\
https://oracle.com/java/technologies/javase/jdk11-archive-downloads.html\n\
\n\
17.0.1 y anteriores\:\n\
https://oracle.com/java/technologies/javase/jdk17-archive-downloads.html
game.crash.reason.forge_repeat_installation=El juego se ha colgado debido a una instalación duplicada de Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">Este es un problema conocido</a>\n\
\n\
Se recomienda enviar comentarios en GitHub junto con este registro para que podamos encontrar más pistas y resolver el problema.\n\
\n\
Actualmente puede desinstalar Forge y volver a instalarlo en "Editar Instancia → Cargadores".
game.crash.reason.optifine_repeat_installation=El juego se ha colgado debido a una instalación duplicada de OptiFine.\n\
\n\
Elimine OptiFine del directorio "mods" o desinstálelo en "Editar instancia → Cargadores".
game.crash.reason.modmixin_failure=El juego se ha colgado debido a que algunos mods no se han inyectado.\n\
\n\
Esto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \n\
\n\
Puede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.mod_repeat_installation=El juego se ha colgado debido a los mods duplicados.\n\
\n\
Cada mod sólo puede instalarse una vez. Elimine el mod duplicado y vuelva a ejecutar el juego.
game.crash.reason.forge_error=Forge/NeoForge puede haber proporcionado información errónea.\n\
\n\
Puede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\n\
\n\
Si no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n\
\n\
%1$s
game.crash.reason.mod_resolution0=El juego se ha colgado debido a algunos problemas con el mod. Puede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.java_version_is_too_high=El juego se ha colgado debido a que la versión de Java es demasiado nueva para seguir ejecutándose.\n\
\n\
Cambia la versión principal anterior de Java en "Config. Global/Específica de instancia → Java" y, a continuación, inicia el juego.\n\
\n\
Si no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java 8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java 17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
game.crash.reason.mod_name=El juego se ha colgado debido a problemas con el nombre de archivo del mod.\n\
\n\
Los nombres de los archivos Mod deben usar sólo letras inglesas (A~Z, a~z), números (0~9), líneas horizontales(-), subrayado (_) y puntos (.) a media altura.\n\
\n\
Por favor, vaya al directorio "mods" y cambie todos los nombres de archivo de mods no conformes utilizando los caracteres conformes anteriores.
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\n\
\n\
Reinstale o Forge/NeoForge em "Editar Instância → Carregadores".
game.crash.reason.modlauncher_8=El juego se ha colgado debido a que la versión actual de Forge no es compatible con tu instalación de Java. Intenta actualizar Forge.
game.crash.reason.no_class_def_found_error=El juego no puede ejecutarse debido a un código incompleto.\n\
\n\
A su instancia de juego le falta %1$s, esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\n\
\n\
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
\n\
A su instancia de juego le falta "%1$s", esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\n\
\n\
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
game.crash.reason.no_such_method_error=El juego no puede ejecutarse debido a un código incompleto.\n\
\n\
Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\n\
\n\
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
game.crash.reason.opengl_not_supported=El juego se ha bloqueado porque OpenGL no es compatible con su controlador de gráficos.\n\
\n\
Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\n\
O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\n\
\n\
Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.
game.crash.reason.openj9=El juego no puede ejecutarse en una máquina virtual OpenJ9. Por favor, cambia a una VM Java Hotspot en la configuración del juego y vuelve a ejecutar el juego. Si no tienes una, puedes descargar una en línea.
game.crash.reason.out_of_memory=El juego se ha bloqueado porque se ha quedado sin memoria.\n\
\n\
Esto puede deberse a que no hay suficiente memoria disponible, o a que hay demasiados mods instalados. Puedes intentar solucionarlo aumentando la memoria asignada en la configuración del juego.\n\
Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.
game.crash.reason.resolution_too_high=El juego se ha bloqueado porque estás utilizando un resource packs cuya resolución de textura es demasiado alta.\n\
\n\
Deberías cambiar a un resource packs con menor resolución, o considerar la compra de una mejor tarjeta gráfica con más VRAM.
game.crash.reason.shaders_mod=El juego actual no puede seguir ejecutándose porque tanto OptiFine como Shaders Mod están instalados. \nDebido a que OptiFine ha integrado las funciones de Shaders Mod, simplemente elimine Shaders Mod.
game.crash.reason.rtss_forest_sodium=El juego actual se bloquea debido a una incompatibilidad entre RivaTuner Statistics Server (RTSS) y Sodium, lo que provoca el fallo del juego. Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
game.crash.reason.stacktrace=El motivo del fallo es desconocido. Puede ver los detalles haciendo clic en el botón "Registros".\n\
\n\
Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo.\n\
\n\
%s
game.crash.reason.too_old_java=El juego se ha bloqueado porque estás utilizando una versión histórica de Java VM..\n\
\n\
Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
game.crash.reason.unknown=No somos capaces de averiguar por qué el juego se estrelló, por favor refiérase a los registros del juego.\n\
\n\
<b>Cuando pidas ayuda a otra persona, por favor, comparte con ella el registro completo del juego y el archivo de informe de fallos relacionado.</b>\n\
\n\
¿Esta caída es causada por un problema común que casi todo el mundo tiene? Puedes proporcionar tus registros en un nuevo tema de GitHub para mejorar el launcher.
\n\
Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\n\
\n\
Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.
game.crash.reason.opengl_not_supported=El juego se ha colgado porque tu controlador gráfico no es compatible con OpenGL.\n\
\n\
Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\n\
O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\n\
\n\
Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.
game.crash.reason.openj9=El juego no puede ejecutarse en una JVM OpenJ9. Por favor, cambia a un Java que utilice la JVM Hotspot en "Config. Global/Específica de instancia → Java" y vuelve a ejecutar el juego. Si no dispones de una, puedes descargarla.
game.crash.reason.out_of_memory=El juego se ha colgado porque el ordenador se ha quedado sin memoria.\n\
\n\
Puede que no haya suficiente memoria disponible o que haya demasiados mods instalados. Puedes intentar resolverlo aumentando la memoria asignada en "Config. Global/Específica de instancia → Memoria".\n\
\n\
Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.
game.crash.reason.resolution_too_high=El juego se ha colgado porque la resolución del paquete de recursos/texturas es demasiado alta.\n\
\n\
Deberías cambiar a un paquete de recursos/texturas con menor resolución o considerar la compra de una tarjeta gráfica mejor con más VRAM.
game.crash.reason.shaders_mod=El juego se ha colgado porque OptiFine y Shaders mod están instalados al mismo tiempo.\n\
\n\
Simplemente elimine el mod Shader porque OptiFine tiene soporte incorporado para shaders.
game.crash.reason.rtss_forest_sodium=El juego se colgó porque el RivaTuner Statistical Server (RTSS) no es compatible con Sodium.\n\
\n\
Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
game.crash.reason.stacktrace=El motivo del cuelgue es desconocido. Puedes ver los detalles haciendo clic en el botón "Registros".\n\
\n\
Hay algunas palabras clave que pueden contener algunos nombres de mods. Puedes buscarlas en Internet para averiguar el problema tú mismo.\n\
\n\
%s
game.crash.reason.too_old_java=El juego se ha colgado porque estás utilizando una versión de Java VM obsoleta.\n\
\n\
Tienes que cambiar a una versión de Java más reciente (%1$s) en "Config. Global/Específica de instancia → Java" y, a continuación, volver a ejecutar el juego. Puede descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">aquí</a>.
game.crash.reason.unknown=No somos capaces de averiguar por qué se ha colgado el juego. Por favor, refiérase a los registros del juego.
game.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque faltan bibliotecas: %1$s.\n\
\n\
Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\
Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\
De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.
game.crash.title=Juego interrumpido
\n\
Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\
\n\
Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\
\n\
De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.
game.crash.reason.mac_jdk_8u261=El juego se ha colgado porque tu versión actual de Forge u OptiFine es incompatible con tu instalación de Java.\n\
\n\
Por favor, intente actualizar Forge y OptiFine, o intente utilizar Java 8u251 o versiones anteriores.
game.crash.reason.mod_files_are_decompressed=El juego se ha colgado porque se ha extraído el archivo del mod.\n\
\n\
Por favor, ¡pon el archivo mod completo directamente en el directorio mod!\n\
\n\
Si la extracción provoca errores en el juego, elimine el mod extraído en el directorio de mods y, a continuación, inicie el juego.
game.crash.reason.optifine_causes_the_world_to_fail_to_load=Es posible que el juego no siga ejecutándose debido al OptiFine.\n\
\n\
Este problema solo se produce en una versión específica de OptiFine. Puede probar a cambiar la versión de OptiFine en "Editar instancia → Cargadores".
game.crash.reason.optifine_is_not_compatible_with_forge=El juego se ha colgado porque OptiFine no es compatible con la instalación actual de Forge.\n\
\n\
Por favor, navegue hasta la <a href="https://optifine.net/downloads">página oficial de OptiFine</a>, compruebe si la versión de Forge es compatible con OptiFine, y reinstale la instancia en estricta conformidad con la versión correspondiente, o cambie la versión de OptiFine en "Editar Instancia → Cargadores".\n\
\n\
Tras realizar pruebas, creemos que las versiones de OptiFine demasiado altas o demasiado bajas pueden provocar fallos.
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=El juego se ha colgado porque has instalado demasiados mods y has superado el límite de ID del juego.\n\
\n\
Por favor, intenta instalar <a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> o eliminar algunos mods grandes.
game.crash.title=Juego colgado
game.directory=Ruta del juego
game.version=Versión del juego
game.version=Instancia del juego
help=Ayuda
help.doc=Documentación de Hello Minecraft! Launcher
@@ -680,7 +740,7 @@ java.install.failed.invalid=Este archivo no es un paquete de instalación de Jav
java.install.failed.unsupported_platform=Este Java no es compatible con la plataforma actual, por lo que no puede instalarse.
java.install.name=Nombre
java.install.warning.invalid_character=Carácter ilegal en el nombre
java.reveal=Revelar el directorio de Java
java.reveal=Abrir el directorio Java
java.uninstall=Desinstalar Java
java.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer!
@@ -740,7 +800,7 @@ launcher.background=Imagen de fondo
launcher.background.choose=Elige una imagen de fondo
launcher.background.classic=Clásico
launcher.background.default=Por defecto
launcher.background.default.tooltip=O "background.png/.jpg/.gif/.webp", o imágenes en la carpeta "bg"
launcher.background.default.tooltip=O "background.png/.jpg/.gif/.webp" y las imágenes en el directorio "bg".
launcher.background.network=Desde la URL
launcher.background.translucent=Translúcido
launcher.cache_directory=Directorio de la caché
@@ -763,7 +823,7 @@ logwindow.terminate_game=Terminar proceso
logwindow.title=Registro
logwindow.help=Puede ir a la comunidad HMCL y encontrar a otros para ayudar
logwindow.autoscroll=Desplazamiento automático
logwindow.export_game_crash_logs=Exportar registros de errores
logwindow.export_game_crash_logs=Exportar registros de colgado
logwindow.export_dump=Exportar volcado de pila de juegos
logwindow.export_dump.no_dependency=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a nuestro Discord o grupo QQ para obtener ayuda.
@@ -795,13 +855,14 @@ modpack.completion=Descargando dependencias
modpack.desc=Describa su modpack, incluyendo una introducción y probablemente algún registro de cambios. Actualmente se admiten Markdown e imágenes desde URL.
modpack.description=Descripción
modpack.download=Descargar Modpacks
modpack.download.title=Descargar Modpack - %1s
modpack.enter_name=Introduzca un nombre para este modpack.
modpack.export=Exportar modpack
modpack.export=Exportar como modpack
modpack.export.as=Exportar Modpack como...
modpack.file_api=Prefijo de la URL del modpack
modpack.files.blueprints=Plantillas de BuildCraft
modpack.files.config=Archivo de configuración del Mod
modpack.files.dumps=Archivo de salida de depuración NEI
modpack.files.config=Archivos de configuración del mod
modpack.files.dumps=Archivos de salida de depuración NEI
modpack.files.hmclversion_cfg=Archivo de configuración del launcher
modpack.files.liteconfig=Archivos relacionados con LiteLoader
modpack.files.mods=Mods
@@ -809,7 +870,7 @@ modpack.files.mods.voxelmods=Opciones de VoxelMods
modpack.files.options_txt=Archivo de configuración de Minecraft
modpack.files.optionsshaders_txt=Archivo de configuración de shaders
modpack.files.resourcepacks=Paquetes de recursos/texturas
modpack.files.saves=Mundos de juego
modpack.files.saves=Mundos
modpack.files.scripts=Archivo de configuración de MineTweaker
modpack.files.servers_dat=Archivo de lista de servidores
modpack.install=Instalar Modpack %s
@@ -847,7 +908,7 @@ modpack.wizard.step.2=Elegir archivos
modpack.wizard.step.2.title=Elige los archivos que quieras añadir al modpack.
modpack.wizard.step.3=Tipo de modpack
modpack.wizard.step.3.title=Elija el tipo de modpack que desea exportar.
modpack.wizard.step.initialization.exported_version=Versión del juego a exportar
modpack.wizard.step.initialization.exported_version=Instancia del juego a exportar
modpack.wizard.step.initialization.force_update=Forzar la actualización del modpack a la última versión (necesitarás un servicio de alojamiento de archivos)
modpack.wizard.step.initialization.include_launcher=Incluir el launcher
modpack.wizard.step.initialization.save=Exportar a...
@@ -958,6 +1019,7 @@ mods.download=Descargar mod
mods.download.title=Descargar mod - %1s
mods.download.recommend=Versión recomendada - Minecraft %1s
mods.enable=Activar
mods.game.version=Versión del juego
mods.manage=Mods
mods.mcbbs=MCBBS
mods.mcmod=MCMod
@@ -988,10 +1050,17 @@ web.open_in_browser=Desea abrir esta dirección en un navegador:\n%s
web.view_in_browser=Ver en navegador
world=Mundos
world.add=Añadir un mundo (.zip)
world.add=Añadir mundo
world.backup=Copia de seguridad
world.backup.create.failed=No se ha podido crear la copia de seguridad.\n%s
world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
world.backup.delete=Eliminar esta copia de seguridad
world.backup.reveal=Abrir el directorio
world.backup.title=Mundo [%s] - Copias de seguridad
world.datapack=Gestionar paquetes de datos
world.datapack.1_13=Sólo Minecraft 1.13 o posterior soporta paquetes de datos.
world.description=%s | Jugado por última vez en %s | Versión del juego: %s
world.datetime=Jugado por última vez en %s
world.download=Descargar Mundo
world.export=Exportar el mundo
world.export.title=Elija el directorio para este mundo exportado
@@ -1003,6 +1072,7 @@ world.import.already_exists=Este mundo ya existe.
world.import.choose=Elija el archivo de mundo que desea importar
world.import.failed=No se ha podido importar este mundo: %s
world.import.invalid=No se ha podido analizar el mundo.
world.info=Información del mundo
world.info.title=Mundo [%s] - Información
world.info.basic=Información básica
world.info.allow_cheats=Permitir comandos/trucos
@@ -1032,10 +1102,11 @@ world.info.player.xp_level=Nivel de experiencia
world.info.random_seed=Semilla
world.info.time=Tiempo de juego
world.info.time.format=%s días
world.locked=En uso
world.manage=Mundos
world.name=Nombre del mundo
world.name.enter=Introducir el nombre del mundo
world.reveal=Abrir en el Explorador
world.reveal=Abrir el directorio
world.show_all=Mostrar todo
profile=Directorios del juego
@@ -1151,6 +1222,9 @@ settings.advanced.wrapper_launcher.prompt=Permite ejecutar el juego usando un pr
settings.custom=Personalizado
settings.game=Configuración
settings.game.copy_global=Copiar desde Config. Global
settings.game.copy_global.copy_all=Copiar todo
settings.game.copy_global.copy_all.confirm=¿Está seguro de que desea sobrescribir la configuración actual de la instancia? Esta acción no se puede deshacer.
settings.game.current=Juego
settings.game.dimension=Resolución
settings.game.exploration=Explorar
@@ -1189,7 +1263,7 @@ settings.launcher.language=Idioma
settings.launcher.launcher_log.export=Exportar registros del launcher
settings.launcher.launcher_log.export.failed=No se han podido exportar los registros
settings.launcher.launcher_log.export.success=Los registros se han exportado a "%s"
settings.launcher.launcher_log.reveal=Revelar registros en el Explorador
settings.launcher.launcher_log.reveal=Abrir el directorio
settings.launcher.log=Registro
settings.launcher.log.font=Fuente
settings.launcher.proxy=Proxy
@@ -1223,9 +1297,9 @@ settings.type=Tipo de configuración de la instancia
settings.type.global=Config. Global (compartida)
settings.type.global.manage=Config. Global
settings.type.global.edit=Editar Config. Global
settings.type.special.enable=Habilitar ajustes personalizados para la instancia actual
settings.type.special.enable=Activar Config. Específica de la instancia
settings.type.special.edit=Editar configuración de la instancia actual
settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones.
settings.type.special.edit.hint=La instancia actual "%s" ha activado la "Configuración específica de la instancia". Todas las configuraciones de esta página NO afectarán a esa instancia. Haga clic aquí para editar la configuración de la instancia.
sponsor=Donantes
sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información.
@@ -1292,7 +1366,7 @@ version.manage.manage.title=Editar instancia - %1s
version.manage.redownload_assets_index=Actualizar activos del juego
version.manage.remove=Eliminar instancia
version.manage.remove.confirm=¿Está seguro de que quiere eliminar permanentemente la instancia "%s"? ¡Esta operación no se puede deshacer!
version.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la instancia "%s"? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de «%s».
version.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la instancia "%s"? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de "%s".
version.manage.remove.confirm.independent=Dado que esta instancia está almacenada en un directorio aislado, al eliminarla también se eliminarán sus guardados y otros datos. ¿Aún quieres borrar la instancia %s?
version.manage.remove_assets=Borrar todas las activos del juego
version.manage.remove_libraries=Borrar todas las bibliotecas
@@ -702,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=保存する場所を選択してください
File diff suppressed because it is too large. Load diff
@@ -96,7 +96,7 @@ account.register=註冊
account.manage=帳戶清單
account.copy_uuid=複製該帳戶的 UUID
account.methods=登入方式
account.methods.authlib_injector=authlib-injector 登入
account.methods.authlib_injector=外置登入
account.methods.microsoft=Microsoft 帳戶
account.methods.microsoft.birth=如何變更帳戶出生日期
account.methods.microsoft.deauthorize=移除應用存取權
@@ -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 選項為進階選項,除非你知道你在做什麼,否則你不需要調整該選項。
@@ -422,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 及更低版本,否則你需要更換獨立顯示卡或新電腦。
@@ -460,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 說明文件
@@ -517,7 +520,7 @@ 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=安裝成功
@@ -671,7 +674,7 @@ modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBB
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=模組
@@ -717,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=選取要匯出到的遊戲模組包位置
@@ -818,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 百科
@@ -849,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=儲存到
@@ -862,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=允許指令(作弊)
@@ -891,6 +902,7 @@ 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=世界名稱
@@ -1145,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=更新模組包
@@ -97,7 +97,7 @@ account.register=注册
account.manage=账户列表
account.copy_uuid=复制该账户的 UUID
account.methods=登录方式
account.methods.authlib_injector=外置登录 (authlib-injector)
account.methods.authlib_injector=外置登录
account.methods.microsoft=微软账户
account.methods.microsoft.birth=如何更改账户出生日期
account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成其余登录步骤。你现在可以关闭本页面了。
@@ -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
@@ -424,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\
@@ -471,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=原因未知,请点击日志按钮查看详细信息。
@@ -682,7 +685,7 @@ 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=模组
@@ -829,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 百科
@@ -860,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=保存到
@@ -874,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=允许命令(作弊)
@@ -903,6 +914,7 @@ 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=输入世界名称
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
dependencies {
api("org.glavo:simple-png-javafx:0.3.0")
api("com.google.code.gson:gson:2.11.0")
api("com.google.code.gson:gson:2.12.1")
api("com.moandjiezana.toml:toml4j:0.7.2")
api("org.tukaani:xz:1.10")
api("org.hildan.fxgson:fx-gson:5.0.0")
@@ -84,7 +84,7 @@ public final class MicrosoftAccount extends OAuthAccount {
@Override
public AuthInfo logIn() throws AuthenticationException {
if (!authenticated) {
if (!authenticated || System.currentTimeMillis() > session.getNotAfter()) {
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
authenticated = true;
} else {
@@ -269,5 +269,5 @@ public class YggdrasilService {
.registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)
.create();
public static final String PURCHASE_URL = "https://www.microsoft.com/store/productId/9NXP44L49SHJ";
public static final String PURCHASE_URL = "https://www.microsoft.com/store/productid/9nxp44l49shj";
}
@@ -518,6 +518,14 @@ public class DefaultGameRepository implements GameRepository {
return new ModManager(this, version);
}
public Path getSavesDirectory(String id) {
return getRunDirectory(id).toPath().resolve("saves");
}
public Path getBackupsDirectory(String id) {
return getRunDirectory(id).toPath().resolve("backups");
}
@Override
public String toString() {
return new ToStringBuilder(this)
@@ -22,14 +22,16 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.Unzipper;
import org.jackhuang.hmcl.util.io.Zipper;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.util.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.List;
import java.util.stream.Collectors;
@@ -46,6 +48,8 @@ public class World {
private String worldName;
private String gameVersion;
private long lastPlayed;
private Image icon;
private boolean isLocked;
public World(Path file) throws IOException {
this.file = file;
@@ -62,6 +66,18 @@ public class World {
fileName = FileUtils.getName(file);
Path levelDat = file.resolve("level.dat");
getWorldName(levelDat);
isLocked = isLocked(getSessionLockFile());
Path iconFile = file.resolve("icon.png");
if (Files.isRegularFile(iconFile)) {
try (InputStream inputStream = Files.newInputStream(iconFile)) {
icon = new Image(inputStream, 64, 64, true, false);
if (icon.isError())
throw icon.getException();
} catch (Exception e) {
LOG.warning("Failed to load world icon", e);
}
}
}
public Path getFile() {
@@ -80,6 +96,10 @@ public class World {
return file.resolve("level.dat");
}
public Path getSessionLockFile() {
return file.resolve("session.lock");
}
public long getLastPlayed() {
return lastPlayed;
}
@@ -88,15 +108,35 @@ public class World {
return gameVersion;
}
public Image getIcon() {
return icon;
}
public boolean isLocked() {
return isLocked;
}
private void loadFromZipImpl(Path root) throws IOException {
Path levelDat = root.resolve("level.dat");
if (!Files.exists(levelDat))
throw new IOException("Not a valid world zip file since level.dat cannot be found.");
getWorldName(levelDat);
Path iconFile = root.resolve("icon.png");
if (Files.isRegularFile(iconFile)) {
try (InputStream inputStream = Files.newInputStream(iconFile)) {
icon = new Image(inputStream, 64, 64, true, false);
if (icon.isError())
throw icon.getException();
} catch (Exception e) {
LOG.warning("Failed to load world icon", e);
}
}
}
private void loadFromZip() throws IOException {
isLocked = false;
try (FileSystem fs = CompressingUtils.readonly(file).setAutoDetectEncoding(true).build()) {
Path cur = fs.getPath("/level.dat");
if (Files.isRegularFile(cur)) {
@@ -209,6 +249,26 @@ public class World {
return parseLevelDat(getLevelDatFile());
}
public FileChannel lock() throws WorldLockedException {
Path lockFile = getSessionLockFile();
FileChannel channel = null;
try {
channel = FileChannel.open(lockFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
channel.write(ByteBuffer.wrap("\u2603".getBytes(StandardCharsets.UTF_8)));
channel.force(true);
FileLock fileLock = channel.tryLock();
if (fileLock != null) {
return channel;
} else {
IOUtils.closeQuietly(channel);
throw new WorldLockedException("The world " + getFile() + " has been locked");
}
} catch (IOException e) {
IOUtils.closeQuietly(channel);
throw new WorldLockedException(e);
}
}
public void writeLevelDat(CompoundTag nbt) throws IOException {
if (!Files.isDirectory(file))
throw new IOException("Not a valid world directory");
@@ -230,12 +290,25 @@ public class World {
}
}
private static boolean isLocked(Path sessionLockFile) {
try (FileChannel fileChannel = FileChannel.open(sessionLockFile, StandardOpenOption.WRITE)) {
return fileChannel.tryLock() == null;
} catch (AccessDeniedException accessDeniedException) {
return true;
} catch (NoSuchFileException noSuchFileException) {
return false;
} catch (IOException e) {
LOG.warning("Failed to open the lock file " + sessionLockFile, e);
return false;
}
}
public static Stream<World> getWorlds(Path savesDir) {
try {
if (Files.exists(savesDir)) {
return Files.list(savesDir).flatMap(world -> {
try {
return Stream.of(new World(world));
return Stream.of(new World(world.toAbsolutePath()));
} catch (IOException e) {
LOG.warning("Failed to read world " + world, e);
return Stream.empty();
@@ -0,0 +1,40 @@
/*
* 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.game;
import java.io.IOException;
/**
* @author Glavo
*/
public final class WorldLockedException extends IOException {
public WorldLockedException() {
}
public WorldLockedException(String message) {
super(message);
}
public WorldLockedException(String message, Throwable cause) {
super(message, cause);
}
public WorldLockedException(Throwable cause) {
super(cause);
}
}
@@ -31,6 +31,7 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
@@ -113,6 +114,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
} else {
throw e;
}
} catch (NoSuchFileException e) {
return Optional.empty();
}
}
@@ -270,13 +270,13 @@ public abstract class Task<T> {
/**
* @throws InterruptedException if current thread is interrupted
* @see Thread#interrupted
* @see Thread#isInterrupted()
*/
public void preExecute() throws Exception {}
/**
* @throws InterruptedException if current thread is interrupted
* @see Thread#interrupted
* @see Thread#isInterrupted()
*/
public abstract void execute() throws Exception;
@@ -292,7 +292,7 @@ public abstract class Task<T> {
* {@link Task#isRelyingOnDependencies()} returns true or false.
*
* @throws InterruptedException if current thread is interrupted
* @see Thread#interrupted
* @see Thread#isInterrupted()
* @see Task#isDependenciesSucceeded()
*/
public void postExecute() throws Exception {}