Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
078c2ad98b | ||
|
|
830d9a05e7 | ||
|
|
7dfeebff39 | ||
|
|
05a414c93f | ||
|
|
16a432125b | ||
|
|
573e25ecdf | ||
|
|
821cc6d768 | ||
|
|
c13b5e15d4 | ||
|
|
3eea76784d | ||
|
|
22c66b2790 | ||
|
|
fcabe9ca36 | ||
|
|
2af3c8f714 | ||
|
|
253440844e | ||
|
|
cc87f6cdd1 | ||
|
|
a77ef95ce4 | ||
|
|
7fb7b00836 | ||
|
|
3252de2f3f | ||
|
|
9b0b975490 | ||
|
|
449acbc7dc | ||
|
|
1c82e4e1e1 | ||
|
|
77cdc2108f | ||
|
|
cc092f535b | ||
|
|
13930f8dcc | ||
|
|
ebad1f67ce |
No files matched your search
@@ -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 *
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 768 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 441 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 677 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 656 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 689 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 236 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 193 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 248 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 253 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 602 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1003 B |
|
Before Width: | Height: | Size: 980 B After Width: | Height: | Size: 374 B |
|
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"));
|
||||
|
||||
@@ -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
|
||||
@@ -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 | / | ❔ | / | / |
|
||||
|
||||
@@ -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 | / | ❔ | / | / |
|
||||
|
||||
@@ -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 | / | ❔ | / | / |
|
||||
|
||||