Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9194057a | ||
|
|
04cf5eca59 | ||
|
|
86d65e96ec | ||
|
|
1262a760b8 | ||
|
|
acd41e5885 | ||
|
|
f22568d3e8 | ||
|
|
11d182f907 | ||
|
|
e25e58cfad | ||
|
|
2e4cc76d6a | ||
|
|
02e9d807cd | ||
|
|
8923be50ec | ||
|
|
a07c0d9746 | ||
|
|
e56c4643de | ||
|
|
fb942d1099 | ||
|
|
3842b6df32 | ||
|
|
fadd9580a2 | ||
|
|
110a58b3cd | ||
|
|
5a25ba6663 | ||
|
|
0013a52a5c | ||
|
|
05d37e3911 | ||
|
|
bc55da0720 | ||
|
|
57a582e2cb | ||
|
|
7c0faf2143 | ||
|
|
fe0515ed28 | ||
|
|
0c657ef382 | ||
|
|
4ce6588f0c | ||
|
|
d083ff9048 | ||
|
|
54f9902849 | ||
|
|
2e572d2b02 | ||
|
|
ac9f337d4e | ||
|
|
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);
|
||||
control.setX(control.getX() + (hAlign == PopupHPosition.RIGHT ? -container.getWidth() + offsetX : offsetX));
|
||||
control.setY(control.getY() + (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);
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
Files.copy(fromJson, toJson);
|
||||
|
||||
JsonUtils.writeToJsonFile(toJson, fromVersion.setId(dstId));
|
||||
JsonUtils.writeToJsonFile(toJson, fromVersion.setId(dstId).setJar(dstId));
|
||||
|
||||
VersionSetting oldVersionSetting = getVersionSetting(srcId).clone();
|
||||
GameDirectoryType originalGameDirType = oldVersionSetting.getGameDirType();
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class HMCLModpackProvider implements ModpackProvider {
|
||||
|
||||
private final static class HMCLModpack extends Modpack {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,21 +17,19 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -43,7 +41,9 @@ public final class LogExporter {
|
||||
private LogExporter() {
|
||||
}
|
||||
|
||||
public static CompletableFuture<Void> exportLogs(Path zipFile, DefaultGameRepository gameRepository, String versionId, String logs, String launchScript) {
|
||||
public static CompletableFuture<Void> exportLogs(
|
||||
Path zipFile, DefaultGameRepository gameRepository, String versionId, String logs, String launchScript,
|
||||
PathMatcher logMatcher) {
|
||||
Path runDirectory = gameRepository.getRunDirectory(versionId);
|
||||
Path baseDirectory = gameRepository.getBaseDirectory();
|
||||
List<String> versions = new ArrayList<>();
|
||||
@@ -65,10 +65,10 @@ public final class LogExporter {
|
||||
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Zipper zipper = new Zipper(zipFile)) {
|
||||
processLogs(runDirectory.resolve("liteconfig"), "*.log", "liteconfig", zipper);
|
||||
processLogs(runDirectory.resolve("logs"), "*.log", "logs", zipper);
|
||||
processLogs(runDirectory, "*.log", "runDirectory", zipper);
|
||||
processLogs(runDirectory.resolve("crash-reports"), "*.txt", "crash-reports", zipper);
|
||||
processLogs(runDirectory.resolve("liteconfig"), "*.log", "liteconfig", zipper, logMatcher);
|
||||
processLogs(runDirectory.resolve("logs"), "*.log", "logs", zipper, logMatcher);
|
||||
processLogs(runDirectory, "*.log", "runDirectory", zipper, logMatcher);
|
||||
processLogs(runDirectory.resolve("crash-reports"), "*.txt", "crash-reports", zipper, logMatcher);
|
||||
|
||||
zipper.putTextFile(LOG.getLogs(), "hmcl.log");
|
||||
zipper.putTextFile(logs, "minecraft.log");
|
||||
@@ -86,14 +86,11 @@ public final class LogExporter {
|
||||
});
|
||||
}
|
||||
|
||||
private static void processLogs(Path directory, String fileExtension, String logDirectory, Zipper zipper) {
|
||||
private static void processLogs(Path directory, String fileExtension, String logDirectory, Zipper zipper, PathMatcher logMatcher) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, fileExtension)) {
|
||||
long processStartTime = ManagementFactory.getRuntimeMXBean().getStartTime();
|
||||
|
||||
for (Path file : stream) {
|
||||
if (Files.isRegularFile(file)) {
|
||||
FileTime time = Files.readAttributes(file, BasicFileAttributes.class).lastModifiedTime();
|
||||
if (time.toMillis() >= processStartTime) {
|
||||
if (logMatcher == null || logMatcher.matches(file)) {
|
||||
try (BufferedReader reader = IOUtils.newBufferedReaderMaybeNativeEncoding(file)) {
|
||||
zipper.putLines(reader.lines().map(Logger::filterForbiddenToken), file.getFileName().toString());
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -188,7 +188,7 @@ public final class ModpackHelper {
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<?> getInstallTask(Profile profile, Path zipFile, String name, Modpack modpack) {
|
||||
public static Task<?> getInstallTask(Profile profile, Path zipFile, String name, Modpack modpack, String iconUrl) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
ExceptionalRunnable<?> success = () -> {
|
||||
@@ -208,17 +208,17 @@ public final class ModpackHelper {
|
||||
};
|
||||
|
||||
if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.withStagesHint(List.of("hmcl.modpack", "hmcl.modpack.download"));
|
||||
else if (modpack.getManifest() instanceof McbbsModpackManifest)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name))
|
||||
.withStagesHint(List.of("hmcl.modpack", "hmcl.modpack.download"));
|
||||
else
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.javafx(), success, failure)
|
||||
.withStagesHint(List.of("hmcl.modpack", "hmcl.modpack.download"));
|
||||
}
|
||||
|
||||
@@ -152,6 +152,12 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
@Override
|
||||
public void openBrowser(String url) throws IOException {
|
||||
lastlyOpenedURL = url;
|
||||
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
FXUtils.openLink(url);
|
||||
|
||||
onOpenBrowser.fireEvent(new OpenBrowserEvent(this, url));
|
||||
|
||||
@@ -465,7 +465,7 @@ public final class Accounts {
|
||||
} else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) {
|
||||
return i18n("account.methods.microsoft.error.no_character");
|
||||
} else if (exception instanceof MicrosoftService.NoXuiException) {
|
||||
return i18n("account.methods.microsoft.error.add_family_probably");
|
||||
return i18n("account.methods.microsoft.error.add_family");
|
||||
} else if (exception instanceof OAuthServer.MicrosoftAuthenticationNotSupportedException) {
|
||||
return i18n("account.methods.microsoft.snapshot");
|
||||
} else if (exception instanceof OAuthAccount.WrongAccountException) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
@@ -72,9 +73,10 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class Controllers {
|
||||
public static final String JAVA_VERSION_TIP = "javaVersion";
|
||||
@@ -505,8 +507,8 @@ public final class Controllers {
|
||||
return prompt(title, onResult, "");
|
||||
}
|
||||
|
||||
public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult, String initialValue) {
|
||||
InputDialogPane pane = new InputDialogPane(title, initialValue, onResult);
|
||||
public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult, String initialValue, ValidatorBase... validators) {
|
||||
InputDialogPane pane = new InputDialogPane(title, initialValue, onResult, validators);
|
||||
dialog(pane);
|
||||
return pane.getCompletableFuture();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.Decorator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class DialogUtils {
|
||||
private DialogUtils() {
|
||||
}
|
||||
|
||||
public static final String PROPERTY_DIALOG_INSTANCE = DialogUtils.class.getName() + ".dialog.instance";
|
||||
public static final String PROPERTY_DIALOG_PANE_INSTANCE = DialogUtils.class.getName() + ".dialog.pane.instance";
|
||||
public static final String PROPERTY_DIALOG_CLOSE_HANDLER = DialogUtils.class.getName() + ".dialog.closeListener";
|
||||
|
||||
public static final String PROPERTY_PARENT_PANE_REF = DialogUtils.class.getName() + ".dialog.parentPaneRef";
|
||||
public static final String PROPERTY_PARENT_DIALOG_REF = DialogUtils.class.getName() + ".dialog.parentDialogRef";
|
||||
|
||||
public static void show(Decorator decorator, Node content) {
|
||||
if (decorator.getDrawerWrapper() == null) {
|
||||
Platform.runLater(() -> show(decorator, content));
|
||||
return;
|
||||
}
|
||||
|
||||
show(decorator.getDrawerWrapper(), content, (dialog) -> {
|
||||
JFXDialogPane pane = (JFXDialogPane) dialog.getContent();
|
||||
decorator.capableDraggingWindow(dialog);
|
||||
decorator.forbidDraggingWindow(pane);
|
||||
dialog.setDialogContainer(decorator.getDrawerWrapper());
|
||||
});
|
||||
}
|
||||
|
||||
public static void show(StackPane container, Node content) {
|
||||
show(container, content, null);
|
||||
}
|
||||
|
||||
public static void show(StackPane container, Node content, @Nullable Consumer<JFXDialog> onDialogCreated) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
JFXDialog dialog = (JFXDialog) container.getProperties().get(PROPERTY_DIALOG_INSTANCE);
|
||||
JFXDialogPane dialogPane = (JFXDialogPane) container.getProperties().get(PROPERTY_DIALOG_PANE_INSTANCE);
|
||||
|
||||
if (dialog == null) {
|
||||
dialog = new JFXDialog(AnimationUtils.isAnimationEnabled()
|
||||
? JFXDialog.DialogTransition.CENTER
|
||||
: JFXDialog.DialogTransition.NONE);
|
||||
dialogPane = new JFXDialogPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
dialog.setDialogContainer(container);
|
||||
dialog.setOverlayClose(false);
|
||||
|
||||
container.getProperties().put(PROPERTY_DIALOG_INSTANCE, dialog);
|
||||
container.getProperties().put(PROPERTY_DIALOG_PANE_INSTANCE, dialogPane);
|
||||
|
||||
if (onDialogCreated != null) {
|
||||
onDialogCreated.accept(dialog);
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
content.getProperties().put(PROPERTY_PARENT_PANE_REF, dialogPane);
|
||||
content.getProperties().put(PROPERTY_PARENT_DIALOG_REF, dialog);
|
||||
|
||||
dialogPane.push(content);
|
||||
|
||||
EventHandler<DialogCloseEvent> handler = event -> close(content);
|
||||
content.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
content.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
|
||||
handleDialogShown(dialog, content);
|
||||
}
|
||||
|
||||
private static void handleDialogShown(JFXDialog dialog, Node node) {
|
||||
if (dialog.isVisible()) {
|
||||
dialog.requestFocus();
|
||||
if (node instanceof DialogAware dialogAware)
|
||||
dialogAware.onDialogShown();
|
||||
} else {
|
||||
dialog.visibleProperty().addListener(new ChangeListener<>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
dialog.requestFocus();
|
||||
if (node instanceof DialogAware dialogAware)
|
||||
dialogAware.onDialogShown();
|
||||
observable.removeListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void close(Node content) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(content.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> content.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
JFXDialogPane pane = (JFXDialogPane) content.getProperties().get(PROPERTY_PARENT_PANE_REF);
|
||||
JFXDialog dialog = (JFXDialog) content.getProperties().get(PROPERTY_PARENT_DIALOG_REF);
|
||||
|
||||
if (dialog != null && pane != null) {
|
||||
if (pane.size() == 1 && pane.peek().orElse(null) == content) {
|
||||
dialog.setOnDialogClosed(e -> pane.pop(content));
|
||||
dialog.close();
|
||||
|
||||
StackPane container = dialog.getDialogContainer();
|
||||
if (container != null) {
|
||||
container.getProperties().remove(PROPERTY_DIALOG_INSTANCE);
|
||||
container.getProperties().remove(PROPERTY_DIALOG_PANE_INSTANCE);
|
||||
container.getProperties().remove(PROPERTY_PARENT_DIALOG_REF);
|
||||
container.getProperties().remove(PROPERTY_PARENT_PANE_REF);
|
||||
}
|
||||
} else {
|
||||
pane.pop(content);
|
||||
}
|
||||
|
||||
if (content instanceof DialogAware dialogAware) {
|
||||
dialogAware.onDialogClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
@@ -50,9 +51,12 @@ import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
@@ -81,6 +85,7 @@ public class GameCrashWindow extends Stage {
|
||||
private final ProcessListener.ExitType exitType;
|
||||
private final LaunchOptions launchOptions;
|
||||
private final View view;
|
||||
private final StackPane stackPane;
|
||||
|
||||
private final List<Log> logs;
|
||||
|
||||
@@ -93,7 +98,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());
|
||||
|
||||
@@ -103,9 +108,10 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
this.view = new View();
|
||||
|
||||
this.stackPane = new StackPane(view);
|
||||
this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
||||
|
||||
setScene(new Scene(view, 800, 480));
|
||||
setScene(new Scene(stackPane, 800, 480));
|
||||
StyleSheets.init(getScene());
|
||||
setTitle(i18n("game.crash.title"));
|
||||
FXUtils.setIcon(this);
|
||||
@@ -269,22 +275,41 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
CompletableFuture.supplyAsync(() ->
|
||||
logs.stream().map(Log::getLog).collect(Collectors.joining("\n")))
|
||||
.thenComposeAsync(logs ->
|
||||
LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs, new CommandBuilder().addAll(managedProcess.getCommands()).toString()))
|
||||
.handleAsync((result, exception) -> {
|
||||
Alert alert;
|
||||
.thenComposeAsync(logs -> {
|
||||
long processStartTime = managedProcess.getProcess().info()
|
||||
.startInstant()
|
||||
.map(Instant::toEpochMilli).orElseGet(() -> {
|
||||
try {
|
||||
return ManagementFactory.getRuntimeMXBean().getStartTime();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get process start time", e);
|
||||
return 0L;
|
||||
}
|
||||
});
|
||||
|
||||
return LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs,
|
||||
new CommandBuilder().addAll(managedProcess.getCommands()).toString(),
|
||||
path -> {
|
||||
try {
|
||||
FileTime lastModifiedTime = Files.getLastModifiedTime(path);
|
||||
return lastModifiedTime.toMillis() >= processStartTime;
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to read file attributes", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
})
|
||||
.handleAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
FXUtils.showFileInExplorer(logFile);
|
||||
alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
|
||||
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.success", logFile), i18n("message.success"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
} else {
|
||||
LOG.warning("Failed to export game crash info", exception);
|
||||
alert = new Alert(Alert.AlertType.WARNING, i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception));
|
||||
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception), i18n("message.error"), MessageDialogPane.MessageType.ERROR).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
}
|
||||
|
||||
alert.setTitle(i18n("settings.launcher.launcher_log.export"));
|
||||
alert.showAndWait();
|
||||
|
||||
return null;
|
||||
}, Schedulers.javafx());
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
@@ -44,12 +43,10 @@ import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
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;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Dwmapi;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
||||
@@ -66,8 +63,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -200,10 +197,12 @@ public final class LogWindow extends Stage {
|
||||
private final StringProperty[] buttonText = new StringProperty[LEVELS.length];
|
||||
private final BooleanProperty[] showLevel = new BooleanProperty[LEVELS.length];
|
||||
private final JFXComboBox<Integer> cboLines = new JFXComboBox<>();
|
||||
private final StackPane stackPane = new StackPane();
|
||||
|
||||
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++) {
|
||||
@@ -241,9 +240,8 @@ public final class LogWindow extends Stage {
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
|
||||
alert.setTitle(i18n("settings.launcher.launcher_log.export"));
|
||||
alert.showAndWait();
|
||||
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.success", logFile), i18n("message.success"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
});
|
||||
|
||||
FXUtils.showFileInExplorer(logFile);
|
||||
@@ -267,9 +265,8 @@ public final class LogWindow extends Stage {
|
||||
LOG.warning("Failed to create minecraft jstack dump", e);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump"));
|
||||
alert.setTitle(i18n("message.error"));
|
||||
alert.showAndWait();
|
||||
var dialog = new MessageDialogPane.Builder(i18n("logwindow.export_dump") + "\n" + StringUtils.getStackTrace(e), i18n("message.error"), MessageDialogPane.MessageType.ERROR).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,7 +297,9 @@ public final class LogWindow extends Stage {
|
||||
|
||||
VBox vbox = new VBox(3);
|
||||
vbox.setPadding(new Insets(3, 0, 3, 0));
|
||||
getChildren().setAll(vbox);
|
||||
getSkinnable().stackPane.getChildren().setAll(vbox);
|
||||
getChildren().setAll(getSkinnable().stackPane);
|
||||
|
||||
|
||||
{
|
||||
BorderPane borderPane = new BorderPane();
|
||||
@@ -343,8 +342,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 +386,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();
|
||||
|
||||
@@ -25,11 +25,10 @@ import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
@@ -88,6 +87,10 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
|
||||
addServerPane.setBody(txtServerUrl);
|
||||
addServerPane.setActions(lblCreationWarning, actions);
|
||||
|
||||
txtServerUrl.getValidators().addAll(new RequiredValidator(), new URLValidator());
|
||||
FXUtils.setValidateWhileTextChanged(txtServerUrl, true);
|
||||
btnAddNext.disableProperty().bind(txtServerUrl.activeValidatorProperty().isNotNull());
|
||||
}
|
||||
|
||||
confirmServerPane = new JFXDialogLayout();
|
||||
@@ -149,7 +152,6 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
this.setContent(addServerPane, ContainerAnimations.NONE);
|
||||
|
||||
lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty());
|
||||
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
|
||||
nextPane.hideSpinner();
|
||||
|
||||
onEscPressed(this, this::onAddCancel);
|
||||
|
||||
@@ -35,8 +35,6 @@ import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
@@ -64,11 +62,7 @@ import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -286,72 +280,95 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
btnAccept.disableProperty().unbind();
|
||||
detailsContainer.getChildren().remove(detailsPane);
|
||||
lblErrorMessage.setText("");
|
||||
lblErrorMessage.setVisible(true);
|
||||
}
|
||||
|
||||
if (factory == Accounts.FACTORY_MICROSOFT) {
|
||||
VBox vbox = new VBox(8);
|
||||
if (!Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
|
||||
if (deviceCode != null) {
|
||||
FXUtils.copyText(deviceCode.getUserCode());
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getUserCode(), deviceCode.getVerificationUri()));
|
||||
} else {
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
|
||||
}
|
||||
});
|
||||
FXUtils.onClicked(hintPane, () -> {
|
||||
if (deviceCode.get() != null) {
|
||||
FXUtils.copyText(deviceCode.get().getUserCode());
|
||||
}
|
||||
});
|
||||
detailsPane = vbox;
|
||||
|
||||
holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value -> {
|
||||
runInFX(() -> deviceCode.set(value));
|
||||
}));
|
||||
FlowPane box = new FlowPane();
|
||||
box.setHgap(8);
|
||||
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
|
||||
birthLink.setExternalLink("https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a");
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
|
||||
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
|
||||
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
|
||||
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
|
||||
JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings"));
|
||||
createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile");
|
||||
JFXHyperlink bannedQueryLink = new JFXHyperlink(i18n("account.methods.ban_query"));
|
||||
bannedQueryLink.setExternalLink("https://enforcement.xbox.com/enforcement/showenforcementhistory");
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink, bannedQueryLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
if (!IntegrityChecker.isOfficial()) {
|
||||
HintPane unofficialHint = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
unofficialHint.setText(i18n("unofficial.hint"));
|
||||
vbox.getChildren().add(unofficialHint);
|
||||
}
|
||||
|
||||
vbox.getChildren().addAll(hintPane, box);
|
||||
|
||||
btnAccept.setDisable(false);
|
||||
} else {
|
||||
if (Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.snapshot"));
|
||||
|
||||
JFXHyperlink officialWebsite = new JFXHyperlink(i18n("account.methods.microsoft.snapshot.website"));
|
||||
officialWebsite.setExternalLink(Metadata.PUBLISH_URL);
|
||||
|
||||
vbox.getChildren().setAll(hintPane, officialWebsite);
|
||||
btnAccept.setDisable(true);
|
||||
vbox.getChildren().add(hintPane);
|
||||
return;
|
||||
}
|
||||
|
||||
detailsPane = vbox;
|
||||
if (!IntegrityChecker.isOfficial()) {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
hintPane.setSegment(i18n("unofficial.hint"));
|
||||
vbox.getChildren().add(hintPane);
|
||||
}
|
||||
|
||||
VBox codeBox = new VBox(8);
|
||||
Label hint = new Label(i18n("account.methods.microsoft.code"));
|
||||
Label code = new Label();
|
||||
code.setMouseTransparent(true);
|
||||
code.setStyle("-fx-font-size: 24");
|
||||
codeBox.getChildren().addAll(hint, code);
|
||||
codeBox.setAlignment(Pos.CENTER);
|
||||
vbox.getChildren().add(codeBox);
|
||||
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
HintPane errHintPane = new HintPane(MessageDialogPane.MessageType.ERROR);
|
||||
errHintPane.setVisible(false);
|
||||
errHintPane.setManaged(false);
|
||||
|
||||
codeBox.setVisible(false);
|
||||
codeBox.setManaged(false);
|
||||
|
||||
FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
|
||||
if (deviceCode != null) {
|
||||
FXUtils.copyText(deviceCode.getUserCode());
|
||||
code.setText(deviceCode.getUserCode());
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getVerificationUri()));
|
||||
codeBox.setVisible(true);
|
||||
codeBox.setManaged(true);
|
||||
} else {
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
|
||||
codeBox.setVisible(false);
|
||||
codeBox.setManaged(false);
|
||||
}
|
||||
});
|
||||
|
||||
lblErrorMessage.setVisible(false);
|
||||
lblErrorMessage.textProperty().addListener((obs, oldVal, newVal) -> {
|
||||
boolean hasError = !newVal.isEmpty();
|
||||
errHintPane.setSegment(newVal);
|
||||
errHintPane.setVisible(hasError);
|
||||
errHintPane.setManaged(hasError);
|
||||
hintPane.setVisible(!hasError);
|
||||
hintPane.setManaged(!hasError);
|
||||
codeBox.setVisible(!hasError && deviceCode.get() != null);
|
||||
codeBox.setManaged(!hasError && deviceCode.get() != null);
|
||||
});
|
||||
|
||||
FXUtils.onClicked(codeBox, () -> {
|
||||
if (deviceCode.get() != null) FXUtils.copyText(deviceCode.get().getUserCode());
|
||||
});
|
||||
|
||||
holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value ->
|
||||
runInFX(() -> deviceCode.set(value))
|
||||
));
|
||||
|
||||
HBox linkBox = new HBox();
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
|
||||
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
|
||||
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
|
||||
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
|
||||
linkBox.getChildren().setAll(profileLink, purchaseLink, deauthorizeLink, forgotpasswordLink);
|
||||
|
||||
vbox.getChildren().addAll(hintPane, errHintPane, linkBox);
|
||||
btnAccept.setDisable(false);
|
||||
} else {
|
||||
detailsPane = new AccountDetailsInputPane(factory, btnAccept::fire);
|
||||
btnAccept.disableProperty().bind(((AccountDetailsInputPane) detailsPane).validProperty().not());
|
||||
}
|
||||
|
||||
detailsContainer.getChildren().add(detailsPane);
|
||||
}
|
||||
|
||||
@@ -460,6 +477,9 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
onChangeAndOperate(cboServers.valueProperty(), server -> {
|
||||
this.server = server;
|
||||
linksContainer.getChildren().setAll(createHyperlinks(server));
|
||||
|
||||
if (txtUsername != null)
|
||||
txtUsername.validate();
|
||||
});
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
@@ -590,6 +610,9 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
if ((factory instanceof AuthlibInjectorAccountFactory) && this.server != null) {
|
||||
return !server.isNonEmailLogin();
|
||||
}
|
||||
if (factory instanceof BoundAuthlibInjectorAccountFactory bound) {
|
||||
return !bound.getServer().isNonEmailLogin();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,11 +66,11 @@ public class OAuthAccountLoginDialog extends DialogPane {
|
||||
|
||||
HBox box = new HBox(8);
|
||||
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
|
||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
birthLink.setExternalLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a");
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
||||
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
|
||||
@@ -159,7 +159,9 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
|
||||
FXUtils.onChangeAndOperate(skinItem.selectedDataProperty(), selectedData -> {
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setPadding(new Insets(0, 0, 0, 10));
|
||||
// Increase bottom padding to prevent the prompt from overlapping with the dialog action area
|
||||
|
||||
gridPane.setPadding(new Insets(0, 0, 45, 10));
|
||||
gridPane.setHgap(16);
|
||||
gridPane.setVgap(8);
|
||||
gridPane.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing());
|
||||
@@ -172,9 +174,15 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
case LITTLE_SKIN:
|
||||
HintPane hint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
hint.setText(i18n("account.skin.type.little_skin.hint"));
|
||||
// Allow the tooltip to span two columns and expand horizontally to avoid overlapping with the dialog action area/top-right help button
|
||||
GridPane.setColumnSpan(hint, 2);
|
||||
GridPane.setHgrow(hint, Priority.ALWAYS);
|
||||
hint.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
gridPane.addRow(0, hint);
|
||||
break;
|
||||
case LOCAL_FILE:
|
||||
gridPane.setPadding(new Insets(0, 0, 0, 10));
|
||||
gridPane.addRow(0, new Label(i18n("account.skin.model")), modelCombobox);
|
||||
gridPane.addRow(1, new Label(i18n("account.skin")), skinSelector);
|
||||
gridPane.addRow(2, new Label(i18n("account.cape")), capeSelector);
|
||||
|
||||
@@ -23,7 +23,6 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
@@ -45,7 +44,6 @@ public class FileItem extends BorderPane {
|
||||
|
||||
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
|
||||
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private final SimpleStringProperty path = new SimpleStringProperty(this, "path");
|
||||
private final SimpleBooleanProperty convertToRelativePath = new SimpleBooleanProperty(this, "convertToRelativePath");
|
||||
|
||||
@@ -65,10 +63,6 @@ public class FileItem extends BorderPane {
|
||||
FXUtils.installFastTooltip(right, i18n("button.edit"));
|
||||
setRight(right);
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
tip.textProperty().bind(tooltipProperty());
|
||||
Tooltip.install(this, tip);
|
||||
|
||||
convertToRelativePath.addListener(onInvalidating(() -> path.set(processPath(path.get()))));
|
||||
}
|
||||
|
||||
@@ -142,18 +136,6 @@ public class FileItem extends BorderPane {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getTooltip() {
|
||||
return tooltip.get();
|
||||
}
|
||||
|
||||
public StringProperty tooltipProperty() {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
public void setTooltip(String tooltip) {
|
||||
this.tooltip.set(tooltip);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path.get();
|
||||
}
|
||||
|
||||
@@ -58,11 +58,12 @@ public final class ImagePickerItem extends BorderPane {
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.CLOSE.createIcon(20));
|
||||
deleteButton.setGraphic(SVG.RESTORE.createIcon(20));
|
||||
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
FXUtils.installFastTooltip(selectButton, i18n("button.edit"));
|
||||
FXUtils.installFastTooltip(deleteButton, i18n("button.reset"));
|
||||
|
||||
HBox hBox = new HBox();
|
||||
hBox.getChildren().setAll(imageView, selectButton, deleteButton);
|
||||
|
||||
@@ -20,9 +20,11 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -30,12 +32,22 @@ 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;
|
||||
private final Label lblCreationWarning;
|
||||
private final SpinnerPane acceptPane;
|
||||
private final JFXButton acceptButton;
|
||||
|
||||
public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult, ValidatorBase... validators) {
|
||||
this(text, initialValue, onResult);
|
||||
if (validators != null && validators.length > 0) {
|
||||
textField.getValidators().addAll(validators);
|
||||
FXUtils.setValidateWhileTextChanged(textField, true);
|
||||
acceptButton.disableProperty().bind(textField.activeValidatorProperty().isNotNull());
|
||||
}
|
||||
}
|
||||
|
||||
public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult) {
|
||||
textField = new JFXTextField(initialValue);
|
||||
@@ -47,7 +59,7 @@ public class InputDialogPane extends JFXDialogLayout {
|
||||
|
||||
acceptPane = new SpinnerPane();
|
||||
acceptPane.getStyleClass().add("small-spinner-pane");
|
||||
JFXButton acceptButton = new JFXButton(i18n("button.ok"));
|
||||
acceptButton = new JFXButton(i18n("button.ok"));
|
||||
acceptButton.getStyleClass().add("dialog-accept");
|
||||
acceptPane.setContent(acceptButton);
|
||||
|
||||
@@ -69,10 +81,15 @@ public class InputDialogPane extends JFXDialogLayout {
|
||||
lblCreationWarning.setText(msg);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class MessageDialogPane extends HBox {
|
||||
{
|
||||
StackPane titlePane = new StackPane();
|
||||
titlePane.getStyleClass().addAll("jfx-layout-heading", "title");
|
||||
titlePane.getChildren().setAll(new Label(title != null ? title : i18n("message.info")));
|
||||
titlePane.getChildren().setAll(new Label(title != null ? title : i18n(type.getDisplayName())));
|
||||
|
||||
StackPane content = new StackPane();
|
||||
content.getStyleClass().add("jfx-layout-body");
|
||||
|
||||
@@ -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;
|
||||
@@ -59,17 +57,14 @@ public class OptionToggleButton extends StackPane {
|
||||
left.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
JFXToggleButton toggleButton = new JFXToggleButton();
|
||||
pane.setRight(toggleButton);
|
||||
StackPane right = new StackPane(toggleButton);
|
||||
right.setAlignment(Pos.CENTER);
|
||||
pane.setRight(right);
|
||||
toggleButton.selectedProperty().bindBidirectional(selected);
|
||||
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();
|
||||
|
||||
@@ -17,18 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -48,14 +43,15 @@ import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogUtils;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||
import org.jackhuang.hmcl.ui.animation.*;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -66,7 +62,9 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
@@ -77,14 +75,9 @@ import static org.jackhuang.hmcl.util.io.FileUtils.getExtension;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DecoratorController {
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final Decorator decorator;
|
||||
private final Navigator navigator;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private JFXDialogPane dialogPane;
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
@@ -134,19 +127,24 @@ public class DecoratorController {
|
||||
|
||||
// pass key events to current dialog / current page
|
||||
decorator.addEventFilter(KeyEvent.ANY, e -> {
|
||||
if (!(e.getTarget() instanceof Node)) {
|
||||
return; // event source can't be determined
|
||||
if (!(e.getTarget() instanceof Node t)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Node newTarget;
|
||||
if (dialogPane != null && dialogPane.peek().isPresent()) {
|
||||
newTarget = dialogPane.peek().get(); // current dialog
|
||||
} else {
|
||||
newTarget = navigator.getCurrentPage(); // current page
|
||||
|
||||
JFXDialogPane currentDialogPane = null;
|
||||
if (decorator.getDrawerWrapper() != null) {
|
||||
currentDialogPane = (JFXDialogPane) decorator.getDrawerWrapper().getProperties().get(DialogUtils.PROPERTY_DIALOG_PANE_INSTANCE);
|
||||
}
|
||||
|
||||
if (currentDialogPane != null && currentDialogPane.peek().isPresent()) {
|
||||
newTarget = currentDialogPane.peek().get();
|
||||
} else {
|
||||
newTarget = navigator.getCurrentPage();
|
||||
}
|
||||
boolean needsRedirect = true;
|
||||
Node t = (Node) e.getTarget();
|
||||
|
||||
while (t != null) {
|
||||
if (t == newTarget) {
|
||||
// current event target is in newTarget
|
||||
@@ -445,81 +443,12 @@ public class DecoratorController {
|
||||
}
|
||||
|
||||
// ==== Dialog ====
|
||||
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialog == null) {
|
||||
if (decorator.getDrawerWrapper() == null) {
|
||||
// Sometimes showDialog will be invoked before decorator was initialized.
|
||||
// Keep trying again.
|
||||
Platform.runLater(() -> showDialog(node));
|
||||
return;
|
||||
}
|
||||
dialog = new JFXDialog(AnimationUtils.isAnimationEnabled()
|
||||
? JFXDialog.DialogTransition.CENTER
|
||||
: JFXDialog.DialogTransition.NONE);
|
||||
dialogPane = new JFXDialogPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
decorator.capableDraggingWindow(dialog);
|
||||
decorator.forbidDraggingWindow(dialogPane);
|
||||
dialog.setDialogContainer(decorator.getDrawerWrapper());
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
|
||||
navigator.setDisable(true);
|
||||
}
|
||||
dialogPane.push(node);
|
||||
|
||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
|
||||
if (dialog.isVisible()) {
|
||||
dialog.requestFocus();
|
||||
if (node instanceof DialogAware)
|
||||
((DialogAware) node).onDialogShown();
|
||||
} else {
|
||||
dialog.visibleProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
dialog.requestFocus();
|
||||
if (node instanceof DialogAware)
|
||||
((DialogAware) node).onDialogShown();
|
||||
observable.removeListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
DialogUtils.show(decorator, node);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
if (dialog != null) {
|
||||
JFXDialogPane pane = dialogPane;
|
||||
|
||||
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
|
||||
dialog.setOnDialogClosed(e -> pane.pop(node));
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
|
||||
navigator.setDisable(false);
|
||||
} else {
|
||||
pane.pop(node);
|
||||
}
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
}
|
||||
DialogUtils.close(node);
|
||||
}
|
||||
|
||||
// ==== Toast ====
|
||||
|
||||
@@ -68,7 +68,7 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
gameVersion,
|
||||
downloadProvider,
|
||||
libraryId,
|
||||
() -> controller.onPrev(false, Navigation.NavigationDirection.NEXT)
|
||||
() -> controller.onPrev(false, Navigation.NavigationDirection.PREVIOUS)
|
||||
), Navigation.NavigationDirection.NEXT
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -40,6 +39,7 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.DownloadListPage;
|
||||
@@ -84,8 +84,8 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
|
||||
"game", versionPageNavigator::onGameSelected)));
|
||||
modpackTab.setNodeSupplier(loadVersionFor(() -> {
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((profile, __, file) -> {
|
||||
Versions.downloadModpackImpl(profile, uploadVersion, file);
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((profile, __, mod, file) -> {
|
||||
Versions.downloadModpackImpl(profile, uploadVersion, mod, file);
|
||||
}, false);
|
||||
|
||||
JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack"));
|
||||
@@ -94,9 +94,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
page.getActions().add(installLocalModpackButton);
|
||||
return page;
|
||||
}));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, file) -> download(profile, version, file, "mods"), true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true)));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, mod, file) -> download(profile, version, file, "mods"), true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, mod, file) -> download(profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack((profile, version, mod, file) -> download(profile, version, file, "shaderpacks"), true)));
|
||||
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
|
||||
tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
|
||||
@@ -135,10 +135,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
|
||||
|
||||
Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> {
|
||||
if (!FileUtils.isNameValid(result)) {
|
||||
reject.accept(i18n("install.new_game.malformed"));
|
||||
return;
|
||||
}
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
@@ -156,9 +152,8 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Controllers.showToast(i18n("install.success"));
|
||||
}
|
||||
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
|
||||
resolve.run();
|
||||
}, file.getFile().getFilename());
|
||||
}, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -180,4 +180,5 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
public static final SettingsMap.Key<Modpack> MODPACK_MANIFEST = new SettingsMap.Key<>("MODPACK_MANIFEST");
|
||||
public static final SettingsMap.Key<Charset> MODPACK_CHARSET = new SettingsMap.Key<>("MODPACK_CHARSET");
|
||||
public static final SettingsMap.Key<Boolean> MODPACK_MANUALLY_CREATED = new SettingsMap.Key<>("MODPACK_MANUALLY_CREATED");
|
||||
public static final SettingsMap.Key<String> MODPACK_ICON_URL = new SettingsMap.Key<>("MODPACK_ICON_URL");
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -45,6 +46,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final Path file;
|
||||
private final String updateVersion;
|
||||
private String iconUrl;
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile) {
|
||||
this(profile, null, null);
|
||||
@@ -64,12 +66,18 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
this.updateVersion = updateVersion;
|
||||
}
|
||||
|
||||
public void setIconUrl(String iconUrl) {
|
||||
this.iconUrl = iconUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(SettingsMap settings) {
|
||||
if (file != null)
|
||||
settings.put(LocalModpackPage.MODPACK_FILE, file);
|
||||
if (updateVersion != null)
|
||||
settings.put(LocalModpackPage.MODPACK_NAME, updateVersion);
|
||||
if (StringUtils.isNotBlank(iconUrl))
|
||||
settings.put(LocalModpackPage.MODPACK_ICON_URL, iconUrl);
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
}
|
||||
|
||||
@@ -78,6 +86,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
ServerModpackManifest serverModpackManifest = settings.get(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
Modpack modpack = settings.get(LocalModpackPage.MODPACK_MANIFEST);
|
||||
String name = settings.get(LocalModpackPage.MODPACK_NAME);
|
||||
String iconUrl = settings.get(LocalModpackPage.MODPACK_ICON_URL);
|
||||
Charset charset = settings.get(LocalModpackPage.MODPACK_CHARSET);
|
||||
boolean isManuallyCreated = settings.getOrDefault(LocalModpackPage.MODPACK_MANUALLY_CREATED, false);
|
||||
|
||||
@@ -111,7 +120,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
return ModpackHelper.getInstallTask(profile, serverModpackManifest, name, modpack)
|
||||
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
|
||||
} else {
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack, iconUrl)
|
||||
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ import javafx.scene.shape.SVGPath;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.URLValidator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
@@ -161,7 +164,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
} catch (IOException e) {
|
||||
reject.accept(e.getMessage());
|
||||
}
|
||||
});
|
||||
}, "", new URLValidator());
|
||||
}
|
||||
|
||||
public void onChooseRepository() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
@@ -207,10 +206,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
updatePane.getChildren().setAll(hBox, closeUpdateButton);
|
||||
}
|
||||
|
||||
StackPane launchPane = new StackPane();
|
||||
HBox launchPane = new HBox();
|
||||
launchPane.getStyleClass().add("launch-pane");
|
||||
launchPane.setMaxWidth(230);
|
||||
launchPane.setMaxHeight(55);
|
||||
FXUtils.onScroll(launchPane, versions, list -> {
|
||||
String currentId = getCurrentGame();
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
@@ -219,16 +216,11 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
||||
{
|
||||
JFXButton launchButton = new JFXButton();
|
||||
launchButton.setPrefWidth(230);
|
||||
launchButton.setPrefHeight(55);
|
||||
//launchButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
launchButton.getStyleClass().add("launch-button");
|
||||
launchButton.setDefaultButton(true);
|
||||
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
|
||||
{
|
||||
VBox graphic = new VBox();
|
||||
graphic.setAlignment(Pos.CENTER);
|
||||
graphic.setTranslateX(-7);
|
||||
graphic.setMaxWidth(200);
|
||||
Label launchLabel = new Label();
|
||||
launchLabel.setStyle("-fx-font-size: 16px;");
|
||||
Label currentLabel = new Label();
|
||||
@@ -261,26 +253,11 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchButton.setGraphic(graphic);
|
||||
}
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.setWidth(1);
|
||||
separator.setHeight(57);
|
||||
separator.setTranslateX(95);
|
||||
separator.setMouseTransparent(true);
|
||||
|
||||
menuButton = new JFXButton();
|
||||
menuButton.setPrefHeight(55);
|
||||
menuButton.setPrefWidth(230);
|
||||
//menuButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
menuButton.setStyle("-fx-font-size: 15px;");
|
||||
menuButton.getStyleClass().add("menu-button");
|
||||
menuButton.setOnAction(e -> onMenu());
|
||||
menuButton.setClip(new Rectangle(211, -100, 100, 200));
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.ARROW_DROP_UP.createIcon(30);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(6);
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(graphic);
|
||||
menuButton.setGraphic(SVG.ARROW_DROP_UP.createIcon(30));
|
||||
|
||||
EventHandler<MouseEvent> secondaryClickHandle = event -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY && event.getClickCount() == 1) {
|
||||
@@ -291,14 +268,14 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchButton.addEventHandler(MouseEvent.MOUSE_CLICKED, secondaryClickHandle);
|
||||
menuButton.addEventHandler(MouseEvent.MOUSE_CLICKED, secondaryClickHandle);
|
||||
|
||||
launchPane.getChildren().setAll(launchButton, separator, menuButton);
|
||||
launchPane.getChildren().setAll(launchButton, menuButton);
|
||||
}
|
||||
|
||||
getChildren().addAll(updatePane, launchPane);
|
||||
|
||||
menu.setMaxHeight(365);
|
||||
menu.setMaxWidth(545);
|
||||
menu.setAlwaysShowingVBar(true);
|
||||
menu.setAlwaysShowingVBar(false);
|
||||
FXUtils.onClicked(menu, popup::hide);
|
||||
versionNodes = MappedObservableList.create(versions, version -> {
|
||||
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
|
||||
@@ -391,7 +368,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) {
|
||||
@@ -285,6 +287,8 @@ public class PersonalizationPage extends StackPane {
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
clearButton.setOnAction(e -> cboFont.setValue(null));
|
||||
|
||||
FXUtils.installFastTooltip(clearButton, i18n("button.reset"));
|
||||
|
||||
hBox.getChildren().setAll(cboFont, clearButton);
|
||||
|
||||
borderPane.setRight(hBox);
|
||||
@@ -299,8 +303,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);
|
||||
@@ -293,7 +299,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
.thenApplyAsync(
|
||||
encoding -> ModpackHelper.readModpackManifest(modpackFile, encoding))
|
||||
.thenApplyAsync(modpack -> ModpackHelper
|
||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack, null)
|
||||
.executor())
|
||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"), TaskCancellationAction.NO_CANCEL);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -226,7 +226,6 @@ public final class SettingsPage extends ScrollPane {
|
||||
|
||||
{
|
||||
MultiFileItem<EnumCommonDirectory> fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumCommonDirectory.CUSTOM)
|
||||
@@ -234,6 +233,7 @@ public final class SettingsPage extends ScrollPane {
|
||||
.setDirectory(true)
|
||||
.bindBidirectional(config().commonDirectoryProperty())
|
||||
));
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
|
||||
ComponentSublist fileCommonLocationSublist = new ComponentSublist();
|
||||
fileCommonLocationSublist.getContent().add(fileCommonLocation);
|
||||
@@ -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());
|
||||
|
||||
@@ -22,10 +22,9 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.RequiredFieldValidator;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -44,6 +43,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -53,7 +53,6 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final StringProperty location;
|
||||
private final Profile profile;
|
||||
|
||||
private final JFXTextField txtProfileName;
|
||||
private final FileItem gameDir;
|
||||
private final OptionToggleButton toggleUseRelativePath;
|
||||
@@ -147,6 +146,38 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
() -> !txtProfileName.validate() || StringUtils.isBlank(getLocation()),
|
||||
txtProfileName.textProperty(), location));
|
||||
}
|
||||
|
||||
ChangeListener<String> locationChangeListener = (observable, oldValue, newValue) -> {
|
||||
Path newPath;
|
||||
try {
|
||||
newPath = FileUtils.toAbsolute(Path.of(newValue));
|
||||
} catch (InvalidPathException ignored) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!".minecraft".equals(FileUtils.getName(newPath)))
|
||||
return;
|
||||
|
||||
Path parent = newPath.getParent();
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
String suggestedName = FileUtils.getName(parent);
|
||||
if (!suggestedName.isBlank()) {
|
||||
txtProfileName.setText(suggestedName);
|
||||
}
|
||||
};
|
||||
locationProperty().addListener(locationChangeListener);
|
||||
|
||||
txtProfileName.textProperty().addListener(new ChangeListener<>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
|
||||
if (txtProfileName.isFocused()) {
|
||||
txtProfileName.textProperty().removeListener(this);
|
||||
locationProperty().removeListener(locationChangeListener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onSave() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -157,15 +157,15 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
this.failed.set(failed);
|
||||
}
|
||||
|
||||
public void download(RemoteMod.Version file) {
|
||||
public void download(RemoteMod mod, RemoteMod.Version file) {
|
||||
if (this.callback == null) {
|
||||
saveAs(file);
|
||||
saveAs(mod, file);
|
||||
} else {
|
||||
this.callback.download(version.getProfile(), version.getVersion(), file);
|
||||
this.callback.download(version.getProfile(), version.getVersion(), mod, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveAs(RemoteMod.Version file) {
|
||||
public void saveAs(RemoteMod mod, RemoteMod.Version file) {
|
||||
String extension = StringUtils.substringAfterLast(file.getFile().getFilename(), '.');
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
@@ -287,7 +287,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (targetLoaders.contains(loader)) {
|
||||
list.getContent().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
|
||||
new ModItem(modVersion, control)
|
||||
new ModItem(control.addon, modVersion, control)
|
||||
);
|
||||
break resolve;
|
||||
}
|
||||
@@ -308,7 +308,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
ComponentList sublist = new ComponentList(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>(versions.size());
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(v, control));
|
||||
items.add(new ModItem(control.addon, v, control));
|
||||
}
|
||||
return items;
|
||||
});
|
||||
@@ -373,7 +373,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
private static final class ModItem extends StackPane {
|
||||
|
||||
ModItem(RemoteMod.Version dataItem, DownloadPage selfPage) {
|
||||
ModItem(RemoteMod mod, RemoteMod.Version dataItem, DownloadPage selfPage) {
|
||||
VBox pane = new VBox(8);
|
||||
pane.setPadding(new Insets(8, 0, 8, 0));
|
||||
|
||||
@@ -435,7 +435,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
FXUtils.onClicked(container, () -> Controllers.dialog(new ModVersion(dataItem, selfPage)));
|
||||
FXUtils.onClicked(container, () -> Controllers.dialog(new ModVersion(mod, dataItem, selfPage)));
|
||||
getChildren().setAll(container);
|
||||
|
||||
// Workaround for https://github.com/HMCL-dev/HMCL/issues/2129
|
||||
@@ -444,30 +444,21 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
private static final class ModVersion extends JFXDialogLayout {
|
||||
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
|
||||
public ModVersion(RemoteMod mod, RemoteMod.Version version, DownloadPage selfPage) {
|
||||
RemoteModRepository.Type type = selfPage.repository.getType();
|
||||
|
||||
String title;
|
||||
switch (type) {
|
||||
case WORLD:
|
||||
title = "world.download.title";
|
||||
break;
|
||||
case MODPACK:
|
||||
title = "modpack.download.title";
|
||||
break;
|
||||
case RESOURCE_PACK:
|
||||
title = "resourcepack.download.title";
|
||||
break;
|
||||
case MOD:
|
||||
default:
|
||||
title = "mods.download.title";
|
||||
break;
|
||||
}
|
||||
String title = switch (type) {
|
||||
case WORLD -> "world.download.title";
|
||||
case MODPACK -> "modpack.download.title";
|
||||
case RESOURCE_PACK -> "resourcepack.download.title";
|
||||
case SHADER_PACK -> "shaderpack.download.title";
|
||||
default -> "mods.download.title";
|
||||
};
|
||||
this.setHeading(new HBox(new Label(i18n(title, version.getName()))));
|
||||
|
||||
VBox box = new VBox(8);
|
||||
box.setPadding(new Insets(8));
|
||||
ModItem modItem = new ModItem(version, selfPage);
|
||||
ModItem modItem = new ModItem(mod, version, selfPage);
|
||||
modItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again
|
||||
box.getChildren().setAll(modItem);
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
@@ -493,7 +484,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (type == RemoteModRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.download(version);
|
||||
selfPage.download(mod, version);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -503,7 +494,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.saveAs(version);
|
||||
selfPage.saveAs(mod, version);
|
||||
});
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
@@ -557,6 +548,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
public interface DownloadCallback {
|
||||
void download(Profile profile, @Nullable String version, RemoteMod.Version file);
|
||||
void download(Profile profile, @Nullable String version, RemoteMod mod, RemoteMod.Version file);
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
public static DownloadListPage ofMod(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
@@ -49,6 +49,10 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.RESOURCE_PACK, CurseForgeRemoteModRepository.RESOURCE_PACKS, ModrinthRemoteModRepository.RESOURCE_PACKS);
|
||||
}
|
||||
|
||||
public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, null, ModrinthRemoteModRepository.SHADER_PACKS);
|
||||
}
|
||||
|
||||
private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection, RemoteModRepository.Type type, CurseForgeRemoteModRepository curseForge, ModrinthRemoteModRepository modrinth) {
|
||||
super(null, callback, versionSelection);
|
||||
|
||||
|
||||
@@ -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 -> {
|
||||
@@ -504,7 +493,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
|
||||
remoteMod,
|
||||
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getInstanceId()),
|
||||
(profile, version, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(profile, version, file, "mods")
|
||||
(profile, version, mod, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(profile, version, file, "mods")
|
||||
));
|
||||
});
|
||||
button.setDisable(false);
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.CreateAccountPane;
|
||||
@@ -50,8 +53,8 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class Versions {
|
||||
private Versions() {
|
||||
@@ -69,7 +72,7 @@ public final class Versions {
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadModpackImpl(Profile profile, String version, RemoteMod.Version file) {
|
||||
public static void downloadModpackImpl(Profile profile, String version, RemoteMod mod, RemoteMod.Version file) {
|
||||
Path modpack;
|
||||
URI downloadURL;
|
||||
try {
|
||||
@@ -85,11 +88,14 @@ public final class Versions {
|
||||
new FileDownloadTask(downloadURL, modpack)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
if (version != null) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack, version));
|
||||
} else {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack));
|
||||
}
|
||||
ModpackInstallWizardProvider installWizardProvider;
|
||||
if (version != null)
|
||||
installWizardProvider = new ModpackInstallWizardProvider(profile, modpack, version);
|
||||
else
|
||||
installWizardProvider = new ModpackInstallWizardProvider(profile, modpack);
|
||||
if (StringUtils.isNotBlank(mod.getIconUrl()))
|
||||
installWizardProvider.setIconUrl(mod.getIconUrl());
|
||||
Controllers.getDecorator().startWizard(installWizardProvider);
|
||||
} else if (e instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
} else {
|
||||
@@ -124,10 +130,6 @@ public final class Versions {
|
||||
|
||||
public static CompletableFuture<String> renameVersion(Profile profile, String version) {
|
||||
return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> {
|
||||
if (!HMCLGameRepository.isValidVersionId(newName)) {
|
||||
reject.accept(i18n("install.new_game.malformed"));
|
||||
return;
|
||||
}
|
||||
if (profile.getRepository().renameVersion(version, newName)) {
|
||||
resolve.run();
|
||||
profile.getRepository().refreshVersionsAsync()
|
||||
@@ -139,7 +141,8 @@ public final class Versions {
|
||||
} else {
|
||||
reject.accept(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
}, version);
|
||||
}, version, new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName)));
|
||||
}
|
||||
|
||||
public static void exportVersion(Profile profile, String version) {
|
||||
@@ -155,10 +158,6 @@ public final class Versions {
|
||||
new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, resolve, reject) -> {
|
||||
String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue();
|
||||
boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue();
|
||||
if (!HMCLGameRepository.isValidVersionId(newVersionName)) {
|
||||
reject.accept(i18n("install.new_game.malformed"));
|
||||
return;
|
||||
}
|
||||
Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
@@ -174,6 +173,7 @@ public final class Versions {
|
||||
})
|
||||
.addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("version.manage.duplicate.confirm")))
|
||||
.addQuestion(new PromptDialogPane.Builder.StringQuestion(null, version,
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName))))
|
||||
.addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n("version.manage.duplicate.duplicate_save"), false)));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
@@ -141,7 +142,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
else
|
||||
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
|
||||
}).start();
|
||||
}, world.getWorldName());
|
||||
}, world.getWorldName(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid));
|
||||
}, e -> {
|
||||
LOG.warning("Unable to parse world file " + zipFile, e);
|
||||
Controllers.dialog(i18n("world.import.invalid"));
|
||||
|
||||
@@ -421,15 +421,40 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.launch-pane {
|
||||
-fx-max-height: 57px;
|
||||
-fx-min-height: 57px;
|
||||
-fx-max-width: 230px;
|
||||
-fx-min-width: 230px;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button {
|
||||
-fx-translate-y: 1px;
|
||||
-fx-max-height: 55px;
|
||||
-fx-min-height: 55px;
|
||||
-fx-background-color: -monet-primary-container;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button.launch-button {
|
||||
-fx-max-width: 207px;
|
||||
-fx-min-width: 207px;
|
||||
-fx-border-width: 0 3px 0 0;
|
||||
-fx-border-color: -monet-on-surface-variant;
|
||||
-fx-background-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button.menu-button {
|
||||
-fx-max-width: 20px;
|
||||
-fx-min-width: 20px;
|
||||
-fx-font-size: 15px;
|
||||
-fx-background-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button > StackPane > .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-on-primary-container;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
-fx-padding: 0.0;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button, .jfx-button * {
|
||||
@@ -441,10 +466,6 @@
|
||||
-fx-fill: -monet-on-primary-container;
|
||||
}
|
||||
|
||||
.launch-pane > Rectangle {
|
||||
-fx-fill: -monet-primary-container;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Tab Pane *
|
||||
@@ -863,6 +884,16 @@
|
||||
-jfx-unfocus-color: transparent;
|
||||
}
|
||||
|
||||
.jfx-text-field .context-menu {
|
||||
-fx-background-color: -monet-surface-container;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
|
||||
.jfx-text-field .context-menu .menu-item:focused {
|
||||
-fx-background-color: -monet-secondary-container;
|
||||
-fx-text-fill: -monet-on-secondary-container;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX List View *
|
||||
@@ -1334,6 +1365,10 @@
|
||||
-fx-stroke: -monet-primary-container;
|
||||
}
|
||||
|
||||
.jfx-spinner .arc {
|
||||
-fx-stroke-line-cap: round;
|
||||
}
|
||||
|
||||
.first-spinner {
|
||||
-jfx-radius: 20;
|
||||
}
|
||||
@@ -1573,6 +1608,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 |
@@ -21,7 +21,7 @@
|
||||
|
||||
about=About
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2025 huangyuhui
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui
|
||||
about.author=Author
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -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.
|
||||
@@ -99,31 +100,31 @@ account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.microsoft=Microsoft
|
||||
account.methods.microsoft.birth=How to Change Your Account Birth Date
|
||||
account.methods.microsoft.code=Code (Copied to Clipboard)
|
||||
account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\
|
||||
\n\
|
||||
There are some extra works for us, but you can safely close this tab for now.
|
||||
account.methods.microsoft.deauthorize=Deauthorize
|
||||
account.methods.microsoft.error.add_family=An adult must add you to a family in order for you to play Minecraft because you are not yet 18 years old.
|
||||
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birth Date" to learn how to change it.
|
||||
account.methods.microsoft.error.add_family=Please click <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">here</a> to change your account birth date to be over 18 years old, or add your account to a family.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.
|
||||
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click "Create Profile / Edit Profile Name" to create one before continuing.
|
||||
account.methods.microsoft.error.no_character=Please ensure you have purchased Minecraft: Java Edition.\nIf you have already purchased the game, a profile may not have been created yet.\nClick "Create Profile / Edit Profile Name" to create it.
|
||||
account.methods.microsoft.error.banned=Your account may have been banned by Xbox Live.\nYou can click "Ban Query" for more details.
|
||||
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to link one.
|
||||
account.methods.microsoft.error.no_character=Please confirm that you have purchased Minecraft: Java Edition.\n\
|
||||
If you have already purchased it, a game profile may not have been created. Please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to create a game profile.
|
||||
account.methods.microsoft.error.banned=Your account may have been banned by Xbox Live.\n\
|
||||
You can click <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">here</a> to check the ban status of your account.
|
||||
account.methods.microsoft.error.unknown=Failed to log in, error code: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Please log in using your password on the Microsoft account login page, and do not use a verification code to log in.
|
||||
account.methods.microsoft.error.wrong_verify_method=Failed to log in. Please try logging into your account using PASSWORD instead of other login methods.
|
||||
account.methods.microsoft.logging_in=Logging in...
|
||||
account.methods.microsoft.hint=Please click "Log in" and copy the code displayed here to complete the login process in the browser window that opens.\n\
|
||||
\n\
|
||||
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
|
||||
account.methods.microsoft.manual=Your device code is <b>%1$s</b>. Please click here to copy.\n\
|
||||
\n\
|
||||
After clicking "Log in", you should complete the login process in the opened browser window. If that does not show up, you can navigate to %2$s manually.\n\
|
||||
\n\
|
||||
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
|
||||
account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
|
||||
account.methods.microsoft.hint=Click the "Log in" button to start adding your Microsoft account.
|
||||
account.methods.microsoft.manual=Please enter the code shown above on the pop-up webpage to complete the login.\n\
|
||||
\n\
|
||||
If the website fails to load, please open %s manually in your browser.\n\
|
||||
\n\
|
||||
<b>If your internet connection is bad, it may cause web pages to load slowly or fail to load altogether. You may try again later or switch to a different internet connection.</b>
|
||||
account.methods.microsoft.profile=Account Profile
|
||||
account.methods.microsoft.purchase=Buy Minecraft
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the <a href="https://hmcl.huangyuhui.net/download">official build</a> to log in.
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
account.methods.offline.name.special_characters=Use only letters, numbers, and underscores (max 16 chars)
|
||||
@@ -186,6 +187,7 @@ button.export=Export
|
||||
button.no=No
|
||||
button.ok=OK
|
||||
button.ok.countdown=OK (%d)
|
||||
button.reset=Reset
|
||||
button.reveal_dir=Reveal
|
||||
button.refresh=Refresh
|
||||
button.remove=Remove
|
||||
@@ -874,6 +876,7 @@ message.info=Information
|
||||
message.success=Operation successfully completed
|
||||
message.unknown=Unknown
|
||||
message.warning=Warning
|
||||
message.question=Question
|
||||
|
||||
modpack=Modpacks
|
||||
modpack.choose=Choose Modpack
|
||||
@@ -1378,7 +1381,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 +1400,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
|
||||
|
||||
@@ -1423,6 +1426,8 @@ settings.type.special.enable=Enable Instance-specific Settings
|
||||
settings.type.special.edit=Edit Current Instance Settings
|
||||
settings.type.special.edit.hint=Current instance "%s" has enabled the "Instance-specific Settings". All options on this page will NOT affect that instance. Click here to edit its own settings.
|
||||
|
||||
shaderpack.download.title=Download Shader - %1s
|
||||
|
||||
sponsor=Donors
|
||||
sponsor.bmclapi=Downloads for the Chinese Mainland are provided by BMCLAPI. Click here for more information.
|
||||
sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher that allows users to manage multiple Minecraft instances easily. Click here for more information.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
about=حول
|
||||
about.copyright=حقوق الطبع والنشر
|
||||
about.copyright.statement=حقوق النشر © 2025 huangyuhui
|
||||
about.copyright.statement=حقوق النشر © 2026 huangyuhui
|
||||
about.author=المطور
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=اتفاقية الترخيص
|
||||
@@ -103,28 +103,9 @@ account.methods.microsoft.close_page=اكتمل ترخيص حساب Microsoft ا
|
||||
\n\
|
||||
هناك بعض الأعمال الإضافية لنا، ولكن يمكنك إغلاق هذه النافذة بأمان الآن.
|
||||
account.methods.microsoft.deauthorize=إلغاء الترخيص
|
||||
account.methods.microsoft.error.add_family=يجب أن يضيفك شخص بالغ إلى عائلة لتتمكن من لعب Minecraft لأنك لم تبلغ 18 عاماً بعد.
|
||||
account.methods.microsoft.error.add_family_probably=يرجى التحقق مما إذا كان العمر المشار إليه في إعدادات حسابك 18 عاماً على الأقل. إذا لم يكن كذلك وتعتقد أن هذا خطأ، يمكنك النقر على "كيفية تغيير تاريخ ميلاد حسابك" لمعرفة كيفية تغييره.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live غير متاح في بلدك/منطقتك الحالية.
|
||||
account.methods.microsoft.error.missing_xbox_account=لا يحتوي حساب Microsoft الخاص بك على حساب Xbox مرتبط بعد. يرجى النقر على "إنشاء ملف تعريف / تعديل اسم الملف الشخصي" لإنشاء واحد قبل المتابعة.
|
||||
account.methods.microsoft.error.no_character=يرجى التأكد من شراء Minecraft: Java Edition.\nإذا كنت قد اشتريت اللعبة بالفعل، فقد لم يتم إنشاء ملف تعريف بعد.\nانقر على "إنشاء ملف تعريف / تعديل اسم الملف الشخصي" لإنشائه.
|
||||
account.methods.microsoft.error.banned=قد يكون حسابك محظوراً بواسطة Xbox Live.\nيمكنك النقر على "استعلام الحظر" لمزيد من التفاصيل.
|
||||
account.methods.microsoft.error.unknown=فشل تسجيل الدخول، رمز الخطأ: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=يرجى تسجيل الدخول باستخدام كلمة المرور الخاصة بك في صفحة تسجيل الدخول إلى حساب Microsoft، ولا تستخدم رمز التحقق لتسجيل الدخول.
|
||||
account.methods.microsoft.logging_in=جاري تسجيل الدخول...
|
||||
account.methods.microsoft.hint=يرجى النقر على "تسجيل الدخول" ونسخ الرمز المعروض هنا لإكمال عملية تسجيل الدخول في نافذة المتصفح التي تفتح.\n\
|
||||
\n\
|
||||
إذا تم تسريب الرمز المستخدم لتسجيل الدخول إلى حساب Microsoft، يمكنك النقر على "إلغاء الترخيص" لإلغاء ترخيصه.
|
||||
account.methods.microsoft.manual=رمز جهازك هو <b>%1$s</b>. يرجى النقر هنا للنسخ.\n\
|
||||
\n\
|
||||
بعد النقر على "تسجيل الدخول"، يجب عليك إكمال عملية تسجيل الدخول في نافذة المتصفح المفتوحة. إذا لم تظهر، يمكنك الانتقال إلى %2$s يدوياً.\n\
|
||||
\n\
|
||||
إذا تم تسريب الرمز المستخدم لتسجيل الدخول إلى حساب Microsoft، يمكنك النقر على "إلغاء الترخيص" لإلغاء ترخيصه.
|
||||
account.methods.microsoft.makegameidsettings=إنشاء ملف تعريف / تعديل اسم الملف الشخصي
|
||||
account.methods.microsoft.profile=ملف تعريف الحساب
|
||||
account.methods.microsoft.purchase=شراء Minecraft
|
||||
account.methods.microsoft.snapshot=أنت تستخدم إصداراً غير رسمي من HMCL. يرجى تنزيل الإصدار الرسمي لتسجيل الدخول.
|
||||
account.methods.microsoft.snapshot.website=الموقع الرسمي
|
||||
account.methods.offline=غير متصل
|
||||
account.methods.offline.name.special_characters=استخدم الحروف والأرقام والشرطات السفلية فقط (بحد أقصى 16 حرفاً)
|
||||
account.methods.offline.name.invalid=يوصى باستخدام الحروف الإنجليزية والأرقام والشرطات السفلية فقط لاسم المستخدم، ويجب ألا يتجاوز الطول 16 حرفاً.\n\
|
||||
@@ -1367,7 +1348,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 +1367,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=الشفافية
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
about=Acerca de
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2025 huangyuhui.
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui.
|
||||
about.author=Autor
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -104,26 +104,9 @@ account.methods.microsoft.close_page=La autorización de la cuenta de Microsoft
|
||||
Hay algunos trabajos extra para nosotros, pero puedes cerrar esta pestaña con seguridad por ahora.
|
||||
account.methods.microsoft.makegameidsettings=Crear perfil / Editar nombre del perfil
|
||||
account.methods.microsoft.deauthorize=Desautorizar
|
||||
account.methods.microsoft.error.add_family=Un adulto debe añadirte a una familia para que puedas jugar a Minecraft porque aún no tienes 18 años.
|
||||
account.methods.microsoft.error.add_family_probably=Por favor, compruebe si la edad indicada en la configuración de su cuenta es de al menos 18 años. Si no es así y cree que se trata de un error, puede hacer clic en «Cómo cambiar la fecha de nacimiento de su cuenta» para saber cómo cambiarla.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live no está disponible en tu país/región actual.
|
||||
account.methods.microsoft.error.missing_xbox_account=Tu cuenta Microsoft aún no tiene una cuenta Xbox vinculada. Haga clic en «Crear perfil / Editar nombre de perfil» para crear una antes de continuar.
|
||||
account.methods.microsoft.error.no_character=Por favor, asegúrese de que ha comprado Minecraft: Java Edition. \nSi ya lo has comprado, es posible que no hayas creado un perfil de juego.\nPor favor, haga clic en «Crear perfil / Editar nombre de perfil» para crearlo.
|
||||
account.methods.microsoft.error.banned=Es posible que Xbox Live haya bloqueado tu cuenta.\nPuede hacer clic en «Consulta de bloqueo de cuentas» para obtener más detalles.
|
||||
account.methods.microsoft.error.unknown=No se ha podido iniciar sesión, error: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Inicie sesión con su contraseña en la página de inicio de sesión de la cuenta Microsoft y no utilice un código de verificación para iniciar sesión.
|
||||
account.methods.microsoft.logging_in=Iniciando sesión...
|
||||
account.methods.microsoft.hint=Por favor, haga clic en «Iniciar sesión» y copie el código que aparece aquí para completar el proceso de inicio de sesión en la ventana del navegador que se abre.\n\
|
||||
\n\
|
||||
Si el token utilizado para iniciar sesión en la cuenta de Microsoft se ha filtrado, puedes hacer clic en «Desautorizar» para desautorizarlo.
|
||||
account.methods.microsoft.manual=El código de su dispositivo es <b>%1$s</b>. Por favor, haga clic aquí para copiarlo.\n\
|
||||
\n\
|
||||
Después de hacer clic en «Iniciar sesión», debe completar el proceso de inicio de sesión en la ventana abierta del navegador. Si no se muestra, puede navegar a %2$s manualmente.\n\
|
||||
\n\
|
||||
Si el token utilizado para iniciar sesión en la cuenta de Microsoft se ha filtrado, puedes hacer clic en «Desautorizar» para desautorizarlo.
|
||||
account.methods.microsoft.profile=Perfil de la cuenta
|
||||
account.methods.microsoft.purchase=Comprar Minecraft
|
||||
account.methods.microsoft.snapshot=Está utilizando una versión no oficial de HMCL. Por favor, descargue la versión oficial para iniciar sesión.
|
||||
account.methods.microsoft.snapshot.website=Sitio web oficial
|
||||
account.methods.offline=Sin conexión
|
||||
account.methods.offline.name.special_characters=Utilice solo letras, números y guiones bajos (máximo 16 caracteres)
|
||||
@@ -1387,7 +1370,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
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=について
|
||||
about.copyright=著作権
|
||||
about.copyright.statement=Copyright © 2025 huangyuhui
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui
|
||||
about.author=著者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -90,20 +90,11 @@ account.methods.microsoft=Microsoft Account
|
||||
account.methods.microsoft.birth=誕生日の設定を編集する方法...
|
||||
account.methods.microsoft.deauthorize=アカウントのバインドを解除
|
||||
account.methods.microsoft.close_page=Microsoftアカウントの認証が終了しました。後で終了するログイン手順がいくつか残っています。このページを今すぐ閉じることができます。
|
||||
account.methods.microsoft.error.add_family=まだ18歳ではないため、Minecraftをプレイするには、大人があなたを家族に追加する必要があります。
|
||||
account.methods.microsoft.error.add_family_probably=すでに18歳であるかどうかにかかわらず、アカウント設定を確認してください。そうでない場合は、アカウントを家族に追加する必要があります。
|
||||
account.methods.microsoft.error.country_unavailable=XBoxLiveはお住まいの国/地域ではご利用いただけません。
|
||||
account.methods.microsoft.error.missing_xbox_account=MicrosoftアカウントがXboxアカウントに接続されていません。続行する前に作成してください。
|
||||
account.methods.microsoft.error.no_character=アカウントにMinecraftJavaプロファイルがありません。\nゲームファイルが作成されていない可能性があります\n上記のリンクをクリックして作成してください
|
||||
account.methods.microsoft.error.unknown=ログインに失敗しました。Microsoftはエラーコード %d で応答します。
|
||||
account.methods.microsoft.logging_in=ログイン...
|
||||
account.methods.forgot_password=パスワードをお忘れの方
|
||||
account.methods.microsoft.makegameidsettings=プロファイルを作成/プロフィール名を編集する
|
||||
account.methods.microsoft.hint=「ログイン」ボタンをクリックして、新しく開いたブラウザウィンドウでログインプロセスを続行する必要があります。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.manual=「ログイン」ボタンをクリックした後、新しく開いたブラウザウィンドウで認証を完了する必要があります。ブラウザウィンドウが表示されない場合は、ここをクリックしてURLをコピーし、ブラウザで手動で開くことができます。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.profile=アカウントプロファイル..
|
||||
account.methods.microsoft.purchase=Minecraftを購入する
|
||||
account.methods.microsoft.snapshot=非公式構築 HMCL を使用しているので、公式構築をダウンロードしてマイクロソフトにログインしてください。
|
||||
account.methods.offline=オフライン
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUIDは、Minecraftのゲームキャラクターの一意の識別子です。UUIDの生成方法は、ゲームランチャーによって異なります。UUIDを他のランチャーによって生成されたものに変更すると、オフラインアカウントのバックパック内のゲームブロック/アイテムが残ることが約束されます。このオプションは専門家向けです。何をしているのかわからない限り、このオプションを変更することはお勧めしません。\nこのオプションはサーバーに参加する場合には必要ありません。
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=有涉
|
||||
about.copyright=持權
|
||||
about.copyright.statement=© 2025 huangyuhui 持權
|
||||
about.copyright.statement=© 2026 huangyuhui 持權
|
||||
about.author=作者
|
||||
about.author.statement=嗶站 @huanghongxun
|
||||
about.claim=客契
|
||||
@@ -105,40 +105,12 @@ account.methods.microsoft=微軟之戶簿
|
||||
account.methods.microsoft.birth=何以改戶簿之生辰?
|
||||
account.methods.microsoft.deauthorize=除戶簿
|
||||
account.methods.microsoft.close_page=登微軟之簿畢。啟者自行之餘。頁可閉矣。
|
||||
account.methods.microsoft.error.add_family=擊「纂戶簿訊息」於上以改生年,俾晉歲十八,抑錄戶簿於一家。\n君可求助於右上之鈕。
|
||||
account.methods.microsoft.error.add_family_probably=擊「纂戶簿訊息」於上以改生年,俾晉歲十八,抑錄戶簿於一家。\n君可求助於右上之鈕。
|
||||
account.methods.microsoft.error.country_unavailable=子之國域,無㐅盒展。
|
||||
account.methods.microsoft.error.missing_xbox_account=擊「添檔」於上以鏈㐅盒戶簿。\n君可求助於右上之鈕。
|
||||
account.methods.microsoft.error.no_character=望君復驗礦藝爪哇版之購否。\n然,則戲檔或未立。擊上之「添檔」以立之。\n君可求助於右上之鈕。
|
||||
account.methods.microsoft.error.banned=君之戶簿或見羈於㐅盒展。\n擊「驗戶簿羈否」於上以驗之。
|
||||
account.methods.microsoft.error.unknown=未明之謬。謬碼:%d。\n君可求助於右上之鈕。
|
||||
account.methods.microsoft.error.wrong_verify_method=君宜行符節於微軟戶簿之登頁,不宜行驗碼也。\n君可求助於右上之鈕。
|
||||
account.methods.microsoft.logging_in=方登入……
|
||||
account.methods.microsoft.makegameidsettings=添檔 / 書檔之名
|
||||
account.methods.microsoft.hint=增戶簿之法如次:\n\
|
||||
\ 一、擊「登入」之鈕;\n\
|
||||
\ 二、鍵 HMCL 所書之碼於葉之方立,且後擊「許之」;\n\
|
||||
\ 三、唯此頁是從;\n\
|
||||
\ 四、凡曰「是否允许此应用访问你的信息?」,則擊「善」;\n\
|
||||
\ 五、凡曰「大功告成」,則靜待戶簿增。\n\
|
||||
若曰「有謬」,抑戶簿不能增,宜復試之。\n\
|
||||
<b>網路誠阻,或滯,乃不能成。宜求代而復試。</b>\n\
|
||||
凡有謬,遽求助於右上之鈕。
|
||||
account.methods.microsoft.manual=增戶簿之法如次:\n\
|
||||
\ 一、擊「登入」之鈕;\n\
|
||||
\ 二、鍵 <b>%1$s</b> 於葉之方立 (既錄矣),然後擊「許之」;\n\
|
||||
\ 三、从是頁之令;\n\
|
||||
\ 四、凡曰「是否允许此应用访问你的信息?」,擊「諾」;\n\
|
||||
\ 五、凡曰「大功告成」,則待戶簿之增也。\n\
|
||||
誠不見頁,君可自訪頁於: %2$s\n\
|
||||
誠報曰「有謬」,抑戶簿不能增,宜復試之。\n\
|
||||
<b>網路誠阻,或頁滯,甚者不能成。宜求代而復試之。</b>\n\
|
||||
凡有謬,遽求助於右上之鈕。
|
||||
account.methods.microsoft.profile=纂戶簿訊息
|
||||
account.methods.microsoft.purchase=買礦藝
|
||||
account.methods.forgot_password=亡符節
|
||||
account.methods.ban_query=檢戶簿羈否
|
||||
account.methods.microsoft.snapshot=君所用之 HMCL,蓋他者之版也。宜取官版以登微軟戶簿。
|
||||
account.methods.microsoft.snapshot.website=官網
|
||||
account.methods.offline=離綫之式
|
||||
account.methods.offline.name.special_characters=諫以英文、數、底綫爲名,凡十六字
|
||||
@@ -1152,7 +1124,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 +1148,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=陰翳
|
||||
|
||||
@@ -1201,6 +1173,8 @@ settings.type.special.enable=啟例殊戲設 (不殃余者)
|
||||
settings.type.special.edit=纂例殊戲設
|
||||
settings.type.special.edit.hint=是戲例「%s」既啟「例殊戲設」,是故本頁諸置設弗行於斯。擊此鏈赴斯版之「戲設」頁。
|
||||
|
||||
shaderpack.download.title=引光影 - %1s
|
||||
|
||||
sponsor=資勉
|
||||
sponsor.bmclapi=本朝迅傳之功在 BMCLAPI 焉。BMCLAPI 久行善業,垂衆以慷慨解囊助作者兼濟天下也。[願聞其詳]
|
||||
sponsor.hmcl=Hello Minecraft! Launcher,乃一免費,自由,開源之礦藝啟者。[願聞其詳]
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=О лаунчере
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=Авторские права © 2025 huangyuhui.
|
||||
about.copyright.statement=Авторские права © 2026 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -104,29 +104,10 @@ account.methods.microsoft=Microsoft
|
||||
account.methods.microsoft.birth=Как изменить настройки даты рождения...
|
||||
account.methods.microsoft.close_page=Авторизация аккаунта Microsoft завершена. Осталось выполнить некоторые шаги, которые необходимо завершить позже. Вы можете закрыть эту страницу.
|
||||
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
|
||||
account.methods.microsoft.error.add_family=Поскольку тебе ещё нет 18 лет, взрослый должен добавить тебя в семейный доступ, чтобы ты мог играть в Minecraft.
|
||||
account.methods.microsoft.error.add_family_probably=Проверьте, что возраст, указанный в настройках вашего аккаунта, составляет не менее 18 лет. Если нет и вы считаете, что это ошибка, вы можете нажать «Как изменить настройки даты рождения...», чтобы узнать, как ее изменить.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live недоступен в вашей стране/регионе.
|
||||
account.methods.microsoft.error.missing_xbox_account=Ваш аккаунт Microsoft не подключена к Xbox. Создайте его, прежде чем продолжить.
|
||||
account.methods.microsoft.error.no_character=Убедитесь, что вы приобрели Minecraft: Java Edition.\n\
|
||||
Если вы уже приобрели игру, профиль может быть еще не создан.\n\
|
||||
Нажмите «Создать / Редактировать профиль», чтобы создать его.
|
||||
account.methods.microsoft.error.banned=Возможно ваш аккаунт был заблокирован Xbox Live.\nВы можете нажать «Просмотреть аккаунт заблокирован» для получения более подробной информации.
|
||||
account.methods.microsoft.error.unknown=Не удалось войти. Microsoft отвечает с кодом ошибки %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Пожалуйста, войдите в систему, используя свой пароль на странице входа в аккаунт Microsoft, и не используйте проверочный код для авторизации.
|
||||
account.methods.microsoft.logging_in=Авторизация...
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\n\
|
||||
\n\
|
||||
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.manual=Ваш код устройства - <b>%1$s</b>. Нажмите здесь, чтобы скопировать.\n\
|
||||
\n\
|
||||
После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\n\
|
||||
\n\
|
||||
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.makegameidsettings=Создать / редактировать профиль
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.purchase=Купить Minecraft
|
||||
account.methods.microsoft.snapshot=Вы используете неофициальную версию HMCL, загрузите официальную версию для входа в аккаунт Microsoft.
|
||||
account.methods.microsoft.snapshot.website=Официальный сайт
|
||||
account.methods.offline=Офлайн
|
||||
account.methods.offline.name.special_characters=Использовать только буквы, цифры и подчеркивания (максимум 16 символов)
|
||||
@@ -1360,7 +1341,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 +1360,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=Непрозрачность
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=Про програму
|
||||
about.copyright=Авторське право
|
||||
about.copyright.statement=Авторське право © 2025 huangyuhui
|
||||
about.copyright.statement=Авторське право © 2026 huangyuhui
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=Ліцензійна угода з кінцевим користувачем (EULA)
|
||||
@@ -102,27 +102,10 @@ account.methods.microsoft.close_page=Авторизація облікового
|
||||
\n\
|
||||
Нам залишилося виконати ще трохи роботи, але ви можете безпечно закрити цю вкладку зараз.
|
||||
account.methods.microsoft.deauthorize=Скасувати авторизацію
|
||||
account.methods.microsoft.error.add_family=Дорослий повинен додати вас до сім'ї, щоб ви могли грати в Minecraft, оскільки вам ще не виповнилося 18 років.
|
||||
account.methods.microsoft.error.add_family_probably=Перевірте, чи вік, зазначений у налаштуваннях вашого облікового запису, становить щонайменше 18 років. Якщо ні і ви вважаєте, що це помилка, ви можете натиснути "Як змінити дату народження облікового запису", щоб дізнатися, як це зробити.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live недоступний у вашій поточній країні/регіоні.
|
||||
account.methods.microsoft.error.missing_xbox_account=Ваш обліковий запис Microsoft ще не має прив'язаного облікового запису Xbox. Натисніть "Створити профіль / Редагувати ім'я профілю", щоб створити його перед продовженням.
|
||||
account.methods.microsoft.error.no_character=Переконайтеся, що ви придбали Minecraft: Java Edition.\nЯкщо ви вже придбали гру, профіль може ще не бути створений.\nНатисніть "Створити профіль / Редагувати ім'я профілю", щоб створити його.
|
||||
account.methods.microsoft.error.banned=Ваш обліковий запис може бути заблокований Xbox Live.\nВи можете натиснути "Запит блокування" для отримання додаткової інформації.
|
||||
account.methods.microsoft.error.unknown=Не вдалося увійти, код помилки: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Будь ласка, увійдіть за допомогою свого пароля на сторінці входу в обліковий запис Microsoft і не використовуйте код підтвердження для входу.
|
||||
account.methods.microsoft.logging_in=Вхід...
|
||||
account.methods.microsoft.hint=Будь ласка, натисніть "Увійти" і скопіюйте код, показаний тут, щоб завершити процес входу у відкритому вікні браузера.\n\
|
||||
\n\
|
||||
Якщо токен, який використовується для входу в обліковий запис Microsoft, витік, ви можете натиснути "Скасувати авторизацію", щоб скасувати її.
|
||||
account.methods.microsoft.manual=Код вашого пристрою <b>%1$s</b>. Натисніть тут, щоб скопіювати.\n\
|
||||
\n\
|
||||
Після натискання "Увійти" ви повинні завершити процес входу у відкритому вікні браузера. Якщо воно не з'явиться, ви можете перейти до %2$s вручну.\n\
|
||||
\n\
|
||||
Якщо токен, який використовується для входу в обліковий запис Microsoft, витік, ви можете натиснути "Скасувати авторизацію", щоб скасувати її.
|
||||
account.methods.microsoft.makegameidsettings=Створити профіль / Редагувати ім'я профілю
|
||||
account.methods.microsoft.profile=Профіль облікового запису
|
||||
account.methods.microsoft.purchase=Купити Minecraft
|
||||
account.methods.microsoft.snapshot=Ви використовуєте неофіційну збірку HMCL. Завантажте офіційну збірку для входу.
|
||||
account.methods.microsoft.snapshot.website=Офіційний сайт
|
||||
account.methods.offline=Автономно
|
||||
account.methods.offline.name.special_characters=Використовуйте лише літери, цифри та підкреслення (макс. 16 символів)
|
||||
@@ -1302,7 +1285,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 +1304,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=Непрозорість
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=關於
|
||||
about.copyright=著作權
|
||||
about.copyright.statement=著作權所有 © 2025 huangyuhui
|
||||
about.copyright.statement=著作權所有 © 2026 huangyuhui
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=使用者協議
|
||||
@@ -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=無法連線至認證伺服器。可能是網路問題,請檢查裝置能否正常上網或使用代理服務。
|
||||
@@ -102,35 +103,27 @@ account.methods=登入方式
|
||||
account.methods.authlib_injector=外部登入
|
||||
account.methods.microsoft=Microsoft 帳戶
|
||||
account.methods.microsoft.birth=如何變更帳戶出生日期
|
||||
account.methods.microsoft.deauthorize=移除應用存取權
|
||||
account.methods.microsoft.code=代碼 (已自動複製)
|
||||
account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權。啟動器將自動執行後續步驟。你現在可以關閉本頁面了。
|
||||
account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。
|
||||
account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。
|
||||
account.methods.microsoft.deauthorize=移除應用存取權
|
||||
account.methods.microsoft.error.add_family=請點擊 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此處</a> 更改你的帳戶出生日期,使年齡滿 18 歲以上,或將帳戶加入到家庭中。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。
|
||||
account.methods.microsoft.error.missing_xbox_account=你的 Microsoft 帳戶尚未關聯 Xbox 帳戶,你必須先建立 Xbox 帳戶,才能登入遊戲。
|
||||
account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft: Java 版購買記錄。\n若已購買,則可能未建立遊戲檔案。請點擊上方連結建立。
|
||||
account.methods.microsoft.error.banned=你的帳戶可能被 Xbox Live 封禁。\n你可以點擊上方「查詢帳戶是否被封禁」查看帳戶狀態。
|
||||
account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d
|
||||
account.methods.microsoft.error.wrong_verify_method=請在 Microsoft 帳戶登入頁面使用密碼登入,不要使用驗證碼登入。
|
||||
account.methods.microsoft.error.missing_xbox_account=請點擊下方 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 關聯 Xbox 帳戶。
|
||||
account.methods.microsoft.error.no_character=請確認你已經購買了 Minecraft: Java 版。\n若已購買,則可能未建立遊戲檔案。請點擊 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。
|
||||
account.methods.microsoft.error.banned=你的帳戶可能被 Xbox Live 封禁。\n你可以點擊 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此處</a> 按鈕查詢帳戶封禁狀態。
|
||||
account.methods.microsoft.error.unknown=登入失敗。錯誤碼:%d。
|
||||
account.methods.microsoft.error.wrong_verify_method=登入失敗。請在 Microsoft 帳戶登入頁面嘗試使用密碼登入,不要使用其他登入方式。
|
||||
account.methods.microsoft.logging_in=登入中……
|
||||
account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱
|
||||
account.methods.microsoft.hint=請點擊「登入」按鈕,稍後複製此處顯示的代碼,在開啟的登入網頁中完成登入過程。\n\
|
||||
\n\
|
||||
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\
|
||||
\n\
|
||||
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
account.methods.microsoft.manual=你的代碼為 <b>%1$s</b>。請點擊此處複製。\n\
|
||||
\n\
|
||||
點擊「登入」按鈕後,你應該在開啟的登入網頁中完成登入過程。如果網頁沒有開啟,你可以自行在瀏覽器中訪問 %2$s\n\
|
||||
\n\
|
||||
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\
|
||||
\n\
|
||||
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
account.methods.microsoft.profile=編輯帳戶配置檔
|
||||
account.methods.microsoft.hint=點擊「登入」按鈕開始新增 Microsoft 帳戶。
|
||||
account.methods.microsoft.manual=請在彈出的網頁中輸入上方顯示的代碼以完成登入。\n\
|
||||
若網站未能顯示,請手動在瀏覽器中開啟:%s\n\
|
||||
<b>若網路環境不佳,可能會導致網頁載入緩慢甚至無法載入,請稍後再試或更換網路環境後再試。</b>\n
|
||||
account.methods.microsoft.profile=編輯帳戶個人信息
|
||||
account.methods.microsoft.purchase=購買 Minecraft
|
||||
account.methods.forgot_password=忘記密碼
|
||||
account.methods.ban_query=查詢帳戶是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載官方版本進行登入。
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載 <a href="https://hmcl.huangyuhui.net/download">官方版本</a> 進行登入。
|
||||
account.methods.microsoft.snapshot.website=官方網站
|
||||
account.methods.offline=離線模式
|
||||
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名,且長度不超過 16 個字元
|
||||
@@ -189,6 +182,7 @@ button.export=匯出
|
||||
button.no=否
|
||||
button.ok=確定
|
||||
button.ok.countdown=確定 (%d)
|
||||
button.reset=重設
|
||||
button.reveal_dir=開啟目錄
|
||||
button.refresh=重新整理
|
||||
button.remove=刪除
|
||||
@@ -415,7 +409,7 @@ folder.logs=日誌目錄
|
||||
folder.mod=模組目錄
|
||||
folder.resourcepacks=資源包目錄
|
||||
folder.shaderpacks=著色器包目錄
|
||||
folder.saves=遊戲存檔目錄
|
||||
folder.saves=遊戲世界目錄
|
||||
folder.schematics=原理圖目錄
|
||||
folder.screenshots=截圖目錄
|
||||
folder.world=世界目錄
|
||||
@@ -426,13 +420,13 @@ game.crash.info=遊戲訊息
|
||||
game.crash.reason=崩潰原因
|
||||
game.crash.reason.analyzing=分析中……
|
||||
game.crash.reason.multiple=檢測到多個原因:\n\n
|
||||
game.crash.reason.block=目前遊戲由於某個方塊不能正常工作,無法繼續執行。\n你可以嘗試使用 MCEdit 工具編輯存檔刪除該方塊,或者直接刪除對應的模組。\n方塊類型:%1$s\n方塊坐標:%2$s
|
||||
game.crash.reason.block=目前遊戲由於某個方塊不能正常工作,無法繼續執行。\n你可以嘗試使用 MCEdit 工具編輯世界刪除該方塊,或者直接刪除對應的模組。\n方塊類型:%1$s\n方塊坐標:%2$s
|
||||
game.crash.reason.bootstrap_failed=目前遊戲由於模組「%1$s」出現問題,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。
|
||||
game.crash.reason.mixin_apply_mod_failed=目前遊戲由於 Mixin 無法應用於「%1$s」模組,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。
|
||||
game.crash.reason.config=目前遊戲由於無法解析模組配置檔案,無法繼續執行\n模組「%1$s」的配置檔案「%2$s」無法被解析。
|
||||
game.crash.reason.debug_crash=目前遊戲由於手動觸發崩潰,無法繼續執行。\n事實上遊戲並沒有問題,問題都是你造成的!
|
||||
game.crash.reason.duplicated_mod=目前遊戲由於模組重複安裝,無法繼續執行。\n%s\n每種模組只能安裝一個,請你刪除多餘的模組再試。
|
||||
game.crash.reason.entity=目前遊戲由於某個實體不能正常工作,無法繼續執行。\n你可以嘗試使用 MCEdit 工具編輯存檔刪除該實體,或者直接刪除對應的模組。\n實體類型:%1$s\n實體坐標:%2$s
|
||||
game.crash.reason.entity=目前遊戲由於某個實體不能正常工作,無法繼續執行。\n你可以嘗試使用 MCEdit 工具編輯世界刪除該實體,或者直接刪除對應的模組。\n實體類型:%1$s\n實體坐標:%2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本與目前已經安裝的模組可能不相容,你需要將 Fabric Loader 降級至 0.11.7。
|
||||
game.crash.reason.fabric_warnings=Fabric 提供了一些警告訊息:\n%1$s
|
||||
game.crash.reason.modmixin_failure=目前遊戲由於某些模組注入失敗,無法繼續執行。\n這一般代表著該模組存在問題,或與目前環境不相容。\n你可以查看日誌尋找出錯模組。
|
||||
@@ -710,7 +704,7 @@ modpack.files.mods.voxelmods=VoxelMods 設定,如小地圖
|
||||
modpack.files.options_txt=遊戲設定
|
||||
modpack.files.optionsshaders_txt=光影設定
|
||||
modpack.files.resourcepacks=資源包 (紋理包)
|
||||
modpack.files.saves=遊戲存檔
|
||||
modpack.files.saves=遊戲世界
|
||||
modpack.files.scripts=MineTweaker 設定
|
||||
modpack.files.servers_dat=多人遊戲伺服器清單
|
||||
modpack.installing=安裝模組包
|
||||
@@ -935,11 +929,11 @@ world.export=匯出此世界
|
||||
world.export.title=選取該世界的儲存位置
|
||||
world.export.location=儲存到
|
||||
world.export.wizard=匯出世界「%s」
|
||||
world.extension=存檔壓縮檔
|
||||
world.extension=世界壓縮檔
|
||||
world.import.already_exists=此世界已經存在
|
||||
world.import.choose=選取要匯入的存檔壓縮檔
|
||||
world.import.choose=選取要匯入的世界壓縮檔
|
||||
world.import.failed=無法匯入此世界: %s
|
||||
world.import.invalid=無法識別的存檔壓縮檔
|
||||
world.import.invalid=無法識別的世界壓縮檔
|
||||
world.info=世界資訊
|
||||
world.info.basic=基本資訊
|
||||
world.info.allow_cheats=允許指令(作弊)
|
||||
@@ -1138,9 +1132,9 @@ settings.game.java_directory.invalid=Java 路徑不正確
|
||||
settings.game.java_directory.version=指定 Java 版本
|
||||
settings.game.java_directory.template=%s (%s)
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=執行路徑 (建議使用模組時選取「各實例獨立」。修改後請自行移動相關遊戲檔案,如存檔、模組設定等)
|
||||
settings.game.working_directory=執行路徑 (建議使用模組時選取「各實例獨立」。修改後請自行移動相關遊戲檔案,如世界、模組設定等)
|
||||
settings.game.working_directory.choose=選取執行目錄
|
||||
settings.game.working_directory.hint=在「執行路徑」選項中選取「各實例獨立」使目前實例獨立存放設定、存檔、模組等資料。使用模組時建議開啟此選項以避免不同實例模組衝突。修改此選項後需自行移動存檔等檔案。
|
||||
settings.game.working_directory.hint=在「執行路徑」選項中選取「各實例獨立」使目前實例獨立存放設定、世界、模組等資料。使用模組時建議開啟此選項以避免不同實例模組衝突。修改此選項後需自行移動世界等檔案。
|
||||
|
||||
settings.icon=遊戲圖示
|
||||
|
||||
@@ -1161,12 +1155,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 +1179,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=不透明度
|
||||
|
||||
@@ -1211,6 +1205,8 @@ settings.type.special.enable=啟用實例特定遊戲設定 (不影響其他實
|
||||
settings.type.special.edit=編輯實例特定遊戲設定
|
||||
settings.type.special.edit.hint=目前實例「%s」啟用了「實例特定遊戲設定」,本頁面選項不對目前實例生效。點擊連結以修改目前實例設定。
|
||||
|
||||
shaderpack.download.title=光影下載 - %1s
|
||||
|
||||
sponsor=贊助
|
||||
sponsor.bmclapi=中國大陸下載源由 BMCLAPI 提供高速下載服務。點選此處查閱詳細訊息。
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、自由、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱詳細訊息。
|
||||
@@ -1256,7 +1252,7 @@ terracotta.status.waiting.guest.desc=輸入房主提供的邀請碼加入遊戲
|
||||
terracotta.status.waiting.guest.prompt.title=請輸入房主提供的邀請碼
|
||||
terracotta.status.waiting.guest.prompt.invalid=邀請碼錯誤
|
||||
terracotta.status.scanning=正在掃描區域網路世界
|
||||
terracotta.status.scanning.desc=請<a href="hmcl://game/launch">啟動遊戲</a>,進入單人存檔,按 ESC 鍵,選擇「在區網上公開」,點擊「開始區網世界」。
|
||||
terracotta.status.scanning.desc=請<a href="hmcl://game/launch">啟動遊戲</a>,進入單人世界,按 ESC 鍵,選擇「在區網上公開」,點擊「開始區網世界」。
|
||||
terracotta.status.scanning.back=這將同時停止掃描區域網路世界。
|
||||
terracotta.status.host_starting=正在建立房間
|
||||
terracotta.status.host_starting.back=這將會取消建立房間。
|
||||
@@ -1277,7 +1273,7 @@ terracotta.status.exception.desc.ping_host_fail=加入房間失敗:房間已
|
||||
terracotta.status.exception.desc.ping_host_rst=房間連線中斷:房間已關閉或網路不穩定
|
||||
terracotta.status.exception.desc.guest_et_crash=加入房間失敗:EasyTier 已崩潰,請向開發者回報該問題
|
||||
terracotta.status.exception.desc.host_et_crash=建立房間失敗:EasyTier 已崩潰,請向開發者回報問題
|
||||
terracotta.status.exception.desc.ping_server_rst=房間已關閉:您已退出遊戲存檔,房間已自動關閉
|
||||
terracotta.status.exception.desc.ping_server_rst=房間已關閉:您已退出遊戲世界,房間已自動關閉
|
||||
terracotta.status.exception.desc.scaffolding_invalid_response=協議錯誤:房主發送了錯誤的回應資料,請向開發者回報該問題
|
||||
terracotta.status.fatal.retry=重試
|
||||
terracotta.status.fatal.network=未能下載線上核心。請檢查網路連接,然後再試一次
|
||||
@@ -1357,7 +1353,7 @@ version.manage=實例清單
|
||||
version.manage.clean=清理遊戲目錄
|
||||
version.manage.clean.tooltip=清理「logs」與「crash-reports」目錄
|
||||
version.manage.duplicate=複製遊戲實例
|
||||
version.manage.duplicate.duplicate_save=複製存檔
|
||||
version.manage.duplicate.duplicate_save=複製世界
|
||||
version.manage.duplicate.prompt=請輸入新遊戲實例名稱
|
||||
version.manage.duplicate.confirm=新的遊戲將複製該實例目錄 (".minecraft/versions/<實例名>") 下的檔案,並帶有獨立的執行目錄和設定。
|
||||
version.manage.manage=實例管理
|
||||
@@ -1365,7 +1361,7 @@ version.manage.manage.title=實例管理 - %1s
|
||||
version.manage.redownload_assets_index=更新遊戲資源檔案
|
||||
version.manage.remove=刪除該實例
|
||||
version.manage.remove.confirm.trash=真的要刪除實例「%s」嗎? 你可以在系統的資源回收筒 (或垃圾桶) 中還原目錄「%s」來找回該實例。
|
||||
version.manage.remove.confirm.independent=由於該實例啟用了「(全域/實例特定) 遊戲設定 → 執行路徑 → 各實例獨立」設定,刪除該實例將導致該遊戲的存檔等資料一同被刪除!真的要刪除實例「%s」嗎?
|
||||
version.manage.remove.confirm.independent=由於該實例啟用了「(全域/實例特定) 遊戲設定 → 執行路徑 → 各實例獨立」設定,刪除該實例將導致該遊戲的世界等資料一同被刪除!真的要刪除實例「%s」嗎?
|
||||
version.manage.remove.failed=刪除實例失敗。可能檔案被占用。
|
||||
version.manage.remove_assets=刪除所有遊戲資源檔案
|
||||
version.manage.remove_libraries=刪除所有支援庫檔案
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=关于
|
||||
about.copyright=版权
|
||||
about.copyright.statement=版权所有 © 2025 huangyuhui
|
||||
about.copyright.statement=版权所有 © 2026 huangyuhui
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=用户协议
|
||||
@@ -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你可以点击右上角帮助按钮进行求助。
|
||||
@@ -103,42 +104,28 @@ account.methods=登录方式
|
||||
account.methods.authlib_injector=外置登录
|
||||
account.methods.microsoft=微软账户
|
||||
account.methods.microsoft.birth=如何更改账户出生日期
|
||||
account.methods.microsoft.code=代码 (已自动复制)
|
||||
account.methods.microsoft.close_page=已完成微软账户授权。其余登录步骤将由启动器自动执行。你现在可以关闭本页面了。
|
||||
account.methods.microsoft.deauthorize=解除账户授权
|
||||
account.methods.microsoft.error.add_family=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.add_family_probably=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.add_family=请点击 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此处</a> 更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。
|
||||
account.methods.microsoft.error.missing_xbox_account=请点击上方“创建档案”关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案。请点击上方“创建档案”以创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\n你可以点击上方“检测账户是否被封禁”查看账户状态。
|
||||
account.methods.microsoft.error.missing_xbox_account=请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案。请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\n你可以点击 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此处</a> 按钮查询账户封禁状态。
|
||||
account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.wrong_verify_method=请在微软账户登录页面使用密码登录,不要使用验证码登录。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.wrong_verify_method=登录失败。请在微软账户登录页面使用密码登录,不要使用其他登录方式。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.logging_in=登录中……
|
||||
account.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称
|
||||
account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\
|
||||
\ 1. 点击“登录”按钮;\n\
|
||||
\ 2. 在弹出的网页中输入 HMCL 显示的代码,并点击“允许访问”;\n\
|
||||
\ 3. 按照网站的提示登录;\n\
|
||||
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
|
||||
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
|
||||
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
|
||||
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载。请使用网络代理并重试。</b>\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
|
||||
\ 1. 点击“登录”按钮;\n\
|
||||
\ 2. 在弹出的网页中输入 <b>%1$s</b> (已自动复制),并点击“允许访问”;\n\
|
||||
\ 3. 按照网站的提示登录;\n\
|
||||
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
|
||||
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
|
||||
若网站未能显示,请手动在浏览器中打开:%2$s\n\
|
||||
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
|
||||
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载。请使用网络代理并重试。</b>\n\
|
||||
account.methods.microsoft.hint=点击“登录”按钮开始添加微软账户。
|
||||
account.methods.microsoft.manual=请在弹出的网页中输入上方显示的代码以完成登录。\n\
|
||||
若网站未能显示,请手动在浏览器中打开:%s\n\
|
||||
<b>若网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.profile=编辑账户个人信息
|
||||
account.methods.microsoft.purchase=购买 Minecraft
|
||||
account.methods.forgot_password=忘记密码
|
||||
account.methods.ban_query=检测账户是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载官方版本来登录微软账户。
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载 <a href="https://hmcl.huangyuhui.net/download">官方版本</a> 来登录微软账户。
|
||||
account.methods.microsoft.snapshot.website=官方网站
|
||||
account.methods.offline=离线模式
|
||||
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名,且长度不超过 16 个字符
|
||||
@@ -197,6 +184,7 @@ button.export=导出
|
||||
button.no=否
|
||||
button.ok=确定
|
||||
button.ok.countdown=确定 (%d)
|
||||
button.reset=重置
|
||||
button.reveal_dir=打开文件夹
|
||||
button.refresh=刷新
|
||||
button.remove=删除
|
||||
@@ -424,7 +412,7 @@ folder.logs=日志文件夹
|
||||
folder.mod=模组文件夹
|
||||
folder.resourcepacks=资源包文件夹
|
||||
folder.shaderpacks=光影包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.saves=世界文件夹
|
||||
folder.schematics=原理图文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
folder.world=世界文件夹
|
||||
@@ -434,14 +422,14 @@ game.crash.feedback=<b>请不要将本界面截图或拍照给他人!</b>如
|
||||
game.crash.info=游戏信息
|
||||
game.crash.reason=崩溃原因
|
||||
game.crash.reason.analyzing=分析中……
|
||||
game.crash.reason.block=当前游戏由于某个方块不能正常工作,无法继续运行。\n你可以尝试使用 <a href="https://www.mcedit.net">MCEdit</a> 工具编辑存档删除该方块,或者直接删除对应的模组。\n方块类型:%1$s\n方块坐标:%2$s
|
||||
game.crash.reason.block=当前游戏由于某个方块不能正常工作,无法继续运行。\n你可以尝试使用 <a href="https://www.mcedit.net">MCEdit</a> 工具编辑世界删除该方块,或者直接删除对应的模组。\n方块类型:%1$s\n方块坐标:%2$s
|
||||
game.crash.reason.bootstrap_failed=当前游戏由于模组“%1$s”出现问题,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.mixin_apply_mod_failed=当前游戏由于 Mixin 无法应用于“%1$s”模组,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.config=当前游戏由于无法解析模组配置文件,无法继续运行。\n无法解析模组“%1$s”的配置文件“%2$s”。
|
||||
game.crash.reason.multiple=检测到多个原因:\n\n
|
||||
game.crash.reason.debug_crash=当前游戏由于手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的!
|
||||
game.crash.reason.duplicated_mod=当前游戏由于模组“%1$s”重复安装,无法继续运行。\n%2$s\n每种模组只能安装一个,请你删除多余的模组再试。
|
||||
game.crash.reason.entity=当前游戏由于某个实体不能正常工作,无法继续运行。\n你可以尝试使用 <a href="https://www.mcedit.net">MCEdit</a> 工具编辑存档删除该实体,或者直接删除对应的模组。\n实体类型:%1$s\n实体坐标:%2$s
|
||||
game.crash.reason.entity=当前游戏由于某个实体不能正常工作,无法继续运行。\n你可以尝试使用 <a href="https://www.mcedit.net">MCEdit</a> 工具编辑世界删除该实体,或者直接删除对应的模组。\n实体类型:%1$s\n实体坐标:%2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本与当前已经安装的模组可能不兼容,你需要将 Fabric Loader 降级至 0.11.7。
|
||||
game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s
|
||||
game.crash.reason.file_already_exists=当前游戏由于文件“%1$s”已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。
|
||||
@@ -720,7 +708,7 @@ modpack.files.mods.voxelmods=VoxelMods 配置,如小地图
|
||||
modpack.files.options_txt=游戏设置
|
||||
modpack.files.optionsshaders_txt=光影设置
|
||||
modpack.files.resourcepacks=资源包 (纹理包)
|
||||
modpack.files.saves=游戏存档
|
||||
modpack.files.saves=游戏世界
|
||||
modpack.files.scripts=MineTweaker 配置
|
||||
modpack.files.servers_dat=多人游戏服务器列表
|
||||
modpack.installing=安装整合包
|
||||
@@ -948,9 +936,9 @@ world.export.wizard=导出世界“%s”
|
||||
world.extension=世界压缩包
|
||||
world.game_version=游戏版本
|
||||
world.import.already_exists=此世界已经存在
|
||||
world.import.choose=选择要导入的存档压缩包
|
||||
world.import.choose=选择要导入的世界压缩包
|
||||
world.import.failed=无法导入此世界:%s
|
||||
world.import.invalid=无法识别该存档压缩包
|
||||
world.import.invalid=无法识别该世界压缩包
|
||||
world.info=世界信息
|
||||
world.info.basic=基本信息
|
||||
world.info.allow_cheats=允许命令(作弊)
|
||||
@@ -1148,9 +1136,9 @@ settings.game.java_directory.invalid=Java 路径不正确
|
||||
settings.game.java_directory.version=指定 Java 版本
|
||||
settings.game.java_directory.template=%s (%s)
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=版本隔离 (建议使用模组时选择“各实例独立”。改后需移动存档、模组等相关游戏文件)
|
||||
settings.game.working_directory=版本隔离 (建议使用模组时选择“各实例独立”。改后需移动世界、模组等相关游戏文件)
|
||||
settings.game.working_directory.choose=选择运行文件夹
|
||||
settings.game.working_directory.hint=在“版本隔离”中选择“各实例独立”使当前实例独立存放设置、存档、模组等数据。使用模组时建议启用此选项以避免不同实例模组冲突。修改此选项后需自行移动存档等文件。
|
||||
settings.game.working_directory.hint=在“版本隔离”中选择“各实例独立”使当前实例独立存放设置、世界、模组等数据。使用模组时建议启用此选项以避免不同实例模组冲突。修改此选项后需自行移动世界等文件。
|
||||
|
||||
settings.icon=游戏图标
|
||||
|
||||
@@ -1171,12 +1159,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 +1183,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 +1195,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=输出调试日志
|
||||
@@ -1221,6 +1209,8 @@ settings.type.special.enable=启用实例特定游戏设置 (不影响其他游
|
||||
settings.type.special.edit=编辑实例特定游戏设置
|
||||
settings.type.special.edit.hint=当前游戏实例“%s”启用了“实例特定游戏设置”,因此本页面选项不对该实例生效。点击链接前往该实例的“游戏设置”页。
|
||||
|
||||
shaderpack.download.title=光影下载 - %1s
|
||||
|
||||
sponsor=赞助
|
||||
sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好地提供稳定高速的下载服务。[点击此处查阅详细信息]
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅详细信息]
|
||||
@@ -1266,7 +1256,7 @@ terracotta.status.waiting.guest.desc=输入房主提供的邀请码加入游戏
|
||||
terracotta.status.waiting.guest.prompt.title=请输入房主提供的邀请码
|
||||
terracotta.status.waiting.guest.prompt.invalid=邀请码错误
|
||||
terracotta.status.scanning=正在扫描局域网世界
|
||||
terracotta.status.scanning.desc=请<a href="hmcl://game/launch">启动游戏</a>,进入单人存档,按下 ESC 键,选择对局域网开放,点击创建局域网世界。
|
||||
terracotta.status.scanning.desc=请<a href="hmcl://game/launch">启动游戏</a>,进入单人世界,按下 ESC 键,选择对局域网开放,点击创建局域网世界。
|
||||
terracotta.status.scanning.back=这将同时停止扫描局域网世界。
|
||||
terracotta.status.host_starting=正在启动房间
|
||||
terracotta.status.host_starting.back=这将会取消创建房间。
|
||||
@@ -1287,7 +1277,7 @@ terracotta.status.exception.desc.ping_host_fail=加入房间失败:房间已
|
||||
terracotta.status.exception.desc.ping_host_rst=房间连接断开:房间已关闭或网络不稳定
|
||||
terracotta.status.exception.desc.guest_et_crash=加入房间失败:EasyTier 已崩溃,请向开发者反馈该问题
|
||||
terracotta.status.exception.desc.host_et_crash=创建房间失败:EasyTier 已崩溃,请向开发者反馈该问题
|
||||
terracotta.status.exception.desc.ping_server_rst=房间已关闭:您已退出游戏存档,房间已自动关闭
|
||||
terracotta.status.exception.desc.ping_server_rst=房间已关闭:您已退出游戏世界,房间已自动关闭
|
||||
terracotta.status.exception.desc.scaffolding_invalid_response=协议错误:房主发送了错误的响应数据,请向开发者反馈该问题
|
||||
terracotta.status.fatal.retry=重试
|
||||
terracotta.status.fatal.network=未能下载联机核心。请检查网络连接,然后再试一次
|
||||
@@ -1367,7 +1357,7 @@ version.manage=实例列表
|
||||
version.manage.clean=清理游戏文件夹
|
||||
version.manage.clean.tooltip=清理“logs”和“crash-reports”文件夹
|
||||
version.manage.duplicate=复制游戏实例
|
||||
version.manage.duplicate.duplicate_save=复制存档
|
||||
version.manage.duplicate.duplicate_save=复制世界
|
||||
version.manage.duplicate.prompt=请输入新游戏名称
|
||||
version.manage.duplicate.confirm=新的游戏实例将复制该实例文件夹 (".minecraft/versions/<实例名>") 下的文件,并带有独立的运行文件夹和设置。
|
||||
version.manage.manage=实例管理
|
||||
@@ -1375,7 +1365,7 @@ version.manage.manage.title=实例管理 - %1s
|
||||
version.manage.redownload_assets_index=更新游戏资源文件
|
||||
version.manage.remove=删除该实例
|
||||
version.manage.remove.confirm.trash=真的要删除实例“%s”吗?你可以在系统的回收站中还原“%s”文件夹来找回该实例。
|
||||
version.manage.remove.confirm.independent=由于该游戏启用了“(全局/实例特定) 游戏设置 → 版本隔离 → 各实例独立”选项,删除该实例将导致该游戏的存档等数据一同被删除!真的要删除实例“%s”吗?
|
||||
version.manage.remove.confirm.independent=由于该游戏启用了“(全局/实例特定) 游戏设置 → 版本隔离 → 各实例独立”选项,删除该实例将导致该游戏的世界等数据一同被删除!真的要删除实例“%s”吗?
|
||||
version.manage.remove.failed=删除实例失败。可能文件被占用。
|
||||
version.manage.remove_assets=删除所有游戏资源文件
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -64,11 +65,13 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
|
||||
NetworkUtils.withQuery(downloadProvider.getApiRoot() + "/liteloader/list", Map.of(
|
||||
"mcversion", gameVersion
|
||||
)))
|
||||
.thenGetJsonAsync(LiteLoaderBMCLVersion.class)
|
||||
.thenApplyAsync(json -> JsonUtils.fromMaybeMalformedJson(json, LiteLoaderBMCLVersion.class))
|
||||
.thenAcceptAsync(v -> {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
versions.clear();
|
||||
if (v == null)
|
||||
return;
|
||||
versions.put(gameVersion, new LiteLoaderRemoteVersion(
|
||||
gameVersion, v.version, RemoteVersion.Type.UNCATEGORIZED,
|
||||
Collections.singletonList(NetworkUtils.withQuery(
|
||||
|
||||
@@ -63,7 +63,13 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
|
||||
if (majorVersion == 0) { // Snapshot version.
|
||||
mcVersion = version.substring(si1 + 1, si2);
|
||||
} else {
|
||||
mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
|
||||
String ver = version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
|
||||
if (majorVersion >= 26) {
|
||||
int separator = version.indexOf('+');
|
||||
mcVersion = separator < 0 ? ver : ver + "-" + version.substring(separator + 1);
|
||||
} else {
|
||||
mcVersion = "1." + ver;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warning(String.format("Cannot parse NeoForge version %s for cracking its mc version.", version), e);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class NeoForgeRemoteVersion extends RemoteVersion {
|
||||
}
|
||||
|
||||
private static Type getType(String version) {
|
||||
return version.contains("beta") ? Type.SNAPSHOT : Type.RELEASE;
|
||||
return version.contains("beta") || version.contains("alpha") ? Type.SNAPSHOT : Type.RELEASE;
|
||||
}
|
||||
|
||||
public static String normalize(String version) {
|
||||
|
||||
@@ -114,7 +114,7 @@ public abstract class Modpack {
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name);
|
||||
public abstract Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl);
|
||||
|
||||
public static boolean acceptFile(String path, List<String> blackList, List<String> whiteList) {
|
||||
if (path.isEmpty())
|
||||
|
||||
@@ -32,6 +32,7 @@ public interface RemoteModRepository {
|
||||
MOD,
|
||||
MODPACK,
|
||||
RESOURCE_PACK,
|
||||
SHADER_PACK,
|
||||
WORLD,
|
||||
CUSTOMIZATION
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public final class CurseModpackProvider implements ModpackProvider {
|
||||
|
||||
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(), description, encoding, manifest) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -424,7 +424,7 @@ public class McbbsModpackManifest implements ModpackManifest, Validation {
|
||||
.orElseThrow(() -> new IOException("Cannot find game version")).getVersion();
|
||||
return new Modpack(name, author, version, gameVersion, description, encoding, this) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new McbbsModpackLocalInstallTask(dependencyManager, zipFile, this, McbbsModpackManifest.this, name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -277,7 +277,12 @@ public final class ForgeNewModMetadata {
|
||||
}
|
||||
|
||||
private static ModLoaderType analyzeLoader(Toml toml, String modID, ModLoaderType loader) throws IOException {
|
||||
List<HashMap<String, Object>> dependencies = toml.getList("dependencies." + modID);
|
||||
List<HashMap<String, Object>> dependencies = null;
|
||||
try {
|
||||
dependencies = toml.getList("dependencies." + modID);
|
||||
} catch (ClassCastException ignored) { // https://github.com/HMCL-dev/HMCL/issues/5068
|
||||
}
|
||||
|
||||
if (dependencies == null) {
|
||||
try {
|
||||
dependencies = toml.getList("dependencies"); // ??? I have no idea why some of the Forge mods use [[dependencies]]
|
||||
|
||||
@@ -22,15 +22,23 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.CacheFileTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModrinthInstallTask extends Task<Void> {
|
||||
private static final Set<String> SUPPORTED_ICON_EXTS = Set.of("png", "jpg", "jpeg", "bmp", "gif", "webp", "apng");
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DefaultGameRepository repository;
|
||||
@@ -38,17 +46,21 @@ public class ModrinthInstallTask extends Task<Void> {
|
||||
private final Modpack modpack;
|
||||
private final ModrinthManifest manifest;
|
||||
private final String name;
|
||||
private final String iconUrl;
|
||||
private final Path run;
|
||||
private final ModpackConfiguration<ModrinthManifest> config;
|
||||
private String iconExt;
|
||||
private Task<Path> downloadIconTask;
|
||||
private final List<Task<?>> dependents = new ArrayList<>(4);
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
|
||||
public ModrinthInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, ModrinthManifest manifest, String name) {
|
||||
public ModrinthInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, ModrinthManifest manifest, String name, String iconUrl) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.zipFile = zipFile;
|
||||
this.modpack = modpack;
|
||||
this.manifest = manifest;
|
||||
this.name = name;
|
||||
this.iconUrl = iconUrl;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.run = repository.getRunDirectory(name);
|
||||
|
||||
@@ -104,6 +116,14 @@ public class ModrinthInstallTask extends Task<Void> {
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), subDirectories, any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), subDirectories, manifest, ModrinthModpackProvider.INSTANCE, manifest.getName(), manifest.getVersionId(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
|
||||
URI iconUri = NetworkUtils.toURIOrNull(iconUrl);
|
||||
if (iconUri != null) {
|
||||
String ext = FileUtils.getExtension(StringUtils.substringAfter(iconUri.getPath(), '/')).toLowerCase(Locale.ROOT);
|
||||
if (SUPPORTED_ICON_EXTS.contains(ext)) {
|
||||
iconExt = ext;
|
||||
dependents.add(downloadIconTask = new CacheFileTask(iconUrl));
|
||||
}
|
||||
}
|
||||
dependencies.add(new ModrinthCompletionTask(dependencyManager, name, manifest));
|
||||
}
|
||||
|
||||
@@ -133,5 +153,13 @@ public class ModrinthInstallTask extends Task<Void> {
|
||||
Path root = repository.getVersionRoot(name);
|
||||
Files.createDirectories(root);
|
||||
JsonUtils.writeToJsonFile(root.resolve("modrinth.index.json"), manifest);
|
||||
|
||||
if (iconExt != null) {
|
||||
try {
|
||||
Files.copy(downloadIconTask.getResult(), root.resolve("icon." + iconExt));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to copy modpack icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public final class ModrinthModpackProvider implements ModpackProvider {
|
||||
if (!(modpack.getManifest() instanceof ModrinthManifest modrinthManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new ModrinthInstallTask(dependencyManager, zipFile, modpack, modrinthManifest, name));
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new ModrinthInstallTask(dependencyManager, zipFile, modpack, modrinthManifest, name, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,8 +58,8 @@ public final class ModrinthModpackProvider implements ModpackProvider {
|
||||
ModrinthManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, "modrinth.index.json"), ModrinthManifest.class);
|
||||
return new Modpack(manifest.getName(), "", manifest.getVersionId(), manifest.getGameVersion(), manifest.getSummary(), encoding, manifest) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
return new ModrinthInstallTask(dependencyManager, zipFile, this, manifest, name);
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new ModrinthInstallTask(dependencyManager, zipFile, this, manifest, name, iconUrl);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public final class MultiMCModpackProvider implements ModpackProvider {
|
||||
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, instanceStream, manifest);
|
||||
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), encoding, cfg) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new MultiMCModpackInstallTask(dependencyManager, zipFile, this, cfg, name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -126,7 +126,7 @@ public class ServerModpackManifest implements ModpackManifest, Validation {
|
||||
.orElseThrow(() -> new IOException("Cannot find game version")).getVersion();
|
||||
return new Modpack(name, author, version, gameVersion, description, encoding, this) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new ServerModpackLocalInstallTask(dependencyManager, zipFile, this, ServerModpackManifest.this, name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class MurmurHash2 {
|
||||
// body
|
||||
for (int i = 0; i < nblocks; i++) {
|
||||
final int index = (i << 2);
|
||||
int k = getLittleEndianInt(data, index);
|
||||
int k = ByteArray.getIntLE(data, index);
|
||||
k *= M32;
|
||||
k ^= k >>> R32;
|
||||
k *= M32;
|
||||
@@ -191,7 +191,7 @@ public final class MurmurHash2 {
|
||||
// body
|
||||
for (int i = 0; i < nblocks; i++) {
|
||||
final int index = (i << 3);
|
||||
long k = getLittleEndianLong(data, index);
|
||||
long k = ByteArray.getLongLE(data, index);
|
||||
|
||||
k *= M64;
|
||||
k ^= k >>> R64;
|
||||
@@ -294,36 +294,4 @@ public final class MurmurHash2 {
|
||||
public static long hash64(final String text, final int from, final int length) {
|
||||
return hash64(text.substring(from, from + length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the little-endian int from 4 bytes starting at the specified index.
|
||||
*
|
||||
* @param data The data
|
||||
* @param index The index
|
||||
* @return The little-endian int
|
||||
*/
|
||||
private static int getLittleEndianInt(final byte[] data, final int index) {
|
||||
return ((data[index] & 0xff)) |
|
||||
((data[index + 1] & 0xff) << 8) |
|
||||
((data[index + 2] & 0xff) << 16) |
|
||||
((data[index + 3] & 0xff) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the little-endian long from 8 bytes starting at the specified index.
|
||||
*
|
||||
* @param data The data
|
||||
* @param index The index
|
||||
* @return The little-endian long
|
||||
*/
|
||||
private static long getLittleEndianLong(final byte[] data, final int index) {
|
||||
return (((long) data[index] & 0xff)) |
|
||||
(((long) data[index + 1] & 0xff) << 8) |
|
||||
(((long) data[index + 2] & 0xff) << 16) |
|
||||
(((long) data[index + 3] & 0xff) << 24) |
|
||||
(((long) data[index + 4] & 0xff) << 32) |
|
||||
(((long) data[index + 5] & 0xff) << 40) |
|
||||
(((long) data[index + 6] & 0xff) << 48) |
|
||||
(((long) data[index + 7] & 0xff) << 56);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||