Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef3c1b8926 | ||
|
|
ade057b819 | ||
|
|
2b1de479e5 | ||
|
|
5502a4efad | ||
|
|
5c36c00479 | ||
|
|
bb92b5ff8a | ||
|
|
8b90464d30 | ||
|
|
aaea473325 | ||
|
|
e8f9f093f4 | ||
|
|
9dc493dd02 | ||
|
|
5cf142df0e | ||
|
|
2a6cd17a3c | ||
|
|
285e361a5f | ||
|
|
0b70dc6d22 | ||
|
|
c8e2802373 | ||
|
|
9ac32a308a | ||
|
|
85cc11be1e | ||
|
|
568fa354ee | ||
|
|
2728be1014 | ||
|
|
388473850d | ||
|
|
1a5b19e7fe | ||
|
|
8d3fa105f7 | ||
|
|
ed43c07d88 | ||
|
|
586dee4f1b |
No files matched your search
@@ -27,7 +27,6 @@ jobs:
|
||||
run: ./gradlew build --no-daemon --parallel
|
||||
env:
|
||||
MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }}
|
||||
MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }}
|
||||
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||
- name: Get short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
@@ -128,7 +128,7 @@ public class JFXSpinner extends ProgressIndicator {
|
||||
|
||||
@Override
|
||||
public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) {
|
||||
return control.radius;
|
||||
return control.radiusProperty();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ public class JFXSpinner extends ProgressIndicator {
|
||||
|
||||
@Override
|
||||
public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) {
|
||||
return control.startingAngle;
|
||||
return control.startingAngleProperty();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
|
||||
@Override
|
||||
protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
|
||||
final double strokeWidth = arc.getStrokeWidth();
|
||||
final double radius = Math.min(contentWidth, contentHeight) / 2 - strokeWidth / 2;
|
||||
double radius = control.getRadius();
|
||||
final double arcSize = snapSizeX(radius * 2 + strokeWidth);
|
||||
|
||||
arcPane.resizeRelocate((contentWidth - arcSize) / 2 + 1, (contentHeight - arcSize) / 2 + 1, arcSize, arcSize);
|
||||
|
||||
@@ -37,7 +37,6 @@ import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
@@ -51,7 +50,6 @@ import javafx.scene.input.*;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
@@ -417,14 +415,6 @@ public final class FXUtils {
|
||||
return region.getMaxHeight();
|
||||
}
|
||||
|
||||
public static Node limitingSize(Node node, double width, double height) {
|
||||
StackPane pane = new StackPane(node);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(pane, width);
|
||||
FXUtils.setLimitHeight(pane, height);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void limitCellWidth(ListView<?> listView, ListCell<?> cell) {
|
||||
ReadOnlyDoubleProperty widthProperty;
|
||||
|
||||
@@ -1291,6 +1281,13 @@ public final class FXUtils {
|
||||
return button;
|
||||
}
|
||||
|
||||
public static JFXButton newToggleButton4(SVG icon, int size) {
|
||||
JFXButton button = new JFXButton();
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(icon.createIcon(size));
|
||||
return button;
|
||||
}
|
||||
|
||||
public static Label newSafeTruncatedLabel() {
|
||||
Label label = new Label();
|
||||
label.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);
|
||||
|
||||
@@ -28,16 +28,15 @@ import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -295,14 +294,14 @@ public class InstallerItem extends Control {
|
||||
paneWrapper.pseudoClassStateChanged(CARD, control.style == Style.CARD);
|
||||
|
||||
if (control.iconType != null) {
|
||||
ImageView view = new ImageView(control.iconType.getIcon());
|
||||
Node node = FXUtils.limitingSize(view, 32, 32);
|
||||
node.setMouseTransparent(true);
|
||||
node.getStyleClass().add("installer-item-image");
|
||||
pane.getChildren().add(node);
|
||||
var imageContainer = new ImageContainer(32);
|
||||
imageContainer.setImage(control.iconType.getIcon());
|
||||
imageContainer.setMouseTransparent(true);
|
||||
imageContainer.getStyleClass().add("installer-item-image");
|
||||
pane.getChildren().add(imageContainer);
|
||||
|
||||
if (control.style == Style.CARD) {
|
||||
VBox.setMargin(node, new Insets(8, 0, 16, 0));
|
||||
VBox.setMargin(imageContainer, new Insets(8, 0, 16, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,9 +347,7 @@ public class InstallerItem extends Control {
|
||||
buttonsContainer.setAlignment(Pos.CENTER);
|
||||
pane.getChildren().add(buttonsContainer);
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.setGraphic(SVG.CLOSE.createIcon());
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
JFXButton removeButton = FXUtils.newToggleButton4(SVG.CLOSE);
|
||||
if (control.id.equals(MINECRAFT.getPatchId())) {
|
||||
removeButton.setVisible(false);
|
||||
} else {
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
spinnerMove.setContent(btnMove);
|
||||
right.getChildren().add(spinnerMove);
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
JFXButton btnRefresh = FXUtils.newToggleButton4(SVG.REFRESH);
|
||||
SpinnerPane spinnerRefresh = new SpinnerPane();
|
||||
spinnerRefresh.getStyleClass().setAll("small-spinner-pane");
|
||||
if (skinnable.getAccount() instanceof MicrosoftAccount && Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
@@ -144,13 +144,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
})
|
||||
.start();
|
||||
});
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon());
|
||||
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
|
||||
spinnerRefresh.setContent(btnRefresh);
|
||||
right.getChildren().add(spinnerRefresh);
|
||||
|
||||
JFXButton btnUpload = new JFXButton();
|
||||
JFXButton btnUpload = FXUtils.newToggleButton4(SVG.CHECKROOM);
|
||||
SpinnerPane spinnerUpload = new SpinnerPane();
|
||||
btnUpload.setOnAction(e -> {
|
||||
Task<?> uploadTask = skinnable.uploadSkin();
|
||||
@@ -161,29 +159,23 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
.start();
|
||||
}
|
||||
});
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnUpload.setGraphic(SVG.CHECKROOM.createIcon());
|
||||
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
|
||||
btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));
|
||||
spinnerUpload.setContent(btnUpload);
|
||||
spinnerUpload.getStyleClass().add("small-spinner-pane");
|
||||
right.getChildren().add(spinnerUpload);
|
||||
|
||||
JFXButton btnCopyUUID = new JFXButton();
|
||||
JFXButton btnCopyUUID = FXUtils.newToggleButton4(SVG.CONTENT_COPY);
|
||||
SpinnerPane spinnerCopyUUID = new SpinnerPane();
|
||||
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
|
||||
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon());
|
||||
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
|
||||
spinnerCopyUUID.setContent(btnCopyUUID);
|
||||
right.getChildren().add(spinnerCopyUUID);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
JFXButton btnRemove = FXUtils.newToggleButton4(SVG.DELETE);
|
||||
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.DELETE.createIcon());
|
||||
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
@@ -399,9 +399,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
});
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
JFXButton btnAddServer = new JFXButton();
|
||||
btnAddServer.setGraphic(SVG.ADD.createIcon(20));
|
||||
btnAddServer.getStyleClass().add("toggle-icon4");
|
||||
JFXButton btnAddServer = FXUtils.newToggleButton4(SVG.ADD, 20);
|
||||
btnAddServer.setOnAction(e -> {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
});
|
||||
@@ -566,7 +564,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
}
|
||||
|
||||
private static class DialogCharacterSelector extends BorderPane implements CharacterSelector {
|
||||
public static class DialogCharacterSelector extends JFXDialogLayout implements CharacterSelector {
|
||||
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXButton cancel = new JFXButton();
|
||||
@@ -578,17 +576,17 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
setStyle("-fx-padding: 8px;");
|
||||
|
||||
cancel.setText(i18n("button.cancel"));
|
||||
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
|
||||
cancel.setOnAction(e -> latch.countDown());
|
||||
cancel.getStyleClass().add("dialog-cancel");
|
||||
|
||||
listBox.startCategory(i18n("account.choose").toUpperCase(Locale.ROOT));
|
||||
|
||||
setCenter(listBox);
|
||||
setBody(listBox);
|
||||
|
||||
HBox hbox = new HBox();
|
||||
hbox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hbox.getChildren().add(cancel);
|
||||
setBottom(hbox);
|
||||
setActions(hbox);
|
||||
|
||||
onEscPressed(this, cancel::fire);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 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.account;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@@ -175,16 +192,20 @@ public class MicrosoftAccountLoginPane extends JFXDialogLayout implements Dialog
|
||||
} else if (currentStep instanceof Step.WaitForScanQrCode wait) {
|
||||
loginButtonSpinner.setLoading(true);
|
||||
|
||||
String scanUri = "https://www.microsoft.com/link".equals(wait.verificationUri())
|
||||
? "https://www.microsoft.com/link?otc=" + wait.userCode()
|
||||
: wait.verificationUri();
|
||||
|
||||
var deviceHint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
deviceHint.setSegment(i18n("account.methods.microsoft.methods.device.hint",
|
||||
StringUtils.escapeXmlAttribute(wait.verificationUri()),
|
||||
StringUtils.escapeXmlAttribute(scanUri),
|
||||
wait.verificationUri(),
|
||||
wait.userCode()
|
||||
));
|
||||
|
||||
var qrCode = new SVGPath();
|
||||
qrCode.fillProperty().bind(Themes.colorSchemeProperty().getPrimary());
|
||||
qrCode.setContent(QrCodeUtils.toSVGPath(QrCode.encodeText(wait.verificationUri() + "?otc=" + wait.userCode(), QrCode.Ecc.MEDIUM)));
|
||||
qrCode.setContent(QrCodeUtils.toSVGPath(QrCode.encodeText(scanUri, QrCode.Ecc.MEDIUM)));
|
||||
qrCode.setScaleX(3);
|
||||
qrCode.setScaleY(3);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
@@ -85,13 +84,11 @@ public class AdvancedListItem extends Control {
|
||||
}
|
||||
|
||||
public void setRightAction(SVG icon, Runnable action) {
|
||||
var button = new JFXButton();
|
||||
var button = FXUtils.newToggleButton4(icon, 14);
|
||||
button.setOnAction(e -> {
|
||||
action.run();
|
||||
e.consume();
|
||||
});
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(icon.createIcon(14));
|
||||
setAlignment(button, Pos.CENTER);
|
||||
setRightGraphic(button);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.css.CssMetaData;
|
||||
import javafx.css.Styleable;
|
||||
import javafx.css.StyleableDoubleProperty;
|
||||
import javafx.css.StyleableProperty;
|
||||
import javafx.css.converter.SizeConverter;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/// A custom ImageView with fixed size and corner radius support.
|
||||
public class ImageContainer extends StackPane {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "image-container";
|
||||
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final Rectangle clip = new Rectangle();
|
||||
|
||||
public ImageContainer(double size) {
|
||||
this(size, size);
|
||||
}
|
||||
|
||||
public ImageContainer(double width, double height) {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
|
||||
FXUtils.setLimitWidth(this, width);
|
||||
FXUtils.setLimitHeight(this, height);
|
||||
|
||||
imageView.setPreserveRatio(true);
|
||||
FXUtils.limitSize(imageView, width, height);
|
||||
StackPane.setAlignment(imageView, Pos.CENTER);
|
||||
|
||||
clip.setWidth(width);
|
||||
clip.setHeight(height);
|
||||
updateCornerRadius(getCornerRadius());
|
||||
this.setClip(clip);
|
||||
|
||||
this.getChildren().setAll(imageView);
|
||||
}
|
||||
|
||||
private void updateCornerRadius(double radius) {
|
||||
clip.setArcWidth(radius);
|
||||
clip.setArcHeight(radius);
|
||||
}
|
||||
|
||||
private static final double DEFAULT_CORNER_RADIUS = 6.0;
|
||||
|
||||
private StyleableDoubleProperty cornerRadius;
|
||||
|
||||
public StyleableDoubleProperty cornerRadiusProperty() {
|
||||
if (this.cornerRadius == null) {
|
||||
cornerRadius = new StyleableDoubleProperty() {
|
||||
@Override
|
||||
public Object getBean() {
|
||||
return ImageContainer.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "cornerRadius";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CssMetaData<? extends Styleable, Number> getCssMetaData() {
|
||||
return StyleableProperties.CORNER_RADIUS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
updateCornerRadius(get());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return cornerRadius;
|
||||
}
|
||||
|
||||
public double getCornerRadius() {
|
||||
return cornerRadius == null ? DEFAULT_CORNER_RADIUS : cornerRadius.get();
|
||||
}
|
||||
|
||||
public void setCornerRadius(double radius) {
|
||||
cornerRadiusProperty().set(radius);
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return imageView.imageProperty();
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return imageView.getImage();
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
imageView.setImage(image);
|
||||
}
|
||||
|
||||
public BooleanProperty smoothProperty() {
|
||||
return imageView.smoothProperty();
|
||||
}
|
||||
|
||||
public boolean isSmooth() {
|
||||
return imageView.isSmooth();
|
||||
}
|
||||
|
||||
public void setSmooth(boolean smooth) {
|
||||
imageView.setSmooth(smooth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||
return StyleableProperties.STYLEABLES;
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
private static final CssMetaData<ImageContainer, Number> CORNER_RADIUS =
|
||||
new CssMetaData<>("-jfx-corner-radius", SizeConverter.getInstance(), DEFAULT_CORNER_RADIUS) {
|
||||
@Override
|
||||
public boolean isSettable(ImageContainer control) {
|
||||
return control.cornerRadius == null || !control.cornerRadius.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Number> getStyleableProperty(ImageContainer control) {
|
||||
return control.cornerRadiusProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||
|
||||
static {
|
||||
var styleables = new ArrayList<>(StackPane.getClassCssMetaData());
|
||||
styleables.add(CORNER_RADIUS);
|
||||
STYLEABLES = List.copyOf(styleables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -40,7 +39,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@DefaultProperty("image")
|
||||
public final class ImagePickerItem extends BorderPane {
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageContainer;
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
|
||||
@@ -48,25 +47,20 @@ public final class ImagePickerItem extends BorderPane {
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
|
||||
public ImagePickerItem() {
|
||||
imageView = new ImageView();
|
||||
imageView.setSmooth(false);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageContainer = new ImageContainer(32);
|
||||
imageContainer.setSmooth(false);
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.EDIT.createIcon(20));
|
||||
JFXButton selectButton = FXUtils.newToggleButton4(SVG.EDIT, 20);
|
||||
selectButton.onActionProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.RESTORE.createIcon(20));
|
||||
JFXButton deleteButton = FXUtils.newToggleButton4(SVG.RESTORE, 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);
|
||||
hBox.getChildren().setAll(imageContainer, selectButton, deleteButton);
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hBox.setSpacing(8);
|
||||
setRight(hBox);
|
||||
@@ -78,7 +72,7 @@ public final class ImagePickerItem extends BorderPane {
|
||||
vBox.setAlignment(Pos.CENTER_LEFT);
|
||||
setLeft(vBox);
|
||||
|
||||
imageView.imageProperty().bind(image);
|
||||
imageContainer.imageProperty().bind(image);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
@@ -128,8 +122,4 @@ public final class ImagePickerItem extends BorderPane {
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public ImageView getImageView() {
|
||||
return imageView;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
@@ -46,13 +47,10 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
public class Decorator extends Control {
|
||||
private final ListProperty<Node> drawer = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<DecoratorPage.State> state = new SimpleObjectProperty<>();
|
||||
private final StringProperty drawerTitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonAction = new SimpleObjectProperty<>();
|
||||
@@ -117,18 +115,6 @@ public class Decorator extends Control {
|
||||
this.drawerWrapper = drawerWrapper;
|
||||
}
|
||||
|
||||
public ObservableList<Node> getDrawer() {
|
||||
return drawer.get();
|
||||
}
|
||||
|
||||
public ListProperty<Node> drawerProperty() {
|
||||
return drawer;
|
||||
}
|
||||
|
||||
public void setDrawer(ObservableList<Node> drawer) {
|
||||
this.drawer.set(drawer);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContent() {
|
||||
return content.get();
|
||||
}
|
||||
@@ -153,30 +139,6 @@ public class Decorator extends Control {
|
||||
this.state.set(state);
|
||||
}
|
||||
|
||||
public String getDrawerTitle() {
|
||||
return drawerTitle.get();
|
||||
}
|
||||
|
||||
public StringProperty drawerTitleProperty() {
|
||||
return drawerTitle;
|
||||
}
|
||||
|
||||
public void setDrawerTitle(String drawerTitle) {
|
||||
this.drawerTitle.set(drawerTitle);
|
||||
}
|
||||
|
||||
public Runnable getOnCloseButtonAction() {
|
||||
return onCloseButtonAction.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
||||
return onCloseButtonAction;
|
||||
}
|
||||
|
||||
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
||||
this.onCloseButtonAction.set(onCloseButtonAction);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContainer() {
|
||||
return container.get();
|
||||
}
|
||||
@@ -301,7 +263,26 @@ public class Decorator extends Control {
|
||||
}
|
||||
|
||||
public void close() {
|
||||
onCloseButtonAction.get().run();
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(scaleYProperty(), 1, Motion.EASE),
|
||||
new KeyValue(scaleZProperty(), 0.3, Motion.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(scaleXProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(scaleYProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(scaleZProperty(), 0.8, Motion.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
}
|
||||
|
||||
public void capableDraggingWindow(Node node) {
|
||||
|
||||
@@ -20,9 +20,6 @@ package org.jackhuang.hmcl.ui.decorator;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import com.jfoenix.controls.JFXSnackbarLayout;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -37,7 +34,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
@@ -47,7 +43,6 @@ 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.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane.AnimationProducer;
|
||||
@@ -81,28 +76,6 @@ public class DecoratorController {
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 1, Motion.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.3, Motion.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(decorator.opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.8, Motion.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
});
|
||||
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
|
||||
|
||||
navigator = new Navigator();
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -50,11 +51,11 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return group.getGame().versionProperty().get().version();
|
||||
return ((RemoteVersion) controller.getSettings().get("game")).getGameVersion();
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return ((RemoteVersion) controller.getSettings().get(id)).getSelfVersion();
|
||||
return I18n.getDisplayVersion((RemoteVersion) controller.getSettings().get(id));
|
||||
}
|
||||
|
||||
protected void reload() {
|
||||
@@ -111,7 +112,7 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
}
|
||||
|
||||
private void setTxtNameWithLoaders() {
|
||||
StringBuilder nameBuilder = new StringBuilder(group.getGame().versionProperty().get().version());
|
||||
StringBuilder nameBuilder = new StringBuilder(getTitle());
|
||||
|
||||
for (InstallerItem library : group.getLibraries()) {
|
||||
String libraryId = library.getLibraryId().replace(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), "");
|
||||
@@ -120,23 +121,22 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
}
|
||||
|
||||
LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(libraryId);
|
||||
|
||||
if (libraryType != null) {
|
||||
String loaderName = switch (libraryType) {
|
||||
case FORGE -> i18n("install.installer.forge");
|
||||
case NEO_FORGE -> i18n("install.installer.neoforge");
|
||||
case CLEANROOM -> i18n("install.installer.cleanroom");
|
||||
case LEGACY_FABRIC -> i18n("install.installer.legacyfabric").replace(" ", "_");
|
||||
case FABRIC -> i18n("install.installer.fabric");
|
||||
case LITELOADER -> i18n("install.installer.liteloader");
|
||||
case QUILT -> i18n("install.installer.quilt");
|
||||
case OPTIFINE -> i18n("install.installer.optifine");
|
||||
case FORGE -> "forge";
|
||||
case NEO_FORGE -> "neoforge";
|
||||
case CLEANROOM -> "cleanroom";
|
||||
case LEGACY_FABRIC -> "legacy_fabric";
|
||||
case FABRIC -> "fabric";
|
||||
case LITELOADER -> "liteloader";
|
||||
case QUILT -> "quilt";
|
||||
case OPTIFINE -> "optifine";
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (loaderName == null)
|
||||
continue;
|
||||
|
||||
nameBuilder.append("-").append(loaderName);
|
||||
if (loaderName != null)
|
||||
nameBuilder.append('-').append(loaderName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private final Path file;
|
||||
private final String updateVersion;
|
||||
private String iconUrl;
|
||||
private boolean hasSource;
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile) {
|
||||
this(profile, null, null);
|
||||
@@ -79,6 +80,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
if (StringUtils.isNotBlank(iconUrl))
|
||||
settings.put(LocalModpackPage.MODPACK_ICON_URL, iconUrl);
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
hasSource = settings.containsKey(LocalModpackPage.MODPACK_FILE) || settings.containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
}
|
||||
|
||||
private Task<?> finishModpackInstallingAsync(SettingsMap settings) {
|
||||
@@ -156,7 +158,6 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
boolean hasSource = controller.getSettings().containsKey(LocalModpackPage.MODPACK_FILE) || settings.containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
if (hasSource) {
|
||||
return switch (step) {
|
||||
case 0 -> createModpackInstallPage(controller);
|
||||
|
||||
@@ -278,10 +278,12 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
iconType = VersionIconType.COMMAND;
|
||||
|
||||
imageView.setImage(iconType.getIcon());
|
||||
String displayGameVersion = I18n.getDisplayVersion(GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()));
|
||||
|
||||
if (twoLineListItem.getSubtitle() == null)
|
||||
twoLineListItem.setSubtitle(remoteVersion.getGameVersion());
|
||||
twoLineListItem.setSubtitle(displayGameVersion);
|
||||
else
|
||||
twoLineListItem.addTag(remoteVersion.getGameVersion());
|
||||
twoLineListItem.addTag(displayGameVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,9 +241,7 @@ public final class JavaManagementPage extends ListPageBase<JavaRuntime> {
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
revealButton.setOnAction(e -> {
|
||||
JavaRuntime java = getItem();
|
||||
if (java != null)
|
||||
|
||||
@@ -157,26 +157,20 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
revealButton.setOnAction(e -> skinnable.onReveal());
|
||||
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
|
||||
|
||||
if (skinnable.realPath == null) {
|
||||
revealButton.setDisable(true);
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
removeButton.setGraphic(SVG.DELETE.createIcon());
|
||||
JFXButton removeButton = FXUtils.newToggleButton4(SVG.DELETE);
|
||||
removeButton.setOnAction(e -> skinnable.onRemove());
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
|
||||
|
||||
right.getChildren().setAll(revealButton, removeButton);
|
||||
} else {
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
restoreButton.setOnAction(e -> skinnable.onRestore());
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));
|
||||
|
||||
|
||||
@@ -226,9 +226,7 @@ public class PersonalizationPage extends StackPane {
|
||||
.fallbackTo(12.0)
|
||||
.asPredicate(Validator.addTo(txtLogFontSize)));
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
clearButton.setOnAction(e -> cboLogFont.setValue(null));
|
||||
|
||||
FXUtils.installFastTooltip(clearButton, i18n("button.reset"));
|
||||
@@ -276,9 +274,7 @@ public class PersonalizationPage extends StackPane {
|
||||
cboFont.setValue(config().getLauncherFontFamily());
|
||||
FXUtils.onChange(cboFont.valueProperty(), FontManager::setFontFamily);
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
clearButton.setOnAction(e -> cboFont.setValue(null));
|
||||
|
||||
FXUtils.installFastTooltip(clearButton, i18n("button.reset"));
|
||||
|
||||
@@ -140,10 +140,8 @@ public final class SettingsPage extends ScrollPane {
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnUpdate = new JFXButton();
|
||||
JFXButton btnUpdate = FXUtils.newToggleButton4(SVG.UPDATE, 20);
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.UPDATE.createIcon(20));
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
|
||||
updateListener = any -> {
|
||||
|
||||
@@ -60,11 +60,9 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
JFXButton btnRemove = FXUtils.newToggleButton4(SVG.CLOSE, 14);
|
||||
btnRemove.setOnAction(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(14));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
@@ -50,10 +49,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
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.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -274,20 +270,20 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIcon(ImageView imageView, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
Image image = imageFuture.getNow(null);
|
||||
if (image != null) {
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||
this.iconCache = new SoftReference<>(imageFuture);
|
||||
}
|
||||
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<DatapackInfoObject> infoObjectProperty = current.get();
|
||||
@@ -296,7 +292,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
}, Schedulers.javafx());
|
||||
|
||||
}
|
||||
@@ -304,7 +300,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
private final class DatapackInfoListCell extends MDListCell<DatapackInfoObject> {
|
||||
final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageView imageView = new ImageView();
|
||||
ImageContainer imageContainer = new ImageContainer(32);
|
||||
final TwoLineListItem content = new TwoLineListItem();
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
@@ -320,13 +316,10 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
checkBox.disableProperty().bind(isReadOnlyProperty);
|
||||
|
||||
imageView.setFitWidth(32);
|
||||
imageView.setFitHeight(32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
|
||||
StackPane.setMargin(container, new Insets(8));
|
||||
container.getChildren().setAll(checkBox, imageView, content);
|
||||
container.getChildren().setAll(checkBox, imageContainer, content);
|
||||
getContainer().getChildren().setAll(container);
|
||||
|
||||
getContainer().getParent().addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEvent -> handleSelect(this, mouseEvent));
|
||||
@@ -341,7 +334,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.activeProperty);
|
||||
dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty()));
|
||||
dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
@@ -50,10 +49,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -537,7 +533,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private final StackPane wrapper = new StackPane();
|
||||
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final ImageContainer imageContainer = new ImageContainer(40);
|
||||
|
||||
{
|
||||
setPadding(PADDING);
|
||||
@@ -547,11 +543,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
container.setCursor(Cursor.HAND);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
imageView.setFitWidth(40);
|
||||
imageView.setFitHeight(40);
|
||||
imageView.setMouseTransparent(true);
|
||||
imageContainer.setMouseTransparent(true);
|
||||
|
||||
container.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
container.getChildren().setAll(imageContainer, content);
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
|
||||
this.graphic = new RipplerContainer(container);
|
||||
@@ -584,7 +578,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
if (getSkinnable().shouldDisplayCategory(category))
|
||||
content.addTag(getSkinnable().getLocalizedCategory(category));
|
||||
}
|
||||
iconLoader.load(imageView.imageProperty(), item.getIconUrl());
|
||||
iconLoader.load(imageContainer.imageProperty(), item.getIconUrl());
|
||||
setGraphic(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -225,25 +223,18 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
VBox pane = new VBox(8);
|
||||
pane.getStyleClass().add("gray-background");
|
||||
pane.setPadding(new Insets(10));
|
||||
ScrollPane scrollPane = new ScrollPane(pane);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
|
||||
HBox descriptionPane = new HBox(8);
|
||||
descriptionPane.setMinHeight(Region.USE_PREF_SIZE);
|
||||
descriptionPane.setAlignment(Pos.CENTER);
|
||||
pane.getChildren().add(descriptionPane);
|
||||
descriptionPane.getStyleClass().add("card-non-transparent");
|
||||
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
||||
{
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitWidth(40);
|
||||
imageView.setFitHeight(40);
|
||||
var imageContainer = new ImageContainer(40);
|
||||
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 80, 80, true, true));
|
||||
imageContainer.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 80, 80, true, true));
|
||||
}
|
||||
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
||||
descriptionPane.getChildren().add(imageContainer);
|
||||
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
@@ -286,8 +277,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
spinnerPane.setOnFailedAction(e -> getSkinnable().loadModVersions());
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
StackPane.setAlignment(list, Pos.TOP_CENTER);
|
||||
spinnerPane.setContent(list);
|
||||
ScrollPane scrollPane = new ScrollPane(list);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
FXUtils.setOverflowHidden(scrollPane, 8);
|
||||
StackPane.setAlignment(scrollPane, Pos.TOP_CENTER);
|
||||
spinnerPane.setContent(scrollPane);
|
||||
|
||||
FXUtils.onChangeAndOperate(control.loaded, loaded -> {
|
||||
if (control.versions == null) return;
|
||||
@@ -341,7 +337,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
});
|
||||
}
|
||||
|
||||
getChildren().setAll(scrollPane);
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,10 +358,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitWidth(40);
|
||||
imageView.setFitHeight(40);
|
||||
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
var imageView = new ImageContainer(40);
|
||||
pane.getChildren().setAll(imageView, content);
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
FXUtils.onClicked(container, () -> {
|
||||
@@ -493,6 +487,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
FXUtils.setOverflowHidden(scrollPane, 8);
|
||||
spinnerPane.setContent(scrollPane);
|
||||
box.getChildren().add(spinnerPane);
|
||||
VBox.setVgrow(spinnerPane, Priority.SOMETIMES);
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -27,32 +25,26 @@ import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageContainer;
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
private Profile profile;
|
||||
@SuppressWarnings("unused")
|
||||
private Consumer<Event> onVersionIconChangedListener;
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
public GameAdvancedListItem() {
|
||||
this.imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, LEFT_GRAPHIC_SIZE, LEFT_GRAPHIC_SIZE);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(null);
|
||||
|
||||
Node imageViewWrapper = FXUtils.limitingSize(imageView, LEFT_GRAPHIC_SIZE, LEFT_GRAPHIC_SIZE);
|
||||
imageView.setMouseTransparent(true);
|
||||
AdvancedListItem.setAlignment(imageViewWrapper, Pos.CENTER);
|
||||
setLeftGraphic(imageViewWrapper);
|
||||
this.imageContainer = new ImageContainer(LEFT_GRAPHIC_SIZE);
|
||||
imageContainer.setMouseTransparent(true);
|
||||
AdvancedListItem.setAlignment(imageContainer, Pos.CENTER);
|
||||
setLeftGraphic(imageContainer);
|
||||
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(), this::loadVersion));
|
||||
|
||||
}
|
||||
|
||||
private void loadVersion(String version) {
|
||||
@@ -68,11 +60,11 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
setTitle(i18n("version.manage.manage"));
|
||||
setSubtitle(version);
|
||||
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
imageContainer.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
} else {
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
imageView.setImage(VersionIconType.DEFAULT.getIcon());
|
||||
imageContainer.setImage(VersionIconType.DEFAULT.getIcon());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -42,7 +41,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
|
||||
private final Region graphic;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageView;
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private final JFXRadioButton chkSelected;
|
||||
@@ -87,8 +86,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
this.imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
this.imageView = new ImageContainer(32);
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
BorderPane.setAlignment(content, Pos.CENTER);
|
||||
@@ -108,30 +106,26 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
this.btnUpgrade = new JFXButton();
|
||||
this.btnUpgrade = FXUtils.newToggleButton4(SVG.UPDATE);
|
||||
btnUpgrade.setOnAction(e -> {
|
||||
GameListItem item = this.getItem();
|
||||
if (item != null)
|
||||
item.update();
|
||||
});
|
||||
btnUpgrade.getStyleClass().add("toggle-icon4");
|
||||
btnUpgrade.setGraphic(SVG.UPDATE.createIcon());
|
||||
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
|
||||
this.btnLaunch = new JFXButton();
|
||||
this.btnLaunch = FXUtils.newToggleButton4(SVG.ROCKET_LAUNCH);
|
||||
btnLaunch.setOnAction(e -> {
|
||||
GameListItem item = this.getItem();
|
||||
if (item != null)
|
||||
item.testGame();
|
||||
});
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
|
||||
btnLaunch.setGraphic(SVG.ROCKET_LAUNCH.createIcon());
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
|
||||
right.getChildren().add(btnLaunch);
|
||||
|
||||
this.btnManage = new JFXButton();
|
||||
this.btnManage = FXUtils.newToggleButton4(SVG.MORE_VERT);
|
||||
btnManage.setOnAction(e -> {
|
||||
GameListItem item = this.getItem();
|
||||
if (item == null)
|
||||
@@ -141,9 +135,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
||||
popup.show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight());
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(SVG.MORE_VERT.createIcon());
|
||||
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
|
||||
right.getChildren().add(btnManage);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -38,6 +37,7 @@ import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -87,7 +87,7 @@ public final class GameListPopupMenu extends StackPane {
|
||||
|
||||
private final Region graphic;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageView;
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private final StringProperty tag = new SimpleStringProperty();
|
||||
@@ -103,8 +103,7 @@ public final class GameListPopupMenu extends StackPane {
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
this.imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
this.imageView = new ImageContainer(32);
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -377,20 +376,20 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
return VersionIconType.getIconType(this.localModFile.getModLoaderType()).getIcon();
|
||||
}
|
||||
|
||||
public void loadIcon(ImageView imageView, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
Image image = imageFuture.getNow(null);
|
||||
if (image != null) {
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||
this.iconCache = new SoftReference<>(imageFuture);
|
||||
}
|
||||
imageView.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon());
|
||||
imageContainer.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon());
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<ModInfoObject> infoObjectProperty = current.get();
|
||||
@@ -400,7 +399,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
}, Schedulers.javafx());
|
||||
}
|
||||
}
|
||||
@@ -414,9 +413,8 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
Stage stage = Controllers.getStage();
|
||||
maxWidthProperty().bind(stage.widthProperty().multiply(0.7));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 40, 40);
|
||||
modInfo.loadIcon(imageView, null);
|
||||
var imageContainer = new ImageContainer(40);
|
||||
modInfo.loadIcon(imageContainer, null);
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
if (modInfo.getModTranslations() != null && I18n.isUseChinese())
|
||||
@@ -437,7 +435,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
title.setSubtitle(subtitle.toString());
|
||||
|
||||
titleContainer.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), title);
|
||||
titleContainer.getChildren().setAll(imageContainer, title);
|
||||
setHeading(titleContainer);
|
||||
|
||||
Label description = new Label(modInfo.getModInfo().getDescription().toString());
|
||||
@@ -554,11 +552,11 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
private static final PseudoClass WARNING = PseudoClass.getPseudoClass("warning");
|
||||
|
||||
JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageView imageView = new ImageView();
|
||||
ImageContainer imageContainer = new ImageContainer(24);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
JFXButton infoButton = new JFXButton();
|
||||
JFXButton revealButton = new JFXButton();
|
||||
JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
JFXButton infoButton = FXUtils.newToggleButton4(SVG.INFO);
|
||||
JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER);
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
Tooltip warningTooltip;
|
||||
@@ -575,23 +573,11 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
content.setMouseTransparent(true);
|
||||
setSelectable();
|
||||
|
||||
imageView.setFitWidth(24);
|
||||
imageView.setFitHeight(24);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
imageContainer.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
|
||||
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(SVG.FOLDER.createIcon());
|
||||
|
||||
infoButton.getStyleClass().add("toggle-icon4");
|
||||
infoButton.setGraphic(SVG.INFO.createIcon());
|
||||
|
||||
container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton);
|
||||
container.getChildren().setAll(checkBox, imageContainer, content, restoreButton, revealButton, infoButton);
|
||||
|
||||
StackPane.setMargin(container, new Insets(8));
|
||||
getContainer().getChildren().setAll(container);
|
||||
@@ -616,7 +602,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
|
||||
ModLoaderType modLoaderType = modInfo.getModLoaderType();
|
||||
|
||||
dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty()));
|
||||
dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));
|
||||
|
||||
String displayName = modInfo.getName();
|
||||
if (modTranslations != null && I18n.isUseChinese()) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
@@ -19,10 +18,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.MDListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -181,10 +177,10 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
|
||||
}
|
||||
|
||||
private static final class ResourcepackListCell extends MDListCell<ResourcepackInfoObject> {
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final ImageContainer imageView = new ImageContainer(32);
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final JFXButton btnReveal = new JFXButton();
|
||||
private final JFXButton btnDelete = new JFXButton();
|
||||
private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
private final JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
private final ResourcepackListPage page;
|
||||
|
||||
public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, ResourcepackListPage page) {
|
||||
@@ -198,7 +194,6 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
|
||||
|
||||
HBox left = new HBox(8);
|
||||
left.setAlignment(Pos.CENTER);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
left.getChildren().add(imageView);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
FXUtils.setLimitWidth(left, 48);
|
||||
@@ -207,12 +202,6 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
root.setCenter(content);
|
||||
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
|
||||
HBox right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
|
||||
@@ -25,7 +25,6 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
@@ -442,9 +441,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
if (image == null) {
|
||||
return super.getIcon(size);
|
||||
} else {
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitHeight(size);
|
||||
imageView.setFitWidth(size);
|
||||
var imageView = new ImageContainer(size);
|
||||
imageView.setImage(image);
|
||||
return imageView;
|
||||
}
|
||||
@@ -548,7 +545,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
private final TwoLineListItem center;
|
||||
private final HBox right;
|
||||
|
||||
private final ImageView iconImageView;
|
||||
private final ImageContainer iconImageView;
|
||||
private final SVGContainer iconSVGView;
|
||||
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
@@ -562,9 +559,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
this.left = new StackPane();
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
this.iconImageView = new ImageView();
|
||||
FXUtils.limitSize(iconImageView, 32, 32);
|
||||
|
||||
this.iconImageView = new ImageContainer(32);
|
||||
this.iconSVGView = new SVGContainer(32);
|
||||
|
||||
BorderPane.setAlignment(left, Pos.CENTER);
|
||||
@@ -580,19 +575,15 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
this.right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
btnReveal.setOnAction(event -> {
|
||||
Item item = getItem();
|
||||
if (item != null && !(item instanceof BackItem))
|
||||
item.onReveal();
|
||||
});
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
btnDelete.setOnAction(event -> {
|
||||
Item item = getItem();
|
||||
if (item != null && !(item instanceof BackItem)) {
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -61,6 +63,13 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
public static class WorkingDirChangedEvent extends Event {
|
||||
public static final EventType<WorkingDirChangedEvent> EVENT_TYPE = new EventType<>(Event.ANY, "WORKING_DIR_CHANGED");
|
||||
public WorkingDirChangedEvent() {
|
||||
super(EVENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public VersionPage() {
|
||||
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
@@ -74,6 +83,21 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
addEventHandler(WorkingDirChangedEvent.EVENT_TYPE, event -> {
|
||||
if (this.version.get() != null) {
|
||||
if (installerListTab.isInitialized())
|
||||
installerListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (modListTab.isInitialized())
|
||||
modListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (resourcePackTab.isInitialized())
|
||||
resourcePackTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (worldListTab.isInitialized())
|
||||
worldListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (schematicsTab.isInitialized())
|
||||
schematicsTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
}
|
||||
});
|
||||
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,12 @@ import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Screen;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ProcessPriority;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -48,7 +52,6 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
@@ -99,6 +102,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final InvalidationListener usesGlobalListener;
|
||||
private final ChangeListener<Boolean> specificSettingsListener;
|
||||
private final InvalidationListener javaListener = any -> initJavaSubtitle();
|
||||
|
||||
private final ChangeListener<GameDirectoryType> gameDirTypeListener = (ob, o, n) -> fireWorkingDirChanged();
|
||||
private final ChangeListener<String> gameDirListener = (ob, o, n) -> fireWorkingDirChanged();
|
||||
|
||||
private boolean updatingJavaSetting = false;
|
||||
private boolean updatingSelectedJava = false;
|
||||
|
||||
@@ -455,13 +462,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
else
|
||||
profile.getRepository().globalizeVersionSetting(versionId);
|
||||
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
FXUtils.runInFX(() -> {
|
||||
loadVersion(profile, versionId);
|
||||
fireWorkingDirChanged();
|
||||
});
|
||||
};
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
|
||||
|
||||
componentList.disableProperty().bind(enableSpecificSettings.not());
|
||||
}
|
||||
|
||||
private void fireWorkingDirChanged() {
|
||||
FXUtils.runInFX(() -> fireEvent(new VersionPage.WorkingDirChangedEvent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
@@ -513,6 +527,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
|
||||
lastVersionSetting.javaVersionProperty().removeListener(javaListener);
|
||||
|
||||
lastVersionSetting.gameDirTypeProperty().removeListener(gameDirTypeListener);
|
||||
lastVersionSetting.gameDirProperty().removeListener(gameDirListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirSublist.subtitleProperty().unbind();
|
||||
|
||||
@@ -589,6 +606,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> profile.getRepository().getRunDirectory(versionId).toAbsolutePath().normalize().toString(),
|
||||
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
|
||||
|
||||
versionSetting.gameDirTypeProperty().addListener(gameDirTypeListener);
|
||||
versionSetting.gameDirProperty().addListener(gameDirListener);
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initJavaSubtitle();
|
||||
@@ -727,7 +747,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
iconPickerItem.setImage(profile.getRepository().getVersionIconImage(versionId));
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
|
||||
private static List<String> getSupportedResolutions() {
|
||||
|
||||
@@ -26,7 +26,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -35,6 +34,7 @@ import org.jackhuang.hmcl.game.WorldLockedException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
@@ -232,9 +232,8 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
root.setLeft(left);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
var imageView = new ImageContainer(32);
|
||||
left.getChildren().add(imageView);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||
}
|
||||
|
||||
@@ -255,18 +254,14 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
right.getChildren().add(btnReveal);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
btnReveal.setOnAction(event -> skinnable.onReveal());
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE);
|
||||
right.getChildren().add(btnDelete);
|
||||
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE.createIcon());
|
||||
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.effect.BoxBlur;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -69,7 +68,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
private final World world;
|
||||
private CompoundTag levelDat;
|
||||
|
||||
ImageView iconImageView = new ImageView();
|
||||
private final ImageContainer iconImageView = new ImageContainer(32);
|
||||
|
||||
public WorldInfoPage(WorldManagePage worldManagePage) {
|
||||
this.worldManagePage = worldManagePage;
|
||||
@@ -137,14 +136,12 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
iconPane.setTitle(i18n("world.icon"));
|
||||
|
||||
{
|
||||
FXUtils.limitSize(iconImageView, 32, 32);
|
||||
iconImageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||
}
|
||||
|
||||
JFXButton editIconButton = new JFXButton();
|
||||
JFXButton resetIconButton = new JFXButton();
|
||||
JFXButton editIconButton = FXUtils.newToggleButton4(SVG.EDIT, 20);
|
||||
JFXButton resetIconButton = FXUtils.newToggleButton4(SVG.RESTORE, 20);
|
||||
{
|
||||
editIconButton.setGraphic(SVG.EDIT.createIcon(20));
|
||||
editIconButton.setDisable(isReadOnly);
|
||||
editIconButton.setOnAction(event -> Controllers.confirm(
|
||||
I18n.i18n("world.icon.change.tip"),
|
||||
@@ -154,13 +151,10 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
null
|
||||
));
|
||||
FXUtils.installFastTooltip(editIconButton, i18n("button.edit"));
|
||||
editIconButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
resetIconButton.setGraphic(SVG.RESTORE.createIcon(20));
|
||||
resetIconButton.setDisable(isReadOnly);
|
||||
resetIconButton.setOnAction(event -> this.clearWorldIcon());
|
||||
FXUtils.installFastTooltip(resetIconButton, i18n("button.reset"));
|
||||
resetIconButton.getStyleClass().add("toggle-icon4");
|
||||
}
|
||||
|
||||
HBox hBox = new HBox(8);
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -243,7 +242,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
private final WorldListPage page;
|
||||
|
||||
private final RipplerContainer graphic;
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageView;
|
||||
private final Tooltip leftTooltip;
|
||||
private final TwoLineListItem content;
|
||||
private final JFXButton btnLaunch;
|
||||
@@ -262,9 +261,8 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
root.setLeft(left);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
this.imageView = new ImageView();
|
||||
this.imageView = new ImageContainer(32);
|
||||
left.getChildren().add(imageView);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -278,12 +276,10 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
btnLaunch = new JFXButton();
|
||||
btnLaunch = FXUtils.newToggleButton4(SVG.ROCKET_LAUNCH);
|
||||
btnLaunch.visibleProperty().bind(page.supportQuickPlayProperty());
|
||||
btnLaunch.managedProperty().bind(btnLaunch.visibleProperty());
|
||||
right.getChildren().add(btnLaunch);
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
btnLaunch.setGraphic(SVG.ROCKET_LAUNCH.createIcon());
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch"));
|
||||
btnLaunch.setOnAction(event -> {
|
||||
World world = getItem();
|
||||
@@ -291,10 +287,8 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
page.launch(world);
|
||||
});
|
||||
|
||||
JFXButton btnMore = new JFXButton();
|
||||
JFXButton btnMore = FXUtils.newToggleButton4(SVG.MORE_VERT);
|
||||
right.getChildren().add(btnMore);
|
||||
btnMore.getStyleClass().add("toggle-icon4");
|
||||
btnMore.setGraphic(SVG.MORE_VERT.createIcon());
|
||||
btnMore.setOnAction(event -> {
|
||||
World world = getItem();
|
||||
if (world != null)
|
||||
|
||||
@@ -20,20 +20,12 @@ package org.jackhuang.hmcl.util;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.countly.CrashReport;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -103,9 +95,6 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
if (showCrashWindow) {
|
||||
new CrashWindow(report).show();
|
||||
}
|
||||
if (!UpdateChecker.isOutdated() && IntegrityChecker.isSelfVerified()) {
|
||||
reportToServer(report);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable handlingException) {
|
||||
@@ -115,22 +104,4 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
FileSaver.shutdown();
|
||||
LOG.shutdown();
|
||||
}
|
||||
|
||||
private void reportToServer(CrashReport crashReport) {
|
||||
Thread t = new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("crash_report", crashReport.getDisplayText());
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", LOG.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(URI.create(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
LOG.error("Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to post HMCL server.", ex);
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
about=About
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui
|
||||
about.copyright.statement=Copyright © 2013-2026 huangyuhui and contributors.
|
||||
about.author=Author
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
about=حول
|
||||
about.copyright=حقوق الطبع والنشر
|
||||
about.copyright.statement=حقوق النشر © 2026 huangyuhui
|
||||
about.author=المطور
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=اتفاقية الترخيص
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
about=Acerca de
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui.
|
||||
about.author=Autor
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=について
|
||||
about.copyright=著作権
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui
|
||||
about.author=著者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=有涉
|
||||
about.copyright=持權
|
||||
about.copyright.statement=© 2026 huangyuhui 持權
|
||||
about.author=作者
|
||||
about.author.statement=嗶站 @huanghongxun
|
||||
about.claim=客契
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=О лаунчере
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=Авторские права © 2026 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=Про програму
|
||||
about.copyright=Авторське право
|
||||
about.copyright.statement=Авторське право © 2026 huangyuhui
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=Ліцензійна угода з кінцевим користувачем (EULA)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=關於
|
||||
about.copyright=著作權
|
||||
about.copyright.statement=著作權所有 © 2026 huangyuhui
|
||||
about.copyright.statement=著作權所有 © 2013-2026 huangyuhui 與貢獻者
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=使用者協議
|
||||
@@ -30,7 +30,7 @@ about.legal=法律宣告
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載來源。請贊助支援 BMCLAPI!
|
||||
about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支援
|
||||
about.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本項目的貢獻者
|
||||
about.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本專案的貢獻者
|
||||
about.thanks_to.contributors.statement=沒有開源社群的支援,HMCL 無法走到今天
|
||||
about.thanks_to.gamerteam.statement=提供預設背景圖
|
||||
about.thanks_to.glavo.statement=負責 HMCL 的日常維護
|
||||
@@ -104,7 +104,7 @@ account.methods=登入方式
|
||||
account.methods.authlib_injector=外部登入
|
||||
account.methods.microsoft=Microsoft 帳戶
|
||||
account.methods.microsoft.birth=如何變更帳戶出生日期
|
||||
account.methods.microsoft.code=代碼 (已自動複製)
|
||||
account.methods.microsoft.code=程式碼 (已自動複製)
|
||||
account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權。啟動器將自動執行後續步驟。你現在可以關閉本頁面了。
|
||||
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 歲以上,或將帳戶加入到家庭中。
|
||||
@@ -479,7 +479,7 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)
|
||||
game.crash.reason.forge_repeat_installation=目前遊戲由於 Forge 重複安裝,無法繼續執行。<a href="https://github.com/HMCL-dev/HMCL/issues/1880">此為已知問題</a>\n建議將日誌上傳並回報至 GitHub,以便我們找到更多線索並修復此問題。\n目前你可以在「實例管理 → 自動安裝」中移除 Forge 並重新安裝。
|
||||
game.crash.reason.optifine_repeat_installation=目前遊戲由於重複安裝 OptiFine,無法繼續執行。\n請刪除模組目錄下的 OptiFine 或前往「實例管理 → 自動安裝」移除安裝的 OptiFine。
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=目前遊戲由於 OptiFine 與目前版本的 Forge 不相容,導致了遊戲崩潰。\n請前往 <a href="https://optifine.net/downloads">OptiFine 官網</a>查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲,或在「實例管理 → 自動安裝」中更換版本。\n經測試,Forge 版本過高或過低都可能導致崩潰。
|
||||
game.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題,無法繼續執行。\n你可以嘗試安裝 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 <a href="https://github.com/Fuzss/nightconfigfixes">GitHub 倉庫</a>。
|
||||
game.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題,無法繼續執行。\n你可以嘗試安裝 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 <a href="https://github.com/Fuzss/nightconfigfixes">GitHub 儲存庫</a>。
|
||||
game.crash.reason.mod_files_are_decompressed=目前遊戲由於模組檔案被解壓了,無法繼續執行。\n請直接把整個模組檔案放進模組目錄中即可。\n解壓模組檔案會導致遊戲出錯。請刪除模組目錄中已被解壓的模組,然後再啟動遊戲。
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=目前遊戲由於你所安裝的模組過多,超出了遊戲的 ID 限制,無法繼續執行。\n請嘗試安裝<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a>等修正模組,或刪除部分大型模組。
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=目前遊戲可能由於 OptiFine 而無法繼續執行。\n該問題只在特定 OptiFine 版本中出現,你可以嘗試在「實例管理 → 自動安裝」中更換 OptiFine 的版本。
|
||||
@@ -691,6 +691,7 @@ message.info=提示
|
||||
message.success=完成
|
||||
message.unknown=未知
|
||||
message.warning=警告
|
||||
message.question=確認
|
||||
|
||||
modpack=模組包
|
||||
modpack.choose=選取要安裝的遊戲模組包檔案
|
||||
@@ -862,7 +863,7 @@ mods.add.failed=新增模組「%s」失敗。
|
||||
mods.add.success=成功新增模組「%s」。
|
||||
mods.add.title=選取要新增的模組檔案
|
||||
mods.broken_dependency.title=損壞的相依模組
|
||||
mods.broken_dependency.desc=該相依模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源。
|
||||
mods.broken_dependency.desc=該相依模組曾經存在於模組儲存庫中,但現在已被刪除,請嘗試其他下載源。
|
||||
mods.category=類別
|
||||
mods.channel.alpha=Alpha
|
||||
mods.channel.beta=Beta
|
||||
@@ -884,7 +885,7 @@ mods.dependency.required=必需相依模組 (必須單獨下載,缺少可能
|
||||
mods.dependency.tool=相依庫 (必須單獨下載,缺少可能會導致遊戲無法啟動)
|
||||
mods.dependency.include=內建相依模組 (作者已經打包在模組檔中,無需單獨下載)
|
||||
mods.dependency.incompatible=不相容模組 (同時安裝此模組和正在下載的模組會導致遊戲無法啟動)
|
||||
mods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源)
|
||||
mods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組儲存庫中,但現在已被刪除,請嘗試其他下載源)
|
||||
mods.disable=停用
|
||||
mods.download=模組下載
|
||||
mods.download.title=模組下載 - %1s
|
||||
@@ -1030,9 +1031,9 @@ profile.title=遊戲目錄
|
||||
profile.selected=已選取
|
||||
profile.use_relative_path=如可行,則對於遊戲目錄使用相對路徑
|
||||
|
||||
repositories.custom=自訂 Maven 倉庫 (%s)
|
||||
repositories.custom=自訂 Maven 儲存庫 (%s)
|
||||
repositories.maven_central=全球 (Maven Central)
|
||||
repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 倉庫)
|
||||
repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 儲存庫)
|
||||
repositories.chooser=缺少 JavaFX 執行環境。HMCL 需要 JavaFX 才能正常執行。\n點擊「確認」從指定下載源下載 JavaFX 執行時元件並開啟 HMCL。點擊「取消」退出程式。\n選取下載源:
|
||||
repositories.chooser.title=選取 JavaFX 下載源
|
||||
|
||||
@@ -1280,7 +1281,7 @@ terracotta.difficulty.easiest=目前網路狀況極佳:稍等一下就成功
|
||||
terracotta.difficulty.simple=目前網路狀況較好:建立連線需要一段時間…
|
||||
terracotta.difficulty.medium=目前網路狀態中等:已啟用抗干擾備用線路,連線可能失敗
|
||||
terracotta.difficulty.tough=目前網路狀態極差:已啟用抗干擾備用線路,連線可能失敗
|
||||
terracotta.difficulty.estimate_only=連接成功率由房主和房客的 NAT 類型推算得到,僅供參考。
|
||||
terracotta.difficulty.estimate_only=連線成功率由房主和房客的 NAT 類型推算得到,僅供參考。
|
||||
terracotta.from_local.title=線上核心第三方下載管道
|
||||
terracotta.from_local.desc=在部分地區,內建的預設下載管道可能不穩定或連線緩慢
|
||||
terracotta.from_local.guide=您應下載名為 %s 的線上核心套件。下載完成後,請將檔案拖曳到目前介面來安裝。
|
||||
@@ -1339,7 +1340,7 @@ terracotta.status.fatal.network=未能下載線上核心。請檢查網路連接
|
||||
terracotta.status.fatal.install=嚴重錯誤:無法安裝線上核心
|
||||
terracotta.status.fatal.terracotta=嚴重錯誤:無法與線上核心通訊
|
||||
terracotta.status.fatal.unknown=嚴重錯誤:原因未知
|
||||
terracotta.player_list=玩家列表
|
||||
terracotta.player_list=玩家清單
|
||||
terracotta.player_anonymous=匿名玩家
|
||||
terracotta.player_kind.host=房主
|
||||
terracotta.player_kind.local=你
|
||||
@@ -1348,7 +1349,7 @@ terracotta.unsupported=多人聯機功能尚未支援目前平台。
|
||||
terracotta.unsupported.os.windows.old=多人聯機功能需要 Windows 10 或更高版本。請更新系統。
|
||||
terracotta.unsupported.arch.32bit=多人聯機功能不支援 32 位元系統。請更新至 64 位元系統。
|
||||
terracotta.unsupported.arch.loongarch64_ow=多人聯機功能不支援 Linux LoongArch64 舊世界發行版,請更新至新世界發行版 (如 AOSC OC)。
|
||||
terracotta.unsupported.region=多人聯機功能目前僅為中國內地用戶提供服務,在您所在地區可能不可用。
|
||||
terracotta.unsupported.region=多人聯機功能目前僅為中國內地使用者提供服務,在您所在地區可能不可用。
|
||||
|
||||
unofficial.hint=你正在使用第三方提供的 HMCL。我們無法保證其安全性,請注意甄別。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=关于
|
||||
about.copyright=版权
|
||||
about.copyright.statement=版权所有 © 2026 huangyuhui
|
||||
about.copyright.statement=版权所有 © 2013-2026 huangyuhui 及贡献者
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=用户协议
|
||||
@@ -696,6 +696,7 @@ message.info=提示
|
||||
message.success=完成
|
||||
message.unknown=未知
|
||||
message.warning=警告
|
||||
message.question=确认
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=选择要安装的游戏整合包文件
|
||||
|
||||
@@ -2661,6 +2661,34 @@
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl:3.4.1:natives-linux": {
|
||||
"name": "org.glavo.hmcl:lwjgl3-natives:3.4.1-rc1",
|
||||
"downloads": {
|
||||
"classifiers": {
|
||||
"linux-loongarch64": {
|
||||
"path": "org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1-linux-loongarch64/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
|
||||
"sha1": "3fed872fc2f9929d2be25389dd312dfb48e9f89e",
|
||||
"size": 22854580
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"exclude": [
|
||||
"META-INF/"
|
||||
]
|
||||
},
|
||||
"natives": {
|
||||
"linux": "linux-loongarch64"
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-openal:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux": null,
|
||||
"net.java.dev.jna:jna:5.8.0": {
|
||||
"name": "net.java.dev.jna:jna:5.13.0",
|
||||
"downloads": {
|
||||
|
||||
@@ -93,13 +93,12 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
pair("https://repo.maven.apache.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
|
||||
pair("https://hmcl.glavo.site/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"),
|
||||
pair("https://hmcl.glavo.site/metadata/fmllibs", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs"),
|
||||
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto")
|
||||
// // https://github.com/mcmod-info-mirror/mcim-rust-api
|
||||
// pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"),
|
||||
// pair("https://cdn.modrinth.com", "https://mod.mcimirror.top"),
|
||||
// pair("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"),
|
||||
// pair("https://edge.forgecdn.net", "https://mod.mcimirror.top"),
|
||||
// pair("https://mediafilez.forgecdn.net", "https://mod.mcimirror.top")
|
||||
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"),
|
||||
// https://github.com/mcmod-info-mirror/mcim-rust-api
|
||||
pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"),
|
||||
pair("https://cdn.modrinth.com", "https://mod.mcimirror.top"),
|
||||
pair("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"),
|
||||
pair("https://edge.forgecdn.net", "https://mod.mcimirror.top")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -382,14 +382,17 @@ public final class World {
|
||||
public static List<World> getWorlds(Path savesDir) {
|
||||
if (Files.exists(savesDir)) {
|
||||
try (Stream<Path> stream = Files.list(savesDir)) {
|
||||
return stream.flatMap(world -> {
|
||||
try {
|
||||
return Stream.of(new World(world.toAbsolutePath().normalize()));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read world " + world, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
}).toList();
|
||||
return stream
|
||||
.filter(Files::isDirectory)
|
||||
.flatMap(world -> {
|
||||
try {
|
||||
return Stream.of(new World(world.toAbsolutePath().normalize()));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read world " + world, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read saves", e);
|
||||
}
|
||||
|
||||
+30
-4
@@ -33,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -42,6 +43,7 @@ import java.util.stream.Stream;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class CurseForgeRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
@@ -131,10 +133,34 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
query.put("index", Integer.toString(pageOffset * pageSize));
|
||||
query.put("pageSize", Integer.toString(pageSize));
|
||||
|
||||
Response<List<CurseAddon>> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", query))))
|
||||
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
|
||||
if (searchFilter.isEmpty()) {
|
||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||
Response<List<CurseAddon>> response = null;
|
||||
|
||||
IOException exception = null;
|
||||
List<URI> candidates = downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", query));
|
||||
for (URI candidate : candidates) {
|
||||
LOG.info("Fetching " + candidate);
|
||||
try {
|
||||
response = withApiKey(HttpRequest.GET(candidate.toString()))
|
||||
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
|
||||
if (searchFilter.isEmpty()) {
|
||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||
}
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to search addons: " + candidate, e);
|
||||
if (candidates.size() == 1) {
|
||||
exception = e;
|
||||
} else {
|
||||
if (exception == null) {
|
||||
exception = new IOException("Failed to search addons");
|
||||
}
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
throw exception != null ? exception : new IOException("No candidates found");
|
||||
}
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/1549
|
||||
|
||||
+26
-3
@@ -37,6 +37,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
@@ -54,6 +55,7 @@ import java.util.stream.Stream;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository("mod");
|
||||
@@ -112,9 +114,30 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
pair("limit", Integer.toString(pageSize)),
|
||||
pair("index", convertSortType(sort))
|
||||
);
|
||||
Response<ProjectSearchResult> response = HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v2/search", query)))
|
||||
.getJson(Response.typeOf(ProjectSearchResult.class));
|
||||
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));
|
||||
|
||||
|
||||
List<URI> candidates = downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(PREFIX + "/v2/search", query));
|
||||
IOException exception = null;
|
||||
for (URI candidate : candidates) {
|
||||
try {
|
||||
LOG.info("Fetching " + candidate);
|
||||
Response<ProjectSearchResult> response = HttpRequest.GET(candidate.toString())
|
||||
.getJson(Response.typeOf(ProjectSearchResult.class));
|
||||
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to search addons: " + candidate, e);
|
||||
if (candidates.size() == 1) {
|
||||
exception = e;
|
||||
} else {
|
||||
if (exception == null) {
|
||||
exception = new IOException("Failed to search addons");
|
||||
}
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw exception != null ? exception : new IOException("No candidates found");
|
||||
} finally {
|
||||
SEMAPHORE.release();
|
||||
}
|
||||
|
||||
@@ -296,6 +296,11 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
exceptions.add(ex);
|
||||
|
||||
LOG.warning("Failed to download " + uri + ", repeat times: " + retryTime + (redirects == null ? "" : ", redirects: " + redirects), ex);
|
||||
|
||||
if (retryTime < retryLimit - 1) {
|
||||
// Wait for a while before retrying
|
||||
Thread.sleep(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,13 @@
|
||||
"time": "2025-12-09T12:43:15+00:00",
|
||||
"releaseTime": "2025-12-09T12:43:15+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1.21.11-rc3_unobfuscated",
|
||||
"type": "unobfuscated",
|
||||
"url": "https://piston-meta.mojang.com/v1/packages/ce3f7ac6d0e9d23ea4e5f0354b91ff15039d9931/1.21.11-rc3_unobfuscated.json",
|
||||
"time": "2025-12-08T13:59:34+00:00",
|
||||
"releaseTime": "2025-12-08T13:59:34+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1.21.11-rc2_unobfuscated",
|
||||
"type": "unobfuscated",
|
||||
|
||||
@@ -891,5 +891,7 @@
|
||||
1.21.11-rc1_unobfuscated
|
||||
1.21.11-rc2
|
||||
1.21.11-rc2_unobfuscated
|
||||
1.21.11-rc3
|
||||
1.21.11-rc3_unobfuscated
|
||||
1.21.11
|
||||
1.21.11_unobfuscated
|
||||
Vendored
-1
@@ -6,7 +6,6 @@ pipeline {
|
||||
|
||||
VERSION_TYPE = 'dev'
|
||||
MICROSOFT_AUTH_ID = credentials('microsoft_auth_id')
|
||||
MICROSOFT_AUTH_SECRET = credentials('microsoft_auth_secret')
|
||||
CURSEFORGE_API_KEY = credentials('curseforge_api_key')
|
||||
HMCL_SIGNATURE_KEY = credentials('hmcl_signature_key')
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -6,7 +6,6 @@ pipeline {
|
||||
|
||||
VERSION_TYPE = 'stable'
|
||||
MICROSOFT_AUTH_ID = credentials('microsoft_auth_id')
|
||||
MICROSOFT_AUTH_SECRET = credentials('microsoft_auth_secret')
|
||||
CURSEFORGE_API_KEY = credentials('curseforge_api_key')
|
||||
HMCL_SIGNATURE_KEY = credentials('hmcl_signature_key')
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ These options can be specified via environment variables or JVM parameters. If b
|
||||
| | `-Dhmcl.openjfx.repo=<maven repository url>` | Adds a custom Maven repository for downloading OpenJFX | | |
|
||||
| | `-Dhmcl.native.encoding=<encoding>` | Specifies the native encoding | Uses the system's native encoding | |
|
||||
| | `-Dhmcl.microsoft.auth.id=<App ID>` | Specifies the Microsoft OAuth App ID | Uses the built-in Microsoft OAuth App ID | |
|
||||
| | `-Dhmcl.microsoft.auth.secret=<App Secret>` | Specifies the Microsoft OAuth App Secret | Uses the built-in Microsoft OAuth App Secret | |
|
||||
| | `-Dhmcl.curseforge.apikey=<Api Key>` | Specifies the CurseForge API key | Uses the built-in CurseForge API key | |
|
||||
| | `-Dhmcl.native.backend=<auto/jna/none>` | Specifies the native backend used by HMCL | `auto` | |
|
||||
| | `-Dhmcl.hardware.fastfetch=<true/false>` | Specifies whether to use fastfetch for hardware detection | `true` | |
|
||||
@@ -99,7 +99,6 @@ HMCL 提供了一系列调试选项,用于控制启动器的行为。
|
||||
| | `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | | |
|
||||
| | `-Dhmcl.native.encoding=<encoding>` | 指定原生编码 | 使用系统的本机编码 | |
|
||||
| | `-Dhmcl.microsoft.auth.id=<App ID>` | 指定 Microsoft OAuth App ID | 使用 HMCL 内置的 Microsoft OAuth App ID | |
|
||||
| | `-Dhmcl.microsoft.auth.secret=<App Secret>` | 指定 Microsoft OAuth App 密钥 | 使用 HMCL 内置的 Microsoft OAuth App 密钥 | |
|
||||
| | `-Dhmcl.curseforge.apikey=<Api Key>` | 指定 CurseForge API 密钥 | 使用 HMCL 内置的 CurseForge API 密钥 | |
|
||||
| | `-Dhmcl.native.backend=<auto/jna/none>` | 指定HMCL使用的本机后端 | `auto` |
|
||||
| | `-Dhmcl.hardware.fastfetch=<true/false>` | 指定是否使用 fastfetch 检测硬件信息 | `true` |
|
||||
|
||||
@@ -99,7 +99,6 @@ HMCL 提供了一系列除錯選項,用於控制啟動器的行為。
|
||||
| | `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用於下載 OpenJFX 的自訂 Maven 倉庫 | | |
|
||||
| | `-Dhmcl.native.encoding=<encoding>` | 設定原生編碼 | 使用系統的本機編碼 | |
|
||||
| | `-Dhmcl.microsoft.auth.id=<App ID>` | 設定 Microsoft OAuth App ID | 使用 HMCL 內建的 Microsoft OAuth App ID | |
|
||||
| | `-Dhmcl.microsoft.auth.secret=<App Secret>` | 設定 Microsoft OAuth App 金鑰 | 使用 HMCL 內建的 Microsoft OAuth App 金鑰 | |
|
||||
| | `-Dhmcl.curseforge.apikey=<Api Key>` | 設定 CurseForge API 金鑰 | 使用 HMCL 內建的 CurseForge API 金鑰 | |
|
||||
| | `-Dhmcl.native.backend=<auto/jna/none>` | 設定 HMCL 使用的本機後端 | `auto` |
|
||||
| | `-Dhmcl.hardware.fastfetch=<true/false>` | 設定是否使用 fastfetch 檢測硬體資訊 | `true` |
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ Before contributing, please read the [Contributing Guide](./Contributing.md), wh
|
||||
|
||||
## Contributors
|
||||
|
||||
Since 2015, more than 110 contributors have participated in HMCL. Thank you for your hard work!
|
||||
Since 2015, more than 120 contributors have participated in HMCL. Thank you for your hard work!
|
||||
|
||||
[](https://github.com/HMCL-dev/HMCL/graphs/contributors)
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ HMCL 乃社群共驅之開源項目,迎諸君獻碼或建言。
|
||||
|
||||
## 貢獻者
|
||||
|
||||
自乙未年(2015)始,百十余人參與 HMCL,謝其勤勞!
|
||||
自乙未年(2015)始,百二十余人參與 HMCL,謝其勤勞!
|
||||
|
||||
[](https://github.com/HMCL-dev/HMCL/graphs/contributors)
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ HMCL 是一个社区驱动的开源项目,欢迎任何人参与贡献代码或
|
||||
|
||||
## 贡献者
|
||||
|
||||
自 2015 年以来,HMCL 已经有超过 110 位贡献者参与其中,感谢他们的辛勤付出!
|
||||
自 2015 年以来,HMCL 已经有超过 120 位贡献者参与其中,感谢他们的辛勤付出!
|
||||
|
||||
[](https://github.com/HMCL-dev/HMCL/graphs/contributors)
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ HMCL 是一個由社群驅動的開源專案,歡迎任何人參與貢獻程式
|
||||
|
||||
## 貢獻者
|
||||
|
||||
自 2015 年以來,HMCL 已經有超過 110 位貢獻者參與其中,感謝他們的辛勤付出!
|
||||
自 2015 年以來,HMCL 已經有超過 120 位貢獻者參與其中,感謝他們的辛勤付出!
|
||||
|
||||
[](https://github.com/HMCL-dev/HMCL/graphs/contributors)
|
||||
|
||||
|
||||
Reference in new issue
Block a user