Compare commits

...
24 Commits
Author SHA1 Message Date
neveler 078c2ad98b 优化 SettingsPage 内边距 (#5077) 2025-12-26 21:06:46 +08:00
neveler 830d9a05e7 优化不透明度滑块显示效果 (#5076) 2025-12-26 20:54:35 +08:00
Glavo 7dfeebff39 更新 placeholder 标签的颜色 (#5057) 2025-12-24 21:13:26 +08:00
mineDiamond 05a414c93f feat: 更新 NBT 图标 (#5062) 2025-12-24 21:10:59 +08:00
3gf8jv4dv 16a432125b fix: reorder items in explore menu on edit instance page (#5059)
resolves #4940
2025-12-24 21:05:59 +08:00
Glavo 573e25ecdf 修复 JFXPopup 在 HiDPI 显示器上播放动画过程中内容 Y 轴方向缩放异常的问题 (#5064) 2025-12-24 20:38:20 +08:00
Glavo 821cc6d768 简化 JFXListViewSkin (#5063) 2025-12-24 01:24:44 +08:00
Glavo c13b5e15d4 修复 JFXListViewSkin 大量触发 ListCell#updateItem 的问题 (#5056) 2025-12-24 00:41:23 +08:00
3gf8jv4dv 3eea76784d fix: correct the MB to MiB in the game crash window (#5061) 2025-12-24 00:28:28 +08:00
Calboot 22c66b2790 [Bugfix] 修复导出整合包有关以及NBT文件查看有关的界面样式及深色模式问题 (#5043)
Fixes #5042
2025-12-23 22:25:21 +08:00
mineDiamond fcabe9ca36 [Feature] [UI] 统一账户选择弹窗在列表为空时的表现 (#5054) 2025-12-23 21:52:43 +08:00
3gf8jv4dv 2af3c8f714 fix(l10n): simplify the text for settings.memory in simpchinese (#5047)
resolves https://github.com/HMCL-dev/HMCL/issues/5041
2025-12-23 21:33:34 +08:00
Glavo 253440844e 优化 JFXListView 的平滑滚动效果 (#5052) 2025-12-23 20:20:52 +08:00
Glavo cc87f6cdd1 禁用 LogWindow 的平滑滚动效果 (#5046) 2025-12-23 20:11:46 +08:00
辞庐 a77ef95ce4 取消 FXUtils#onClicked 的连点限制 (#4882) 2025-12-22 21:56:01 +08:00
Glavo 7fb7b00836 简化 GlobalConfig (#4781) 2025-12-22 21:09:56 +08:00
Glavo 3252de2f3f Update PLATFORM.md (#5045) 2025-12-22 20:48:30 +08:00
Calboot 9b0b975490 [Enhancement] 导出整合包时隐藏系统资源文件 (#4897)
Resolves #4785
2025-12-21 21:49:19 +08:00
mineDiamond 449acbc7dc feat: 优化InputDialogPane体验 (#5032) 2025-12-21 21:41:20 +08:00
神奇的赵二 (OrzMiku) 1c82e4e1e1 ModListPage 刷新时不滚动到顶部 (#5016) 2025-12-21 21:40:47 +08:00
辞庐 77cdc2108f 在非 Windows 平台也将 : 视为非法字符 (#5035) 2025-12-21 21:20:54 +08:00
neveler cc092f535b 使用 subtitle 显示重启生效提示 (#5024)
close #4956
2025-12-21 21:09:42 +08:00
Glavo 13930f8dcc Bump version root to 3.10 (#5028) 2025-12-21 20:22:22 +08:00
neveler ebad1f67ce 优化同步工作流 (#5036) 2025-12-21 16:25:45 +08:00
65 changed files with 542 additions and 354 deletions

No files matched your search

+6 -5
View File
@@ -11,8 +11,9 @@ concurrency:
jobs:
mirror:
strategy:
fail-fast: false
matrix:
target:
include:
- name: Gitee
repo: gitee.com/huanghongxun/HMCL
user: 'hmcl-sync'
@@ -21,12 +22,12 @@ jobs:
repo: cnb.cool/HMCL-dev/HMCL
user: 'cnb'
token: 'CNB_SYNC_TOKEN'
name: Mirror to ${{ matrix.target.name }}
name: Mirror to ${{ matrix.name }}
if: ${{ github.repository == 'HMCL-dev/HMCL' }}
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub to ${{ matrix.target.name }}
- name: Mirror GitHub to ${{ matrix.name }}
run: |
git clone --mirror "https://github.com/$GITHUB_REPOSITORY.git" -- repo
git clone --mirror "https://github.com/${{ github.repository }}.git" -- repo
cd repo
git push -f --prune "https://${{ matrix.target.user }}:${{ secrets[matrix.target.token] }}@${{ matrix.target.repo }}.git" "refs/heads/*:refs/heads/*" "refs/tags/*:refs/tags/*"
git push -f --prune "https://${{ matrix.user }}:${{ secrets[matrix.token] }}@${{ matrix.repo }}.git" "refs/heads/*:refs/heads/*" "refs/tags/*:refs/tags/*"
@@ -0,0 +1,171 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.jfoenix.controls;
import com.jfoenix.skins.JFXPopupSkin;
import javafx.application.Platform;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.PopupControl;
import javafx.scene.control.Skin;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.stage.Window;
/// JFXPopup is the material design implementation of a popup.
///
/// @author Shadi Shaheen
/// @version 2.0
/// @since 2017-03-01
@DefaultProperty(value = "popupContent")
public class JFXPopup extends PopupControl {
public enum PopupHPosition {
RIGHT, LEFT
}
public enum PopupVPosition {
TOP, BOTTOM
}
/// Creates empty popup.
public JFXPopup() {
this(null);
}
/// creates popup with a specified container and content
///
/// @param content the node that will be shown in the popup
public JFXPopup(Region content) {
setPopupContent(content);
initialize();
}
private void initialize() {
this.setAutoFix(false);
this.setAutoHide(true);
this.setHideOnEscape(true);
this.setConsumeAutoHidingEvents(false);
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
}
@Override
protected Skin<?> createDefaultSkin() {
return new JFXPopupSkin(this);
}
/***************************************************************************
* *
* Setters / Getters *
* *
**************************************************************************/
private final ObjectProperty<Region> popupContent = new SimpleObjectProperty<>(new Pane());
public final ObjectProperty<Region> popupContentProperty() {
return this.popupContent;
}
public final Region getPopupContent() {
return this.popupContentProperty().get();
}
public final void setPopupContent(final Region popupContent) {
this.popupContentProperty().set(popupContent);
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
/// show the popup using the default position
public void show(Node node) {
this.show(node, PopupVPosition.TOP, PopupHPosition.LEFT, 0, 0);
}
/// show the popup according to the specified position
///
/// @param vAlign can be TOP/BOTTOM
/// @param hAlign can be LEFT/RIGHT
public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign) {
this.show(node, vAlign, hAlign, 0, 0);
}
/// show the popup according to the specified position with a certain offset
///
/// @param vAlign can be TOP/BOTTOM
/// @param hAlign can be LEFT/RIGHT
/// @param initOffsetX on the x-axis
/// @param initOffsetY on the y-axis
public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) {
if (!isShowing()) {
if (node.getScene() == null || node.getScene().getWindow() == null) {
throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window.");
}
Window parent = node.getScene().getWindow();
final Point2D origin = node.localToScene(0, 0);
final double anchorX = parent.getX() + origin.getX()
+ node.getScene().getX() + (hAlign == PopupHPosition.RIGHT ? ((Region) node).getWidth() : 0);
final double anchorY = parent.getY() + origin.getY()
+ node.getScene()
.getY() + (vAlign == PopupVPosition.BOTTOM ? ((Region) node).getHeight() : 0);
this.show(parent, anchorX, anchorY);
((JFXPopupSkin) getSkin()).reset(vAlign, hAlign, initOffsetX, initOffsetY);
Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate());
}
}
public void show(Window window, double x, double y, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) {
if (!isShowing()) {
if (window == null) {
throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window.");
}
Window parent = window;
final double anchorX = parent.getX() + x + initOffsetX;
final double anchorY = parent.getY() + y + initOffsetY;
this.show(parent, anchorX, anchorY);
((JFXPopupSkin) getSkin()).reset(vAlign, hAlign, initOffsetX, initOffsetY);
Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate());
}
}
@Override
public void hide() {
super.hide();
((JFXPopupSkin) getSkin()).init();
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
/// Initialize the style class to 'jfx-popup'.
///
/// This is the selector class from which CSS can be used to style
/// this control.
private static final String DEFAULT_STYLE_CLASS = "jfx-popup";
}
@@ -20,26 +20,24 @@
*/
package com.jfoenix.skins;
import com.jfoenix.adapters.skins.ListViewSkin;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.effects.JFXDepthManager;
import javafx.scene.control.ListCell;
import javafx.scene.control.skin.ListViewSkin;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.ui.FXUtils;
// https://github.com/HMCL-dev/HMCL/issues/4720
public class JFXListViewSkin<T> extends ListViewSkin<T> {
private final VirtualFlow<ListCell<T>> flow;
@SuppressWarnings("unchecked")
public JFXListViewSkin(final JFXListView<T> listView) {
super(listView);
flow = (VirtualFlow<ListCell<T>>) getChildren().get(0);
VirtualFlow<ListCell<T>> flow = getVirtualFlow();
JFXDepthManager.setDepth(flow, listView.depthProperty().get());
listView.depthProperty().addListener((o, oldVal, newVal) -> JFXDepthManager.setDepth(flow, newVal));
FXUtils.smoothScrolling(flow);
if (!Boolean.TRUE.equals(listView.getProperties().get("no-smooth-scrolling"))) {
FXUtils.smoothScrolling(flow);
}
}
@Override
@@ -47,47 +45,4 @@ public class JFXListViewSkin<T> extends ListViewSkin<T> {
return 200;
}
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
final int itemsCount = getSkinnable().getItems().size();
if (getSkinnable().maxHeightProperty().isBound() || itemsCount <= 0) {
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
final double fixedCellSize = getSkinnable().getFixedCellSize();
double computedHeight = fixedCellSize != Region.USE_COMPUTED_SIZE ?
fixedCellSize * itemsCount + snapVerticalInsets() : estimateHeight();
double height = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
if (height > computedHeight) {
height = computedHeight;
}
if (getSkinnable().getMaxHeight() > 0 && computedHeight > getSkinnable().getMaxHeight()) {
return getSkinnable().getMaxHeight();
}
return height;
}
private double estimateHeight() {
// compute the border/padding for the list
double borderWidth = snapVerticalInsets();
// compute the gap between list cells
JFXListView<T> listview = (JFXListView<T>) getSkinnable();
double gap = listview.isExpanded() ? ((JFXListView<T>) getSkinnable()).getVerticalGap() * (getSkinnable().getItems()
.size()) : 0;
// compute the height of each list cell
double cellsHeight = 0;
for (int i = 0; i < flow.getCellCount(); i++) {
ListCell<T> cell = flow.getCell(i);
cellsHeight += cell.getHeight();
}
return cellsHeight + gap + borderWidth;
}
private double snapVerticalInsets() {
return getSkinnable().snappedBottomInset() + getSkinnable().snappedTopInset();
}
}
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2016 JFoenix
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.jfoenix.skins;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.controls.JFXPopup.PopupHPosition;
import com.jfoenix.controls.JFXPopup.PopupVPosition;
import com.jfoenix.effects.JFXDepthManager;
import javafx.animation.*;
import javafx.animation.Animation.Status;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.layout.*;
import javafx.scene.transform.Scale;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.Motion;
/// # Material Design Popup Skin
/// TODO: REWORK
///
/// @author Shadi Shaheen
/// @version 2.0
/// @since 2017-03-01
public class JFXPopupSkin implements Skin<JFXPopup> {
protected JFXPopup control;
protected StackPane container = new StackPane();
protected Region popupContent;
protected Node root;
private Animation animation;
protected Scale scale;
public JFXPopupSkin(JFXPopup control) {
this.control = control;
// set scale y to 0.01 instead of 0 to allow layout of the content,
// otherwise it will cause exception in traverse engine, when focusing the 1st node
scale = new Scale(1.0, 0.01, 0, 0);
popupContent = control.getPopupContent();
container.getStyleClass().add("jfx-popup-container");
container.getChildren().add(popupContent);
container.getTransforms().add(scale);
container.setOpacity(0);
root = JFXDepthManager.createMaterialNode(container, 4);
animation = AnimationUtils.isAnimationEnabled() ? getAnimation() : null;
}
public void reset(PopupVPosition vAlign, PopupHPosition hAlign, double offsetX, double offsetY) {
// postion the popup according to its animation
scale.setPivotX(hAlign == PopupHPosition.RIGHT ? container.getWidth() : 0);
scale.setPivotY(vAlign == PopupVPosition.BOTTOM ? container.getHeight() : 0);
root.setTranslateX(hAlign == PopupHPosition.RIGHT ? -container.getWidth() + offsetX : offsetX);
root.setTranslateY(vAlign == PopupVPosition.BOTTOM ? -container.getHeight() + offsetY : offsetY);
}
public final void animate() {
if (animation != null) {
if (animation.getStatus() == Status.STOPPED) {
container.setOpacity(1);
animation.playFromStart();
}
} else {
container.setOpacity(1);
popupContent.setOpacity(1);
scale.setX(1.0);
scale.setY(1.0);
}
}
@Override
public JFXPopup getSkinnable() {
return control;
}
@Override
public Node getNode() {
return root;
}
@Override
public void dispose() {
if (animation != null) {
animation.stop();
animation = null;
}
container = null;
control = null;
popupContent = null;
root = null;
}
protected Animation getAnimation() {
Interpolator interpolator = Motion.EASE;
return new Timeline(
new KeyFrame(
Duration.ZERO,
new KeyValue(popupContent.opacityProperty(), 0, interpolator),
new KeyValue(scale.xProperty(), 0, interpolator),
new KeyValue(scale.yProperty(), 0, interpolator)
),
new KeyFrame(Motion.SHORT4,
new KeyValue(popupContent.opacityProperty(), 0, interpolator),
new KeyValue(scale.xProperty(), 1, interpolator)
),
new KeyFrame(Motion.MEDIUM2,
new KeyValue(popupContent.opacityProperty(), 1, interpolator),
new KeyValue(scale.yProperty(), 1, interpolator)
)
);
}
public void init() {
if (animation != null)
animation.stop();
container.setOpacity(0);
scale.setX(1.0);
scale.setY(0.01);
}
}
@@ -19,131 +19,109 @@ package org.jackhuang.hmcl.setting;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.util.gson.ObservableSetting;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Type;
import java.util.*;
@JsonAdapter(GlobalConfig.Serializer.class)
public final class GlobalConfig implements Observable {
@JsonAdapter(GlobalConfig.Adapter.class)
public final class GlobalConfig extends ObservableSetting {
@Nullable
public static GlobalConfig fromJson(String json) throws JsonParseException {
GlobalConfig loaded = Config.CONFIG_GSON.fromJson(json, GlobalConfig.class);
if (loaded == null) {
return null;
}
GlobalConfig instance = new GlobalConfig();
PropertyUtils.copyProperties(loaded, instance);
instance.unknownFields.putAll(loaded.unknownFields);
return instance;
return Config.CONFIG_GSON.fromJson(json, GlobalConfig.class);
}
private final IntegerProperty agreementVersion = new SimpleIntegerProperty();
private final IntegerProperty terracottaAgreementVersion = new SimpleIntegerProperty();
private final IntegerProperty platformPromptVersion = new SimpleIntegerProperty();
private final IntegerProperty logRetention = new SimpleIntegerProperty();
private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);
private final StringProperty fontAntiAliasing = new SimpleStringProperty();
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
private final Map<String, Object> unknownFields = new HashMap<>();
private final transient ObservableHelper helper = new ObservableHelper(this);
public GlobalConfig() {
PropertyUtils.attachListener(this, helper);
}
@Override
public void addListener(InvalidationListener listener) {
helper.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper.removeListener(listener);
register();
}
public String toJson() {
return Config.CONFIG_GSON.toJson(this);
}
public int getAgreementVersion() {
return agreementVersion.get();
}
@SerializedName("agreementVersion")
private final IntegerProperty agreementVersion = new SimpleIntegerProperty();
public IntegerProperty agreementVersionProperty() {
return agreementVersion;
}
public int getAgreementVersion() {
return agreementVersion.get();
}
public void setAgreementVersion(int agreementVersion) {
this.agreementVersion.set(agreementVersion);
}
public int getTerracottaAgreementVersion() {
return terracottaAgreementVersion.get();
}
@SerializedName("terracottaAgreementVersion")
private final IntegerProperty terracottaAgreementVersion = new SimpleIntegerProperty();
public IntegerProperty terracottaAgreementVersionProperty() {
return terracottaAgreementVersion;
}
public int getTerracottaAgreementVersion() {
return terracottaAgreementVersion.get();
}
public void setTerracottaAgreementVersion(int terracottaAgreementVersion) {
this.terracottaAgreementVersion.set(terracottaAgreementVersion);
}
public int getPlatformPromptVersion() {
return platformPromptVersion.get();
}
@SerializedName("platformPromptVersion")
private final IntegerProperty platformPromptVersion = new SimpleIntegerProperty();
public IntegerProperty platformPromptVersionProperty() {
return platformPromptVersion;
}
public int getPlatformPromptVersion() {
return platformPromptVersion.get();
}
public void setPlatformPromptVersion(int platformPromptVersion) {
this.platformPromptVersion.set(platformPromptVersion);
}
public int getLogRetention() {
return logRetention.get();
}
@SerializedName("logRetention")
private final IntegerProperty logRetention = new SimpleIntegerProperty(20);
public IntegerProperty logRetentionProperty() {
return logRetention;
}
public int getLogRetention() {
return logRetention.get();
}
public void setLogRetention(int logRetention) {
this.logRetention.set(logRetention);
}
public boolean isEnableOfflineAccount() {
return enableOfflineAccount.get();
}
@SerializedName("enableOfflineAccount")
private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);
public BooleanProperty enableOfflineAccountProperty() {
return enableOfflineAccount;
}
public boolean isEnableOfflineAccount() {
return enableOfflineAccount.get();
}
public void setEnableOfflineAccount(boolean value) {
enableOfflineAccount.set(value);
}
@SerializedName("fontAntiAliasing")
private final StringProperty fontAntiAliasing = new SimpleStringProperty();
public StringProperty fontAntiAliasingProperty() {
return fontAntiAliasing;
}
@@ -156,89 +134,24 @@ public final class GlobalConfig implements Observable {
this.fontAntiAliasing.set(value);
}
@SerializedName("userJava")
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
public ObservableSet<String> getUserJava() {
return userJava;
}
@SerializedName("disabledJava")
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
public ObservableSet<String> getDisabledJava() {
return disabledJava;
}
public static final class Serializer implements JsonSerializer<GlobalConfig>, JsonDeserializer<GlobalConfig> {
private static final Set<String> knownFields = new HashSet<>(Arrays.asList(
"agreementVersion",
"terracottaAgreementVersion",
"platformPromptVersion",
"logRetention",
"userJava",
"disabledJava",
"enableOfflineAccount",
"fontAntiAliasing"
));
static final class Adapter extends ObservableSetting.Adapter<GlobalConfig> {
@Override
public JsonElement serialize(GlobalConfig src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) {
return JsonNull.INSTANCE;
}
JsonObject jsonObject = new JsonObject();
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
jsonObject.add("terracottaAgreementVersion", context.serialize(src.getTerracottaAgreementVersion()));
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
jsonObject.add("logRetention", context.serialize(src.getLogRetention()));
jsonObject.add("fontAntiAliasing", context.serialize(src.getFontAntiAliasing()));
if (src.enableOfflineAccount.get())
jsonObject.addProperty("enableOfflineAccount", true);
if (!src.getUserJava().isEmpty())
jsonObject.add("userJava", context.serialize(src.getUserJava()));
if (!src.getDisabledJava().isEmpty())
jsonObject.add("disabledJava", context.serialize(src.getDisabledJava()));
for (Map.Entry<String, Object> entry : src.unknownFields.entrySet()) {
jsonObject.add(entry.getKey(), context.serialize(entry.getValue()));
}
return jsonObject;
}
@Override
public GlobalConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (!(json instanceof JsonObject)) return null;
JsonObject obj = (JsonObject) json;
GlobalConfig config = new GlobalConfig();
config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setTerracottaAgreementVersion(Optional.ofNullable(obj.get("terracottaAgreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0));
config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20));
config.setEnableOfflineAccount(Optional.ofNullable(obj.get("enableOfflineAccount")).map(JsonElement::getAsBoolean).orElse(false));
config.setFontAntiAliasing(Optional.ofNullable(obj.get("fontAntiAliasing")).map(JsonElement::getAsString).orElse(null));
JsonElement userJava = obj.get("userJava");
if (userJava != null && userJava.isJsonArray()) {
for (JsonElement element : userJava.getAsJsonArray()) {
config.userJava.add(element.getAsString());
}
}
JsonElement disabledJava = obj.get("disabledJava");
if (disabledJava != null && disabledJava.isJsonArray()) {
for (JsonElement element : disabledJava.getAsJsonArray()) {
config.disabledJava.add(element.getAsString());
}
}
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
if (!knownFields.contains(entry.getKey())) {
config.unknownFields.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class));
}
}
return config;
protected GlobalConfig createInstance() {
return new GlobalConfig();
}
}
}
@@ -1309,17 +1309,11 @@ public final class FXUtils {
}
public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {
Holder<Object> lastCell = new Holder<>();
return view -> new JFXListCell<T>() {
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (this == lastCell.value && !isVisible())
return;
lastCell.value = this;
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(graphicBuilder.apply(item));
@@ -1364,7 +1358,7 @@ public final class FXUtils {
public static void onClicked(Node node, Runnable action) {
node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 1) {
if (e.getButton() == MouseButton.PRIMARY) {
action.run();
e.consume();
}
@@ -93,7 +93,7 @@ public class GameCrashWindow extends Stage {
this.logs = logs;
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " MB").orElse("-");
memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " " + i18n("settings.memory.unit.mib")).orElse("-");
total_memory = MEGABYTES.formatBytes(SystemInfo.getTotalMemorySize());
@@ -46,7 +46,6 @@ import org.jackhuang.hmcl.setting.StyleSheets;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.CircularArrayList;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
@@ -204,6 +203,7 @@ public final class LogWindow extends Stage {
LogWindowImpl() {
getStyleClass().add("log-window");
listView.getProperties().put("no-smooth-scrolling", true);
listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size())));
for (int i = 0; i < LEVELS.length; i++) {
@@ -343,8 +343,7 @@ public final class LogWindow extends Stage {
listView.setStyle("-fx-font-family: \"" + Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT)
+ "\"; -fx-font-size: " + config().getFontSize() + "px;");
Holder<Object> lastCell = new Holder<>();
listView.setCellFactory(x -> new ListCell<Log>() {
listView.setCellFactory(x -> new ListCell<>() {
{
x.setSelectionModel(new NoneMultipleSelectionModel<>());
getStyleClass().add("log-window-list-cell");
@@ -388,11 +387,6 @@ public final class LogWindow extends Stage {
protected void updateItem(Log item, boolean empty) {
super.updateItem(item, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (this == lastCell.value && !isVisible())
return;
lastCell.value = this;
pseudoClassStateChanged(EMPTY, empty);
pseudoClassStateChanged(FATAL, !empty && item.getLevel() == Log4jLevel.FATAL);
pseudoClassStateChanged(ERROR, !empty && item.getLevel() == Log4jLevel.ERROR);
@@ -25,7 +25,6 @@ import javafx.animation.Animation.Status;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.input.MouseEvent;
@@ -226,7 +225,6 @@ final class ScrollUtils {
final double[] derivatives = new double[FRICTIONS.length];
Timeline timeline = new Timeline();
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
final EventHandler<ScrollEvent> scrollHandler = event -> {
if (event.getEventType() == ScrollEvent.SCROLL) {
@@ -234,7 +232,6 @@ final class ScrollUtils {
if (scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.RIGHT) {
return;
}
scrollDirectionHolder.value = scrollDirection;
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
derivatives[0] += scrollDirection.intDirection * currentSpeed;
@@ -256,15 +253,7 @@ final class ScrollUtils {
}
double dy = derivatives[derivatives.length - 1];
int cellCount = virtualFlow.getCellCount();
IndexedCell<?> firstVisibleCell = virtualFlow.getFirstVisibleCell();
double height = firstVisibleCell != null ? firstVisibleCell.getHeight() * cellCount : 0.0;
double delta = height > 0.0
? dy / height
: (scrollDirectionHolder.value == ScrollDirection.DOWN ? 0.001 : -0.001);
virtualFlow.setPosition(Math.min(Math.max(virtualFlow.getPosition() + delta, 0), 1));
virtualFlow.scrollPixels(dy);
if (Math.abs(dy) < 0.001) {
timeline.stop();
@@ -30,7 +30,7 @@ import java.util.concurrent.CompletableFuture;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class InputDialogPane extends JFXDialogLayout {
public class InputDialogPane extends JFXDialogLayout implements DialogAware {
private final CompletableFuture<String> future = new CompletableFuture<>();
private final JFXTextField textField;
@@ -70,9 +70,15 @@ public class InputDialogPane extends JFXDialogLayout {
});
});
textField.setOnAction(event -> acceptButton.fire());
onEscPressed(this, cancelButton::fire);
}
@Override
public void onDialogShown() {
textField.requestFocus();
}
public CompletableFuture<String> getCompletableFuture() {
return future;
}
@@ -24,17 +24,14 @@ import javafx.scene.control.ListCell;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Holder;
public abstract class MDListCell<T> extends ListCell<T> {
private final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private final StackPane container = new StackPane();
private final StackPane root = new StackPane();
private final Holder<Object> lastCell;
public MDListCell(JFXListView<T> listView, Holder<Object> lastCell) {
this.lastCell = lastCell;
public MDListCell(JFXListView<T> listView) {
setText(null);
setGraphic(null);
@@ -58,13 +55,6 @@ public abstract class MDListCell<T> extends ListCell<T> {
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (lastCell != null) {
if (this == lastCell.value && !isVisible())
return;
lastCell.value = this;
}
updateControl(item, empty);
if (empty) {
setGraphic(null);
@@ -25,8 +25,6 @@ import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
@@ -64,12 +62,7 @@ public class OptionToggleButton extends StackPane {
toggleButton.setSize(8);
FXUtils.setLimitHeight(toggleButton, 30);
container.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.PRIMARY) {
toggleButton.setSelected(!toggleButton.isSelected());
e.consume();
}
});
FXUtils.onClicked(container, () -> toggleButton.setSelected(!toggleButton.isSelected()));
FXUtils.onChangeAndOperate(subtitleProperty(), subtitle -> {
if (StringUtils.isNotBlank(subtitle)) {
@@ -92,8 +92,6 @@ public final class TaskListPane extends StackPane {
private final ObjectProperty<Insets> progressNodePadding = new SimpleObjectProperty<>(Insets.EMPTY);
private final DoubleProperty cellWidth = new SimpleDoubleProperty();
private Cell lastCell;
public TaskListPane() {
listView.setPadding(new Insets(12, 0, 0, 0));
listView.setCellFactory(l -> new Cell());
@@ -316,11 +314,6 @@ public final class TaskListPane extends StackPane {
protected void updateItem(Node item, boolean empty) {
super.updateItem(item, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (this == lastCell && !isVisible())
return;
lastCell = this;
pane.paddingProperty().unbind();
title.textProperty().unbind();
message.textProperty().unbind();
@@ -57,7 +57,6 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.NativePatcher;
import org.jackhuang.hmcl.util.SettingsMap;
import org.jackhuang.hmcl.util.StringUtils;
@@ -164,10 +163,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
private final ImageView imageView = new ImageView();
private final StackPane pane = new StackPane();
private final Holder<RemoteVersionListCell> lastCell;
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell, VersionsPage control) {
this.lastCell = lastCell;
RemoteVersionListCell(VersionsPage control) {
this.control = control;
HBox hbox = new HBox(16);
@@ -222,11 +218,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
public void updateItem(RemoteVersion remoteVersion, boolean empty) {
super.updateItem(remoteVersion, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (this == lastCell.value && !isVisible())
return;
lastCell.value = this;
if (empty) {
setGraphic(null);
return;
@@ -398,8 +389,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
control.versions.addListener((InvalidationListener) o -> updateList());
Holder<RemoteVersionListCell> lastCell = new Holder<>();
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, control));
list.setCellFactory(listView -> new RemoteVersionListCell(control));
ComponentList.setVgrow(list, Priority.ALWAYS);
@@ -18,10 +18,10 @@
package org.jackhuang.hmcl.ui.export;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXTreeView;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
@@ -43,8 +43,8 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -68,6 +68,8 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft");
treeView.setRoot(rootNode);
treeView.setSelectionModel(new NoneMultipleSelectionModel<>());
onEscPressed(treeView, () -> controller.onPrev(true));
setMargin(treeView, new Insets(10, 10, 5, 10));
this.setCenter(treeView);
HBox nextPane = new HBox();
@@ -93,9 +95,22 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
if (basePath.length() > "minecraft/".length()) {
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (isDirectory ? "/" : ""), isDirectory);
if (!isDirectory && Objects.equals(FileUtils.getNameWithoutExtension(file), version))
state = ModAdviser.ModSuggestion.HIDDEN;
if (isDirectory && Objects.equals(FileUtils.getName(file), version + "-natives")) // Ignore <version>-natives
String fileName = FileUtils.getName(file);
if (!isDirectory) {
switch (fileName) {
case ".DS_Store", // macOS system file
"desktop.ini", "Thumbs.db" // Windows system files
-> state = ModAdviser.ModSuggestion.HIDDEN;
}
if (fileName.startsWith("._")) // macOS system file
state = ModAdviser.ModSuggestion.HIDDEN;
if (FileUtils.getNameWithoutExtension(file).equals(version))
state = ModAdviser.ModSuggestion.HIDDEN;
}
if (isDirectory && fileName.equals(version + "-natives")) // Ignore <version>-natives
state = ModAdviser.ModSuggestion.HIDDEN;
if (state == ModAdviser.ModSuggestion.HIDDEN)
return null;
@@ -130,15 +145,14 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
}
HBox graphic = new HBox();
CheckBox checkBox = new CheckBox();
JFXCheckBox checkBox = new JFXCheckBox();
checkBox.selectedProperty().bindBidirectional(node.selectedProperty());
checkBox.indeterminateProperty().bindBidirectional(node.indeterminateProperty());
graphic.getChildren().add(checkBox);
if (TRANSLATION.containsKey(basePath)) {
Label comment = new Label();
comment.setText(TRANSLATION.get(basePath));
comment.setStyle("-fx-text-fill: gray;");
Label comment = new Label(TRANSLATION.get(basePath));
comment.setStyle("-fx-text-fill: -monet-on-surface-variant;");
comment.setMouseTransparent(true);
graphic.getChildren().add(comment);
}
@@ -20,10 +20,10 @@ package org.jackhuang.hmcl.ui.export;
import com.jfoenix.controls.JFXButton;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.SVGPath;
import org.jackhuang.hmcl.mod.ModpackExportInfo;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
@@ -77,8 +77,7 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
graphic.setMouseTransparent(true);
graphic.setLeft(new TwoLineListItem(i18n("modpack.type." + type), i18n("modpack.type." + type + ".export")));
SVGPath arrow = new SVGPath();
arrow.setContent(SVG.ARROW_FORWARD.getPath());
Node arrow = SVG.ARROW_FORWARD.createIcon();
BorderPane.setAlignment(arrow, Pos.CENTER);
graphic.setRight(arrow);
@@ -391,7 +391,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
Node contentNode;
if (menu.getContent().isEmpty()) {
Label placeholder = new Label(i18n("version.empty"));
placeholder.setStyle("-fx-padding: 10px; -fx-text-fill: gray; -fx-font-style: italic;");
placeholder.setStyle("-fx-padding: 10px; -fx-text-fill: -monet-on-surface-variant; -fx-font-style: italic;");
contentNode = placeholder;
} else {
contentNode = menu;
@@ -126,6 +126,7 @@ public class PersonalizationPage extends StackPane {
themeList.getContent().add(animationButton);
animationButton.selectedProperty().bindBidirectional(config().animationDisabledProperty());
animationButton.setTitle(i18n("settings.launcher.turn_off_animations"));
animationButton.setSubtitle(i18n("settings.take_effect_after_restart"));
}
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.appearance")), themeList);
@@ -172,6 +173,7 @@ public class PersonalizationPage extends StackPane {
slider.setMinorTickCount(1);
slider.setBlockIncrement(5);
slider.setSnapToTicks(true);
slider.setPadding(new Insets(9, 0, 0, 0));
HBox.setHgrow(slider, Priority.ALWAYS);
if (config().getBackgroundImageType() == EnumBackgroundImage.TRANSLUCENT) {
@@ -299,8 +301,12 @@ public class PersonalizationPage extends StackPane {
{
BorderPane fontAntiAliasingPane = new BorderPane();
{
Label left = new Label(i18n("settings.launcher.font.anti_aliasing"));
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
VBox left = new VBox();
Label title = new Label(i18n("settings.launcher.font.anti_aliasing"));
title.getStyleClass().add("title");
Label subtitle = new Label(i18n("settings.take_effect_after_restart"));
subtitle.getStyleClass().add("subtitle");
left.getChildren().setAll(title, subtitle);
fontAntiAliasingPane.setLeft(left);
}
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.main;
import com.jfoenix.controls.JFXPopup;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.Account;
@@ -149,8 +150,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
accountListItem.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.SECONDARY) {
if (!Accounts.getAccounts().isEmpty())
showAccountListPopupMenu(accountListItem);
showAccountListPopupMenu(accountListItem);
e.consume();
}
});
@@ -265,13 +265,19 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
scrollPane.setPrefHeight(-1);
scrollPane.setMaxHeight(260);
for (Account account : Accounts.getAccounts()) {
AccountAdvancedListItem item = new AccountAdvancedListItem(account);
item.setOnAction(e -> {
Accounts.setSelectedAccount(account);
popup.hide();
});
scrollPane.add(item);
if (Accounts.getAccounts().isEmpty()) {
Label placeholder = new Label(i18n("account.empty"));
placeholder.setStyle("-fx-padding: 10px; -fx-text-fill: -monet-on-surface-variant; -fx-font-style: italic;");
scrollPane.add(placeholder);
} else {
for (Account account : Accounts.getAccounts()) {
AccountAdvancedListItem item = new AccountAdvancedListItem(account);
item.setOnAction(e -> {
Accounts.setSelectedAccount(account);
popup.hide();
});
scrollPane.add(item);
}
}
popupMenu.getContent().add(scrollPane);
@@ -88,7 +88,7 @@ public final class SettingsPage extends ScrollPane {
this.setFitToWidth(true);
VBox rootPane = new VBox();
rootPane.setPadding(new Insets(32, 10, 32, 10));
rootPane.setPadding(new Insets(10));
this.setContent(rootPane);
FXUtils.smoothScrolling(this);
@@ -254,8 +254,12 @@ public final class SettingsPage extends ScrollPane {
{
BorderPane languagePane = new BorderPane();
Label left = new Label(i18n("settings.launcher.language"));
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
VBox left = new VBox();
Label title = new Label(i18n("settings.launcher.language"));
title.getStyleClass().add("title");
Label subtitle = new Label(i18n("settings.take_effect_after_restart"));
subtitle.getStyleClass().add("subtitle");
left.getChildren().setAll(title, subtitle);
languagePane.setLeft(left);
SupportedLocale currentLocale = I18n.getLocale();
@@ -89,7 +89,10 @@ public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
setLoading(false);
root.setCenter(new NBTTreeView(result));
NBTTreeView view = new NBTTreeView(result);
BorderPane.setMargin(view, new Insets(10));
onEscPressed(view, cancelButton::fire);
root.setCenter(view);
} 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);
@@ -20,11 +20,13 @@ package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import javafx.scene.control.*;
import com.jfoenix.controls.JFXTreeView;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.util.Callback;
import org.jackhuang.hmcl.util.Holder;
import java.lang.reflect.Array;
import java.util.EnumMap;
@@ -34,7 +36,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author Glavo
*/
public final class NBTTreeView extends TreeView<Tag> {
public final class NBTTreeView extends JFXTreeView<Tag> {
public NBTTreeView(NBTTreeView.Item tree) {
this.setRoot(tree);
@@ -42,10 +44,9 @@ public final class NBTTreeView extends TreeView<Tag> {
}
private static Callback<TreeView<Tag>, TreeCell<Tag>> cellFactory() {
Holder<Object> lastCell = new Holder<>();
EnumMap<NBTTagType, Image> icons = new EnumMap<>(NBTTagType.class);
return view -> new TreeCell<Tag>() {
return view -> new TreeCell<>() {
private void setTagText(String text) {
String name = ((Item) getTreeItem()).getName();
@@ -66,11 +67,6 @@ public final class NBTTreeView extends TreeView<Tag> {
public void updateItem(Tag item, boolean empty) {
super.updateItem(item, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (this == lastCell.value && !isVisible())
return;
lastCell.value = this;
ImageView imageView = (ImageView) this.getGraphic();
if (imageView == null) {
imageView = new ImageView();
@@ -85,6 +81,8 @@ public final class NBTTreeView extends TreeView<Tag> {
NBTTagType tagType = NBTTagType.typeOf(item);
imageView.setImage(icons.computeIfAbsent(tagType, type -> new Image(type.getIconUrl())));
imageView.setFitHeight(16);
imageView.setFitWidth(16);
if (((Item) getTreeItem()).getText() != null) {
setText(((Item) getTreeItem()).getText());
@@ -54,7 +54,6 @@ import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.MDListCell;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jetbrains.annotations.Nullable;
@@ -180,8 +179,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
center.getStyleClass().add("large-spinner-pane");
center.loadingProperty().bind(skinnable.loadingProperty());
Holder<Object> lastCell = new Holder<>();
listView.setCellFactory(x -> new DatapackInfoListCell(listView, lastCell));
listView.setCellFactory(x -> new DatapackInfoListCell(listView));
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
this.listView.setItems(filteredList);
@@ -304,8 +302,8 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
final TwoLineListItem content = new TwoLineListItem();
BooleanProperty booleanProperty;
DatapackInfoListCell(JFXListView<DatapackInfoObject> listView, Holder<Object> lastCell) {
super(listView, lastCell);
DatapackInfoListCell(JFXListView<DatapackInfoObject> listView) {
super(listView);
HBox container = new HBox(8);
container.setPickOnBounds(false);
@@ -23,7 +23,6 @@ import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
@@ -93,9 +92,6 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
// FXThread
private boolean isSearching = false;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final ChangeListener<Boolean> holder;
ModListPageSkin(ModListPage skinnable) {
super(skinnable);
@@ -107,12 +103,6 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
root.getStyleClass().add("no-padding");
listView = new JFXListView<>();
this.holder = FXUtils.onWeakChange(skinnable.loadingProperty(), loading -> {
if (!loading) {
listView.scrollTo(0);
}
});
{
toolbarPane = new TransitionPane();
@@ -199,8 +189,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
center.getStyleClass().add("large-spinner-pane");
center.loadingProperty().bind(skinnable.loadingProperty());
Holder<Object> lastCell = new Holder<>();
listView.setCellFactory(x -> new ModInfoListCell(listView, lastCell));
listView.setCellFactory(x -> new ModInfoListCell(listView));
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Bindings.bindContent(listView.getItems(), skinnable.getItems());
skinnable.getItems().addListener((ListChangeListener<? super ModInfoObject>) c -> {
@@ -572,8 +561,8 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
Tooltip warningTooltip;
ModInfoListCell(JFXListView<ModInfoObject> listView, Holder<Object> lastCell) {
super(listView, lastCell);
ModInfoListCell(JFXListView<ModInfoObject> listView) {
super(listView);
this.getStyleClass().add("mod-info-list-cell");
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPageBase;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.ByteArrayInputStream;
@@ -156,8 +155,7 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
center.getStyleClass().add("large-spinner-pane");
center.loadingProperty().bind(control.loadingProperty());
Holder<Object> lastCell = new Holder<>();
listView.setCellFactory(x -> new ResourcepackListCell(listView, lastCell, control));
listView.setCellFactory(x -> new ResourcepackListCell(listView, control));
Bindings.bindContent(listView.getItems(), control.getItems());
center.setContent(listView);
@@ -210,8 +208,8 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
private final JFXButton btnDelete = new JFXButton();
private final ResourcepackListPage page;
public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, Holder<Object> lastCell, ResourcepackListPage page) {
super(listView, lastCell);
public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, ResourcepackListPage page) {
super(listView);
this.page = page;
@@ -252,12 +252,12 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
browseList.getContent().setAll(
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.WB_SUNNY, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
new IconedMenuItem(SVG.SCHEMA, i18n("folder.schematics"), () -> control.onBrowse("schematics"), 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.SCHEMA, i18n("folder.schematics"), () -> control.onBrowse("schematics"), 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.SETTINGS, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
new IconedMenuItem(SVG.SCRIPT, i18n("folder.logs"), () -> control.onBrowse("logs"), browsePopup)
);
@@ -1573,6 +1573,47 @@
-fx-fill: transparent;
}
/*******************************************************************************
* *
* JFX Tree View *
* *
******************************************************************************/
.tree-view {
-fx-padding: 5;
-fx-background-radius: 6;
-fx-background-color: -monet-surface-container-low;
-fx-border-radius: 6;
}
.tree-cell {
-fx-padding: 5;
-fx-background-radius: 6;
-fx-background-color: transparent;
-fx-text-fill: -monet-on-surface;
-fx-border-radius: 6;
}
.tree-cell:focused {
-fx-background-color: -monet-secondary-container;
}
.tree-cell .arrow {
-fx-background-color: -monet-on-surface;
}
.tree-cell .jfx-rippler {
-jfx-rippler-disabled: true;
}
.tree-view > .virtual-flow > .scroll-bar {
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
}
.tree-view > .virtual-flow > .scroll-bar .track {
-fx-fill: transparent;
}
/*******************************************************************************
* *
* JFX Decorator *
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 627 B

@@ -60,6 +60,7 @@ account.create.microsoft=Add a Microsoft Account
account.create.offline=Add an Offline Account
account.create.authlibInjector=Add an authlib-injector Account
account.email=Email
account.empty=No Accounts
account.failed=Failed to refresh account.
account.failed.character_deleted=The player has already been deleted.
account.failed.connect_authentication_server=Failed to connect to the authentication server, your network connection may be down.
@@ -1378,7 +1379,7 @@ settings.launcher.font.anti_aliasing.auto=Auto
settings.launcher.font.anti_aliasing.gray=Grayscale
settings.launcher.font.anti_aliasing.lcd=Sub-pixel
settings.launcher.general=General
settings.launcher.language=Language (Applies After Restart)
settings.launcher.language=Language
settings.launcher.launcher_log.export=Export Launcher Logs
settings.launcher.launcher_log.export.failed=Failed to export logs.
settings.launcher.launcher_log.export.success=Logs have been exported to "%s".
@@ -1397,7 +1398,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Username
settings.launcher.theme=Theme Color
settings.launcher.title_transparent=Transparent Titlebar
settings.launcher.turn_off_animations=Disable Animation (Applies After Restart)
settings.launcher.turn_off_animations=Disable Animation
settings.launcher.version_list_source=Version List
settings.launcher.background.settings.opacity=Opacity
@@ -1367,7 +1367,7 @@ settings.launcher.font.anti_aliasing.auto=تلقائي
settings.launcher.font.anti_aliasing.gray=تدرج رمادي
settings.launcher.font.anti_aliasing.lcd=Sub-pixel
settings.launcher.general=عام
settings.launcher.language=اللغة (يطبق بعد إعادة التشغيل)
settings.launcher.language=اللغة
settings.launcher.launcher_log.export=تصدير سجلات المشغل
settings.launcher.launcher_log.export.failed=فشل تصدير السجلات.
settings.launcher.launcher_log.export.success=تم تصدير السجلات إلى "%s".
@@ -1386,7 +1386,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=اسم المستخدم
settings.launcher.theme=السمة
settings.launcher.title_transparent=شريط العنوان الشفاف
settings.launcher.turn_off_animations=تعطيل الرسوم المتحركة (يطبق بعد إعادة التشغيل)
settings.launcher.turn_off_animations=تعطيل الرسوم المتحركة
settings.launcher.version_list_source=قائمة الإصدارات
settings.launcher.background.settings.opacity=الشفافية
@@ -1387,7 +1387,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Nombre de usuario
settings.launcher.theme=Tema
settings.launcher.title_transparent=Barra de título transparente
settings.launcher.turn_off_animations=Desactivar animación (Se aplica después de reiniciar)
settings.launcher.turn_off_animations=Desactivar animación
settings.launcher.version_list_source=Lista de versiones
settings.launcher.background.settings.opacity=Opacidad
@@ -1152,7 +1152,7 @@ settings.launcher.download_source=引源
settings.launcher.download_source.auto=自擇所引源
settings.launcher.enable_game_list=見版列於主頁
settings.launcher.font=書體
settings.launcher.font.anti_aliasing=抗鋸 (復啟而效)
settings.launcher.font.anti_aliasing=抗鋸
settings.launcher.font.anti_aliasing.auto=自調
settings.launcher.font.anti_aliasing.gray=灰階
settings.launcher.font.anti_aliasing.lcd=子像素
@@ -1176,7 +1176,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=戶簿
settings.launcher.theme=主題
settings.launcher.title_transparent=通透題欄
settings.launcher.turn_off_animations=廢動效 (復啟而效)
settings.launcher.turn_off_animations=廢動效
settings.launcher.version_list_source=版列供者
settings.launcher.background.settings.opacity=陰翳
@@ -1360,7 +1360,7 @@ settings.launcher.font.anti_aliasing.auto=Автоматический
settings.launcher.font.anti_aliasing.gray=Оттенки серого
settings.launcher.font.anti_aliasing.lcd=Субпиксель
settings.launcher.general=Общие
settings.launcher.language=Язык (Применится после перезапуска)
settings.launcher.language=Язык
settings.launcher.launcher_log.export=Экспорт логов лаунчера
settings.launcher.launcher_log.export.failed=Не удалось экспортировать логи
settings.launcher.launcher_log.export.success=Логи экспортированы в %s
@@ -1379,7 +1379,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Имя пользователя
settings.launcher.theme=Тема
settings.launcher.title_transparent=Прозрачная строка заголовка
settings.launcher.turn_off_animations=Отключить анимацию (Применится после перезапуска)
settings.launcher.turn_off_animations=Отключить анимацию
settings.launcher.version_list_source=Список версий
settings.launcher.background.settings.opacity=Непрозрачность
@@ -1302,7 +1302,7 @@ settings.launcher.font.anti_aliasing.auto=Авто
settings.launcher.font.anti_aliasing.gray=Відтінки сірого
settings.launcher.font.anti_aliasing.lcd=Субпіксельне
settings.launcher.general=Загальні
settings.launcher.language=Мова (Застосовується після перезавантаження)
settings.launcher.language=Мова
settings.launcher.launcher_log.export=Експортувати журнали лаунчера
settings.launcher.launcher_log.export.failed=Не вдалося експортувати журнали.
settings.launcher.launcher_log.export.success=Журнали було експортовано до "%s".
@@ -1321,7 +1321,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Ім'я користувача
settings.launcher.theme=Тема
settings.launcher.title_transparent=Прозорий заголовок
settings.launcher.turn_off_animations=Вимкнути анімацію (Застосовується після перезавантаження)
settings.launcher.turn_off_animations=Вимкнути анімацію
settings.launcher.version_list_source=Список версій
settings.launcher.background.settings.opacity=Непрозорість
@@ -59,6 +59,7 @@ account.create.microsoft=新增 Microsoft 帳戶
account.create.offline=新增離線模式帳戶
account.create.authlibInjector=新增 authlib-injector 帳戶
account.email=電子信箱
account.empty=沒有帳戶
account.failed=帳戶重新整理失敗
account.failed.character_deleted=已刪除此角色
account.failed.connect_authentication_server=無法連線至認證伺服器。可能是網路問題,請檢查裝置能否正常上網或使用代理服務。
@@ -1161,12 +1162,12 @@ settings.launcher.download_source=下載來源
settings.launcher.download_source.auto=自動選取下載來源
settings.launcher.enable_game_list=在首頁內顯示遊戲清單
settings.launcher.font=字體
settings.launcher.font.anti_aliasing=反鋸齒 (重啟後生效)
settings.launcher.font.anti_aliasing=反鋸齒
settings.launcher.font.anti_aliasing.auto=自動
settings.launcher.font.anti_aliasing.gray=灰階
settings.launcher.font.anti_aliasing.lcd=子像素
settings.launcher.general=一般
settings.launcher.language=語言 (重啟後生效)
settings.launcher.language=語言
settings.launcher.launcher_log.export=匯出啟動器日誌
settings.launcher.launcher_log.export.failed=無法匯出日誌。
settings.launcher.launcher_log.export.success=日誌已儲存到「%s」。
@@ -1185,7 +1186,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=帳戶
settings.launcher.theme=主題色
settings.launcher.title_transparent=標題欄透明
settings.launcher.turn_off_animations=關閉動畫 (重啟後生效)
settings.launcher.turn_off_animations=關閉動畫
settings.launcher.version_list_source=版本清單來源
settings.launcher.background.settings.opacity=不透明度
@@ -59,6 +59,7 @@ account.create.microsoft=添加微软账户
account.create.offline=添加离线模式账户
account.create.authlibInjector=添加外置登录账户 (authlib-injector)
account.email=邮箱
account.empty=没有账户
account.failed=账户刷新失败
account.failed.character_deleted=此角色已被删除
account.failed.connect_authentication_server=无法连接认证服务器。可能是网络问题,请检查设备能否正常上网或使用代理服务。\n你可以点击右上角帮助按钮进行求助。
@@ -1171,12 +1172,12 @@ settings.launcher.download_source=下载源
settings.launcher.download_source.auto=自动选择下载源
settings.launcher.enable_game_list=在主页内显示版本列表
settings.launcher.font=字体
settings.launcher.font.anti_aliasing=抗锯齿 (重启后生效)
settings.launcher.font.anti_aliasing=抗锯齿
settings.launcher.font.anti_aliasing.auto=自动
settings.launcher.font.anti_aliasing.gray=灰度
settings.launcher.font.anti_aliasing.lcd=子像素
settings.launcher.general=通用
settings.launcher.language=语言 (重启后生效)
settings.launcher.language=语言
settings.launcher.launcher_log.export=导出启动器日志
settings.launcher.launcher_log.export.failed=无法导出日志
settings.launcher.launcher_log.export.success=日志已保存到“%s”
@@ -1195,7 +1196,7 @@ settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=账户
settings.launcher.theme=主题色
settings.launcher.title_transparent=标题栏透明
settings.launcher.turn_off_animations=关闭动画 (重启后生效)
settings.launcher.turn_off_animations=关闭动画
settings.launcher.version_list_source=版本列表源
settings.launcher.background.settings.opacity=不透明度
@@ -1207,7 +1208,7 @@ settings.memory.allocate.manual.exceeded=游戏分配 %1$.1f GiB (设备仅 %3$.
settings.memory.auto_allocate=自动分配内存
settings.memory.lower_bound=最低内存分配
settings.memory.unit.mib=MiB
settings.memory.used_per_total=设备中已使用 %1$.1f GiB / 设备总内存 %2$.1f GiB
settings.memory.used_per_total=已使用 %1$.1f GiB / 总内存 %2$.1f GiB
settings.physical_memory=物理内存大小
settings.show_log=查看日志
settings.enable_debug_log_output=输出调试日志
@@ -177,6 +177,7 @@ public final class FileUtils {
if (!Character.isValidCodePoint(codePoint)
|| Character.isISOControl(codePoint)
|| codePoint == '/' || codePoint == '\0'
|| codePoint == ':'
// Unicode replacement character
|| codePoint == 0xfffd
// Not Unicode character
@@ -185,7 +186,7 @@ public final class FileUtils {
// https://learn.microsoft.com/windows/win32/fileio/naming-a-file
if (os == OperatingSystem.WINDOWS &&
(ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '\\' || ch == '|' || ch == '?' || ch == '*')) {
(ch == '<' || ch == '>' || ch == '"' || ch == '\\' || ch == '|' || ch == '?' || ch == '*')) {
return false;
}
}
@@ -52,6 +52,7 @@ public class FileUtilsTest {
assertFalse(FileUtils.isNameValid(os, "a\uD83Db"));
assertFalse(FileUtils.isNameValid(os, "a\uDE00b"));
assertFalse(FileUtils.isNameValid(os, "a\uDE00\uD83Db"));
assertFalse(FileUtils.isNameValid(os, "f:oo"));
// Platform-specific tests
boolean isWindows = os == OperatingSystem.WINDOWS;
@@ -62,7 +63,6 @@ public class FileUtilsTest {
assertEquals(isNotWindows, FileUtils.isNameValid(os, "foo "));
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f<oo"));
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f>oo"));
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f:oo"));
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f?oo"));
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f*oo"));
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f\\oo"));
+1 -1
View File
@@ -15,4 +15,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
versionRoot=3.9
versionRoot=3.10
+3 -3
View File
@@ -119,13 +119,13 @@ Legend:
<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->
| | Windows | Linux | macOS | FreeBSD |
|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.10) |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.11) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.10) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.11) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.5) | / | / |
| LoongArch64 (New World) | / | 👌 (Minecraft 1.6~1.21.10) | / | / |
| LoongArch64 (New World) | / | 👌 (Minecraft 1.6~1.21.11) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
+3 -3
View File
@@ -126,13 +126,13 @@
<!-- #PROPERTY REPLACE="\\(use Rosetta 2\\)" "(使用 Rosetta 2)" -->
| | Windows | Linux | macOS | FreeBSD |
|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.10) |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.11) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.10) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.11) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.5) | / | / |
| LoongArch64 (新世界) | / | 👌 (Minecraft 1.6~1.21.10) | / | / |
| LoongArch64 (新世界) | / | 👌 (Minecraft 1.6~1.21.11) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
+3 -3
View File
@@ -126,13 +126,13 @@
<!-- #PROPERTY REPLACE="\\(use Rosetta 2\\)" "(使用 Rosetta 2)" -->
| | Windows | Linux | macOS | FreeBSD |
|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.10) |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.11) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.10) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.11) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.5) | / | / |
| LoongArch64 (新世界) | / | 👌 (Minecraft 1.6~1.21.10) | / | / |
| LoongArch64 (新世界) | / | 👌 (Minecraft 1.6~1.21.11) | / | / |
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |