feat: redesign UI of game management.
This commit is contained in:
@@ -208,6 +208,24 @@ public final class Profile implements Observable {
|
||||
Platform.runLater(observableHelper::invalidate);
|
||||
}
|
||||
|
||||
public static class ProfileVersion {
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
|
||||
public ProfileVersion(Profile profile, String version) {
|
||||
this.profile = profile;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
@Override
|
||||
public JsonElement serialize(Profile src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.java.JavaRepository;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
||||
@@ -40,13 +41,16 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.main.RootPage;
|
||||
import org.jackhuang.hmcl.ui.versions.GameListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
@@ -62,6 +66,7 @@ public final class Controllers {
|
||||
private static Scene scene;
|
||||
private static Stage stage;
|
||||
private static VersionPage versionPage = null;
|
||||
private static GameListPage gameListPage = null;
|
||||
private static AuthlibInjectorServersPage serversPage = null;
|
||||
private static RootPage rootPage;
|
||||
private static DecoratorController decorator;
|
||||
@@ -84,6 +89,20 @@ public final class Controllers {
|
||||
return versionPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static GameListPage getGameListPage() {
|
||||
if (gameListPage == null) {
|
||||
gameListPage = new GameListPage();
|
||||
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
}
|
||||
return gameListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static RootPage getRootPage() {
|
||||
if (rootPage == null)
|
||||
@@ -167,7 +186,11 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void confirm(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||
confirm(text, title, MessageType.QUESTION, onAccept, onCancel);
|
||||
}
|
||||
|
||||
public static void confirm(String text, String title, MessageType type, Runnable onAccept, Runnable onCancel) {
|
||||
dialog(new MessageDialogPane(text, title, type, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult) {
|
||||
|
||||
@@ -194,4 +194,20 @@ public final class SVG {
|
||||
public static Node hanger(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M12 4A3.5 3.5 0 0 0 8.5 7.5H10.5A1.5 1.5 0 0 1 12 6A1.5 1.5 0 0 1 13.5 7.5A1.5 1.5 0 0 1 12 9C11.45 9 11 9.45 11 10V11.75L2.4 18.2A1 1 0 0 0 3 20H21A1 1 0 0 0 21.6 18.2L13 11.75V10.85A3.5 3.5 0 0 0 15.5 7.5A3.5 3.5 0 0 0 12 4M12 13.5L18 18H6Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node puzzle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node cube(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node pack(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node gamepad(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M6,9H8V11H10V13H8V15H6V13H4V11H6V9M18.5,9A1.5,1.5 0 0,1 20,10.5A1.5,1.5 0 0,1 18.5,12A1.5,1.5 0 0,1 17,10.5A1.5,1.5 0 0,1 18.5,9M15.5,12A1.5,1.5 0 0,1 17,13.5A1.5,1.5 0 0,1 15.5,15A1.5,1.5 0 0,1 14,13.5A1.5,1.5 0 0,1 15.5,12M17,5A7,7 0 0,1 24,12A7,7 0 0,1 17,19C15.04,19 13.27,18.2 12,16.9C10.73,18.2 8.96,19 7,19A7,7 0 0,1 0,12A7,7 0 0,1 7,5H17M7,7A5,5 0 0,0 2,12A5,5 0 0,0 7,17C8.64,17 10.09,16.21 11,15H13C13.91,16.21 15.36,17 17,17A5,5 0 0,0 22,12A5,5 0 0,0 17,7H7Z", fill, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,28 @@
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
private final Tooltip tooltip;
|
||||
private final ImageView imageView;
|
||||
|
||||
private ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
|
||||
|
||||
@@ -47,23 +50,26 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
titleProperty().unbind();
|
||||
setTitle(i18n("account.missing"));
|
||||
setSubtitle(i18n("account.missing.add"));
|
||||
imageProperty().unbind();
|
||||
setImage(newImage("/assets/img/craft_table.png"));
|
||||
imageView.imageProperty().unbind();
|
||||
imageView.setImage(newImage("/assets/img/craft_table.png"));
|
||||
tooltip.setText("");
|
||||
} else {
|
||||
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
|
||||
setSubtitle(accountSubtitle(account));
|
||||
imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
|
||||
imageView.imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
|
||||
tooltip.setText(account.getCharacter() + " " + accountTooltip(account));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public AccountAdvancedListItem() {
|
||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
||||
tooltip = new Tooltip();
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
|
||||
Pair<Node, ImageView> view = createImageView(null);
|
||||
setLeftGraphic(view.getKey());
|
||||
imageView = view.getValue();
|
||||
|
||||
setOnScroll(event -> {
|
||||
Account current = account.get();
|
||||
if (current == null) return;
|
||||
|
||||
@@ -37,7 +37,6 @@ public class AdvancedListBox extends ScrollPane {
|
||||
setFitToWidth(true);
|
||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
|
||||
container.setSpacing(5);
|
||||
container.getStyleClass().add("advanced-list-box-content");
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,16 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class AdvancedListItem extends Control {
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
private final ObjectProperty<Node> leftGraphic = new SimpleObjectProperty<>(this, "leftGraphic");
|
||||
private final ObjectProperty<Node> rightGraphic = new SimpleObjectProperty<>(this, "rightGraphic");
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final BooleanProperty active = new SimpleBooleanProperty(this, "active");
|
||||
@@ -39,16 +45,16 @@ public class AdvancedListItem extends Control {
|
||||
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent()));
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
public Node getLeftGraphic() {
|
||||
return leftGraphic.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
public ObjectProperty<Node> leftGraphicProperty() {
|
||||
return leftGraphic;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
public void setLeftGraphic(Node leftGraphic) {
|
||||
this.leftGraphic.set(leftGraphic);
|
||||
}
|
||||
|
||||
public Node getRightGraphic() {
|
||||
@@ -134,4 +140,17 @@ public class AdvancedListItem extends Control {
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new AdvancedListItemSkin(this);
|
||||
}
|
||||
|
||||
public static Pair<Node, ImageView> createImageView(Image image) {
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(image);
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
return pair(imageViewContainer, imageView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,16 +18,13 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
@@ -48,35 +45,22 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
root.setPickOnBounds(false);
|
||||
|
||||
HBox left = new HBox();
|
||||
left.setAlignment(Pos.CENTER);
|
||||
left.setAlignment(Pos.CENTER_LEFT);
|
||||
left.setMouseTransparent(true);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
root.setCenter(vbox);
|
||||
vbox.setMouseTransparent(true);
|
||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||
vbox.setPadding(new Insets(0, 0, 0, 10));
|
||||
|
||||
Label title = new Label();
|
||||
title.textProperty().bind(skinnable.titleProperty());
|
||||
title.setMaxWidth(90);
|
||||
title.setStyle("-fx-font-size: 15;");
|
||||
title.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
title.getStyleClass().add("title");
|
||||
vbox.getChildren().add(title);
|
||||
|
||||
Label subtitle = new Label();
|
||||
subtitle.textProperty().bind(skinnable.subtitleProperty());
|
||||
subtitle.setMaxWidth(90);
|
||||
subtitle.setStyle("-fx-font-size: 10;");
|
||||
subtitle.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
subtitle.getStyleClass().add("subtitle");
|
||||
vbox.getChildren().add(subtitle);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.subtitleProperty(), subtitleString -> {
|
||||
@@ -84,7 +68,14 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
else vbox.getChildren().setAll(title, subtitle);
|
||||
});
|
||||
|
||||
left.getChildren().setAll(imageViewContainer, vbox);
|
||||
FXUtils.onChangeAndOperate(skinnable.leftGraphicProperty(),
|
||||
newGraphic -> {
|
||||
if (newGraphic == null) {
|
||||
left.getChildren().clear();
|
||||
} else {
|
||||
left.getChildren().setAll(newGraphic);
|
||||
}
|
||||
});
|
||||
root.setLeft(left);
|
||||
|
||||
HBox right = new HBox();
|
||||
@@ -105,8 +96,6 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
FXUtils.onChangeAndOperate(skinnable.actionButtonVisibleProperty(),
|
||||
visible -> root.setRight(visible ? right : null));
|
||||
|
||||
stackPane.setStyle("-fx-padding: 10 16 10 16;");
|
||||
stackPane.getStyleClass().add("transparent");
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getChildren().setAll(root);
|
||||
|
||||
|
||||
@@ -88,8 +88,8 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, MessageType.QUESTION, onAccept);
|
||||
public MessageDialogPane(String text, String title, MessageType type, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, type, onAccept);
|
||||
|
||||
cancelButton.setVisible(true);
|
||||
cancelButton.setOnMouseClicked(e -> {
|
||||
|
||||
@@ -49,6 +49,7 @@ public class TabHeader extends Control implements TabControl {
|
||||
}
|
||||
|
||||
private ObservableList<Tab> tabs = FXCollections.observableArrayList();
|
||||
private ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
|
||||
|
||||
@Override
|
||||
public ObservableList<Tab> getTabs() {
|
||||
@@ -69,6 +70,27 @@ public class TabHeader extends Control implements TabControl {
|
||||
this.selectionModel.set(selectionModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* The position to place the tabs.
|
||||
*/
|
||||
public Side getSide() {
|
||||
return side.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The position of the tabs.
|
||||
*/
|
||||
public ObjectProperty<Side> sideProperty() {
|
||||
return side;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position the place the tabs in this TabHeader.
|
||||
*/
|
||||
public void setSide(Side side) {
|
||||
this.side.set(side);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new TabHeaderSkin(this);
|
||||
@@ -115,7 +137,7 @@ public class TabHeader extends Control implements TabControl {
|
||||
protected class HeaderContainer extends StackPane {
|
||||
private Timeline timeline;
|
||||
private StackPane selectedTabLine;
|
||||
private StackPane headersRegion;
|
||||
private HeadersRegion headersRegion;
|
||||
private Scale scale = new Scale(1, 1, 0, 0);
|
||||
private Rotate rotate = new Rotate(0, 0, 1);
|
||||
private double selectedTabLineOffset;
|
||||
@@ -125,9 +147,115 @@ public class TabHeader extends Control implements TabControl {
|
||||
getStyleClass().add("tab-header-area");
|
||||
setPickOnBounds(false);
|
||||
|
||||
headersRegion = new StackPane() {
|
||||
headersRegion = new HeadersRegion();
|
||||
headersRegion.sideProperty().bind(getSkinnable().sideProperty());
|
||||
|
||||
selectedTabLine = new StackPane();
|
||||
selectedTabLine.setManaged(false);
|
||||
selectedTabLine.getTransforms().addAll(scale, rotate);
|
||||
selectedTabLine.setCache(true);
|
||||
selectedTabLine.getStyleClass().addAll("tab-selected-line");
|
||||
selectedTabLine.setPrefHeight(2);
|
||||
selectedTabLine.setPrefWidth(2);
|
||||
selectedTabLine.setBackground(new Background(new BackgroundFill(ripplerColor, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
getChildren().setAll(headersRegion, selectedTabLine);
|
||||
headersRegion.setPickOnBounds(false);
|
||||
headersRegion.prefHeightProperty().bind(heightProperty());
|
||||
prefWidthProperty().bind(headersRegion.widthProperty());
|
||||
rotate.pivotXProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 0.0 : 1, getSkinnable().sideProperty()));
|
||||
rotate.pivotYProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 1.0 : 0, getSkinnable().sideProperty()));
|
||||
|
||||
Bindings.bindContent(headersRegion.getChildren(), binding = MappedObservableList.create(getSkinnable().getTabs(), tab -> {
|
||||
TabHeaderContainer container = new TabHeaderContainer(tab);
|
||||
container.setVisible(true);
|
||||
return container;
|
||||
}));
|
||||
}
|
||||
|
||||
public void setNeedsLayout2(boolean value) {
|
||||
setNeedsLayout(value);
|
||||
}
|
||||
|
||||
private boolean isAnimating() {
|
||||
return this.timeline != null && this.timeline.getStatus() == Animation.Status.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
if (isSelectingTab) {
|
||||
headersRegion.animateSelectionLine();
|
||||
isSelectingTab = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class HeadersRegion extends StackPane {
|
||||
private SideAction action;
|
||||
private final ObjectProperty<Side> side = new SimpleObjectProperty<Side>() {
|
||||
@Override
|
||||
protected double computePrefWidth(double height) {
|
||||
protected void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
switch (get()) {
|
||||
case TOP: action = new Top(); break;
|
||||
case BOTTOM: action = new Bottom(); break;
|
||||
case LEFT: action = new Left(); break;
|
||||
case RIGHT: action = new Right(); break;
|
||||
default: throw new InternalError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public Side getSide() {
|
||||
return side.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Side> sideProperty() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(Side side) {
|
||||
this.side.set(side);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefWidth(double height) {
|
||||
return action.computePrefWidth(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width) {
|
||||
return action.computePrefHeight(width);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
action.layoutChildren();
|
||||
}
|
||||
|
||||
protected void animateSelectionLine() {
|
||||
action.animateSelectionLine();
|
||||
}
|
||||
|
||||
private abstract class SideAction {
|
||||
abstract double computePrefWidth(double height);
|
||||
|
||||
abstract double computePrefHeight(double width);
|
||||
|
||||
void layoutChildren() {
|
||||
if (isSelectingTab) {
|
||||
animateSelectionLine();
|
||||
isSelectingTab = false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract void animateSelectionLine();
|
||||
}
|
||||
|
||||
private abstract class Horizontal extends SideAction {
|
||||
@Override
|
||||
public double computePrefWidth(double height) {
|
||||
double width = 0;
|
||||
for (Node child : getChildren()) {
|
||||
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||
@@ -137,7 +265,7 @@ public class TabHeader extends Control implements TabControl {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width) {
|
||||
public double computePrefHeight(double width) {
|
||||
double height = 0;
|
||||
for (Node child : getChildren()) {
|
||||
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||
@@ -146,13 +274,81 @@ public class TabHeader extends Control implements TabControl {
|
||||
return snapSize(height) + snappedTopInset() + snappedBottomInset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
if (isSelectingTab) {
|
||||
animateSelectionLine();
|
||||
isSelectingTab = false;
|
||||
private void runTimeline(double newTransX, double newWidth) {
|
||||
double lineWidth = selectedTabLine.prefWidth(-1.0D);
|
||||
if (isAnimating()) {
|
||||
timeline.stop();
|
||||
double tempScaleX = scale.getX();
|
||||
if (rotate.getAngle() != 0.0D) {
|
||||
rotate.setAngle(0.0D);
|
||||
double tempWidth = tempScaleX * lineWidth;
|
||||
selectedTabLine.setTranslateX(selectedTabLine.getTranslateX() - tempWidth);
|
||||
}
|
||||
}
|
||||
|
||||
double oldScaleX = scale.getX();
|
||||
double oldWidth = lineWidth * oldScaleX;
|
||||
double oldTransX = selectedTabLine.getTranslateX();
|
||||
double newScaleX = newWidth * oldScaleX / oldWidth;
|
||||
selectedTabLineOffset = newTransX;
|
||||
// newTransX += offsetStart * (double)this.direction;
|
||||
double transDiff = newTransX - oldTransX;
|
||||
if (transDiff < 0.0D) {
|
||||
selectedTabLine.setTranslateX(selectedTabLine.getTranslateX() + oldWidth);
|
||||
newTransX += newWidth;
|
||||
rotate.setAngle(180.0D);
|
||||
}
|
||||
|
||||
timeline = new Timeline(
|
||||
new KeyFrame(
|
||||
Duration.ZERO,
|
||||
new KeyValue(selectedTabLine.translateXProperty(), selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)
|
||||
),
|
||||
new KeyFrame(
|
||||
Duration.seconds(0.24D),
|
||||
new KeyValue(scale.xProperty(), newScaleX, Interpolator.EASE_BOTH),
|
||||
new KeyValue(selectedTabLine.translateXProperty(), newTransX, Interpolator.EASE_BOTH)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished((finish) -> {
|
||||
if (rotate.getAngle() != 0.0D) {
|
||||
rotate.setAngle(0.0D);
|
||||
selectedTabLine.setTranslateX(selectedTabLine.getTranslateX() - newWidth);
|
||||
}
|
||||
|
||||
});
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateSelectionLine() {
|
||||
double offset = 0.0D;
|
||||
double selectedTabOffset = 0.0D;
|
||||
double selectedTabWidth = 0.0D;
|
||||
|
||||
for (Node node : headersRegion.getChildren()) {
|
||||
if (node instanceof TabHeaderContainer) {
|
||||
TabHeaderContainer tabHeader = (TabHeaderContainer) node;
|
||||
double tabHeaderPrefWidth = snapSize(tabHeader.prefWidth(-1.0D));
|
||||
if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {
|
||||
selectedTabOffset = offset;
|
||||
selectedTabWidth = tabHeaderPrefWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
offset += tabHeaderPrefWidth;
|
||||
}
|
||||
}
|
||||
|
||||
this.runTimeline(selectedTabOffset, selectedTabWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private class Top extends Horizontal {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
double headerHeight = snapSize(prefHeight(-1));
|
||||
double tabStartX = 0;
|
||||
for (Node node : getChildren()) {
|
||||
@@ -171,115 +367,171 @@ public class TabHeader extends Control implements TabControl {
|
||||
snapSize(selectedTabLine.prefWidth(-1)),
|
||||
snapSize(selectedTabLine.prefHeight(-1)));
|
||||
}
|
||||
};
|
||||
|
||||
selectedTabLine = new StackPane();
|
||||
selectedTabLine.setManaged(false);
|
||||
selectedTabLine.getTransforms().addAll(scale, rotate);
|
||||
selectedTabLine.setCache(true);
|
||||
selectedTabLine.getStyleClass().addAll("tab-selected-line");
|
||||
selectedTabLine.setPrefHeight(2);
|
||||
selectedTabLine.setPrefWidth(1);
|
||||
selectedTabLine.setBackground(new Background(new BackgroundFill(ripplerColor, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
getChildren().setAll(headersRegion, selectedTabLine);
|
||||
headersRegion.setPickOnBounds(false);
|
||||
headersRegion.prefHeightProperty().bind(heightProperty());
|
||||
prefWidthProperty().bind(headersRegion.widthProperty());
|
||||
|
||||
Bindings.bindContent(headersRegion.getChildren(), binding = MappedObservableList.create(getSkinnable().getTabs(), tab -> {
|
||||
TabHeaderContainer container = new TabHeaderContainer(tab);
|
||||
container.setVisible(true);
|
||||
return container;
|
||||
}));
|
||||
}
|
||||
|
||||
public void setNeedsLayout2(boolean value) {
|
||||
setNeedsLayout(value);
|
||||
}
|
||||
|
||||
private void runTimeline(double newTransX, double newWidth) {
|
||||
double tempScaleX = 0.0D;
|
||||
double tempWidth = 0.0D;
|
||||
double lineWidth = this.selectedTabLine.prefWidth(-1.0D);
|
||||
if (this.isAnimating()) {
|
||||
this.timeline.stop();
|
||||
tempScaleX = this.scale.getX();
|
||||
if (this.rotate.getAngle() != 0.0D) {
|
||||
this.rotate.setAngle(0.0D);
|
||||
tempWidth = tempScaleX * lineWidth;
|
||||
this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() - tempWidth);
|
||||
}
|
||||
}
|
||||
|
||||
double oldScaleX = this.scale.getX();
|
||||
double oldWidth = lineWidth * oldScaleX;
|
||||
double oldTransX = this.selectedTabLine.getTranslateX();
|
||||
double newScaleX = newWidth * oldScaleX / oldWidth;
|
||||
this.selectedTabLineOffset = newTransX;
|
||||
// newTransX += offsetStart * (double)this.direction;
|
||||
double transDiff = newTransX - oldTransX;
|
||||
if (transDiff < 0.0D) {
|
||||
this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() + oldWidth);
|
||||
newTransX += newWidth;
|
||||
this.rotate.setAngle(180.0D);
|
||||
}
|
||||
private class Bottom extends Horizontal {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
this.timeline = new Timeline(
|
||||
new KeyFrame(
|
||||
Duration.ZERO,
|
||||
new KeyValue(this.selectedTabLine.translateXProperty(), this.selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)
|
||||
),
|
||||
new KeyFrame(
|
||||
Duration.seconds(0.24D),
|
||||
new KeyValue(this.scale.xProperty(), newScaleX, Interpolator.EASE_BOTH),
|
||||
new KeyValue(this.selectedTabLine.translateXProperty(), newTransX, Interpolator.EASE_BOTH)
|
||||
)
|
||||
);
|
||||
this.timeline.setOnFinished((finish) -> {
|
||||
if (this.rotate.getAngle() != 0.0D) {
|
||||
this.rotate.setAngle(0.0D);
|
||||
this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() - newWidth);
|
||||
}
|
||||
double headerHeight = snapSize(prefHeight(-1));
|
||||
double tabStartX = 0;
|
||||
for (Node node : getChildren()) {
|
||||
if (!(node instanceof TabHeaderContainer)) continue;
|
||||
TabHeaderContainer child = (TabHeaderContainer) node;
|
||||
double w = snapSize(child.prefWidth(-1));
|
||||
double h = snapSize(child.prefHeight(-1));
|
||||
child.resize(w, h);
|
||||
|
||||
});
|
||||
this.timeline.play();
|
||||
}
|
||||
|
||||
private boolean isAnimating() {
|
||||
return this.timeline != null && this.timeline.getStatus() == Animation.Status.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
if (isSelectingTab) {
|
||||
animateSelectionLine();
|
||||
isSelectingTab = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void animateSelectionLine() {
|
||||
double offset = 0.0D;
|
||||
double selectedTabOffset = 0.0D;
|
||||
double selectedTabWidth = 0.0D;
|
||||
Side side = Side.TOP;
|
||||
|
||||
for (Node node : headersRegion.getChildren()) {
|
||||
if (node instanceof TabHeaderContainer) {
|
||||
TabHeaderContainer tabHeader = (TabHeaderContainer)node;
|
||||
double tabHeaderPrefWidth = this.snapSize(tabHeader.prefWidth(-1.0D));
|
||||
if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {
|
||||
selectedTabOffset = side != Side.LEFT && side != Side.BOTTOM ? offset : -offset - tabHeaderPrefWidth;
|
||||
selectedTabWidth = tabHeaderPrefWidth;
|
||||
break;
|
||||
child.relocate(tabStartX, snappedTopInset());
|
||||
tabStartX += w;
|
||||
}
|
||||
|
||||
offset += tabHeaderPrefWidth;
|
||||
selectedTabLine.resizeRelocate(0, 0,
|
||||
snapSize(selectedTabLine.prefWidth(-1)),
|
||||
snapSize(selectedTabLine.prefHeight(-1)));
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class Vertical extends SideAction {
|
||||
@Override
|
||||
public double computePrefWidth(double height) {
|
||||
double width = 0;
|
||||
for (Node child : getChildren()) {
|
||||
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||
width = Math.max(width, child.prefWidth(height));
|
||||
}
|
||||
return snapSize(width) + snappedLeftInset() + snappedRightInset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double computePrefHeight(double width) {
|
||||
double height = 0;
|
||||
for (Node child : getChildren()) {
|
||||
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||
height += child.prefHeight(width);
|
||||
}
|
||||
return snapSize(height) + snappedTopInset() + snappedBottomInset();
|
||||
}
|
||||
|
||||
private void runTimeline(double newTransY, double newHeight) {
|
||||
double lineHeight = selectedTabLine.prefHeight(-1.0D);
|
||||
if (isAnimating()) {
|
||||
timeline.stop();
|
||||
double tempScaleY = scale.getY();
|
||||
if (rotate.getAngle() != 0.0D) {
|
||||
rotate.setAngle(0.0D);
|
||||
double tempHeight = tempScaleY * lineHeight;
|
||||
selectedTabLine.setTranslateY(selectedTabLine.getTranslateY() - tempHeight);
|
||||
}
|
||||
}
|
||||
|
||||
double oldScaleY = scale.getY();
|
||||
double oldHeight = lineHeight * oldScaleY;
|
||||
double oldTransY = selectedTabLine.getTranslateY();
|
||||
double newScaleY = newHeight * oldScaleY / oldHeight;
|
||||
selectedTabLineOffset = newTransY;
|
||||
// newTransY += offsetStart * (double)this.direction;
|
||||
double transDiff = newTransY - oldTransY;
|
||||
if (transDiff < 0.0D) {
|
||||
selectedTabLine.setTranslateY(selectedTabLine.getTranslateY() + oldHeight);
|
||||
newTransY += newHeight;
|
||||
rotate.setAngle(180.0D);
|
||||
}
|
||||
|
||||
timeline = new Timeline(
|
||||
new KeyFrame(
|
||||
Duration.ZERO,
|
||||
new KeyValue(selectedTabLine.translateYProperty(), selectedTabLine.getTranslateY(), Interpolator.EASE_BOTH)
|
||||
),
|
||||
new KeyFrame(
|
||||
Duration.seconds(1.24D),
|
||||
new KeyValue(scale.yProperty(), newScaleY, Interpolator.EASE_BOTH),
|
||||
new KeyValue(selectedTabLine.translateYProperty(), newTransY, Interpolator.EASE_BOTH)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished((finish) -> {
|
||||
if (rotate.getAngle() != 0.0D) {
|
||||
rotate.setAngle(0.0D);
|
||||
selectedTabLine.setTranslateY(selectedTabLine.getTranslateY() - newHeight);
|
||||
}
|
||||
|
||||
});
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateSelectionLine() {
|
||||
double offset = 0.0D;
|
||||
double selectedTabOffset = 0.0D;
|
||||
double selectedTabHeight = 0.0D;
|
||||
|
||||
for (Node node : headersRegion.getChildren()) {
|
||||
if (node instanceof TabHeaderContainer) {
|
||||
TabHeaderContainer tabHeader = (TabHeaderContainer) node;
|
||||
double tabHeaderPrefHeight = snapSize(tabHeader.prefHeight(-1.0D));
|
||||
if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {
|
||||
selectedTabOffset = offset;
|
||||
selectedTabHeight = tabHeaderPrefHeight;
|
||||
break;
|
||||
}
|
||||
|
||||
offset += tabHeaderPrefHeight;
|
||||
}
|
||||
}
|
||||
|
||||
this.runTimeline(selectedTabOffset, selectedTabHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private class Left extends Vertical {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
double headerWidth = snapSize(prefWidth(-1));
|
||||
double tabStartY = 0;
|
||||
for (Node node : getChildren()) {
|
||||
if (!(node instanceof TabHeaderContainer)) continue;
|
||||
TabHeaderContainer child = (TabHeaderContainer) node;
|
||||
double w = snapSize(child.prefWidth(-1));
|
||||
double h = snapSize(child.prefHeight(-1));
|
||||
child.resize(w, h);
|
||||
|
||||
child.relocate(headerWidth - w - snappedRightInset(), tabStartY);
|
||||
tabStartY += h;
|
||||
}
|
||||
|
||||
selectedTabLine.resizeRelocate(headerWidth - selectedTabLine.prefWidth(-1), 0,
|
||||
snapSize(selectedTabLine.prefWidth(-1)),
|
||||
snapSize(selectedTabLine.prefHeight(-1)));
|
||||
}
|
||||
}
|
||||
|
||||
private class Right extends Vertical {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
double headerWidth = snapSize(prefWidth(-1));
|
||||
double tabStartY = 0;
|
||||
for (Node node : getChildren()) {
|
||||
if (!(node instanceof TabHeaderContainer)) continue;
|
||||
TabHeaderContainer child = (TabHeaderContainer) node;
|
||||
double w = snapSize(child.prefWidth(-1));
|
||||
double h = snapSize(child.prefHeight(-1));
|
||||
child.resize(w, h);
|
||||
|
||||
child.relocate(snappedLeftInset(), tabStartY);
|
||||
tabStartY += h;
|
||||
}
|
||||
|
||||
selectedTabLine.resizeRelocate(0, 0,
|
||||
snapSize(selectedTabLine.prefWidth(-1)),
|
||||
snapSize(selectedTabLine.prefHeight(-1)));
|
||||
}
|
||||
}
|
||||
|
||||
this.runTimeline(selectedTabOffset, selectedTabWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,4 +566,5 @@ public class TabHeader extends Control implements TabControl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,10 +41,7 @@ import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorTabPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
@@ -65,15 +62,11 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public class RootPage extends DecoratorTabPage {
|
||||
private MainPage mainPage = null;
|
||||
private SettingsPage settingsPage = null;
|
||||
private GameList gameListPage = null;
|
||||
private AccountList accountListPage = null;
|
||||
private ProfileList profileListPage = null;
|
||||
|
||||
private final TabHeader.Tab mainTab = new TabHeader.Tab("main");
|
||||
private final TabHeader.Tab settingsTab = new TabHeader.Tab("settings");
|
||||
private final TabHeader.Tab gameTab = new TabHeader.Tab("game");
|
||||
private final TabHeader.Tab accountTab = new TabHeader.Tab("account");
|
||||
private final TabHeader.Tab profileTab = new TabHeader.Tab("profile");
|
||||
|
||||
public RootPage() {
|
||||
setLeftPaneWidth(200);
|
||||
@@ -86,10 +79,8 @@ public class RootPage extends DecoratorTabPage {
|
||||
|
||||
mainTab.setNodeSupplier(this::getMainPage);
|
||||
settingsTab.setNodeSupplier(this::getSettingsPage);
|
||||
gameTab.setNodeSupplier(this::getGameListPage);
|
||||
accountTab.setNodeSupplier(this::getAccountListPage);
|
||||
profileTab.setNodeSupplier(this::getProfileListPage);
|
||||
getTabs().setAll(mainTab, settingsTab, gameTab, accountTab, profileTab);
|
||||
getTabs().setAll(mainTab, settingsTab, accountTab);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,17 +142,6 @@ public class RootPage extends DecoratorTabPage {
|
||||
return settingsPage;
|
||||
}
|
||||
|
||||
private GameList getGameListPage() {
|
||||
if (gameListPage == null) {
|
||||
gameListPage = new GameList();
|
||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
}
|
||||
return gameListPage;
|
||||
}
|
||||
|
||||
private AccountList getAccountListPage() {
|
||||
if (accountListPage == null) {
|
||||
accountListPage = new AccountList();
|
||||
@@ -171,15 +151,6 @@ public class RootPage extends DecoratorTabPage {
|
||||
return accountListPage;
|
||||
}
|
||||
|
||||
private ProfileList getProfileListPage() {
|
||||
if (profileListPage == null) {
|
||||
profileListPage = new ProfileList();
|
||||
profileListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
profileListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
}
|
||||
return profileListPage;
|
||||
}
|
||||
|
||||
public Tab getMainTab() {
|
||||
return mainTab;
|
||||
}
|
||||
@@ -188,18 +159,10 @@ public class RootPage extends DecoratorTabPage {
|
||||
return settingsTab;
|
||||
}
|
||||
|
||||
public Tab getGameTab() {
|
||||
return gameTab;
|
||||
}
|
||||
|
||||
public Tab getAccountTab() {
|
||||
return accountTab;
|
||||
}
|
||||
|
||||
public Tab getProfileTab() {
|
||||
return profileTab;
|
||||
}
|
||||
|
||||
private void selectPage(Tab tab) {
|
||||
if (getSelectionModel().getSelectedItem() == tab) {
|
||||
getSelectionModel().select(getMainTab());
|
||||
@@ -226,7 +189,7 @@ public class RootPage extends DecoratorTabPage {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
String version = Profiles.getSelectedVersion();
|
||||
if (version == null) {
|
||||
control.selectPage(control.gameTab);
|
||||
Controllers.navigate(Controllers.getGameListPage());
|
||||
} else {
|
||||
Versions.modifyGameSettings(profile, version);
|
||||
}
|
||||
@@ -234,21 +197,14 @@ public class RootPage extends DecoratorTabPage {
|
||||
|
||||
// third item in left sidebar
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
gameItem.activeProperty().bind(control.gameTab.selectedProperty());
|
||||
gameItem.setImage(newImage("/assets/img/bookshelf.png"));
|
||||
gameItem.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/bookshelf.png")).getKey());
|
||||
gameItem.setTitle(i18n("version.manage"));
|
||||
gameItem.setOnAction(e -> control.selectPage(control.gameTab));
|
||||
|
||||
// forth item in left sidebar
|
||||
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
|
||||
profileListItem.activeProperty().bind(control.profileTab.selectedProperty());
|
||||
profileListItem.setOnAction(e -> control.selectPage(control.profileTab));
|
||||
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
|
||||
// fifth item in left sidebar
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.activeProperty().bind(control.settingsTab.selectedProperty());
|
||||
launcherSettingsItem.setImage(newImage("/assets/img/command.png"));
|
||||
launcherSettingsItem.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
|
||||
launcherSettingsItem.setTitle(i18n("settings.launcher"));
|
||||
launcherSettingsItem.setOnAction(e -> control.selectPage(control.settingsTab));
|
||||
|
||||
@@ -259,8 +215,6 @@ public class RootPage extends DecoratorTabPage {
|
||||
.startCategory(i18n("version").toUpperCase())
|
||||
.add(gameListItem)
|
||||
.add(gameItem)
|
||||
.startCategory(i18n("profile.title").toUpperCase())
|
||||
.add(profileListItem)
|
||||
.startCategory(i18n("launcher").toUpperCase())
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class ProfileAdvancedListItem extends AdvancedListItem {
|
||||
};
|
||||
|
||||
public ProfileAdvancedListItem() {
|
||||
setImage(newImage("/assets/img/craft_table.png"));
|
||||
setLeftGraphic(createImageView(newImage("/assets/img/craft_table.png")).getKey());
|
||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.profile;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class ProfileList extends ListPage<ProfileListItem> implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("profile.manage")));
|
||||
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private ObjectProperty<Profile> selectedProfile;
|
||||
|
||||
public ProfileList() {
|
||||
setItems(MappedObservableList.create(profilesProperty(), ProfileListItem::new));
|
||||
selectedProfile = createSelectedItemPropertyFor(getItems(), Profile.class);
|
||||
}
|
||||
|
||||
public ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public ListProperty<Profile> profilesProperty() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
Controllers.navigate(new ProfilePage(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class ProfileListItem extends RadioButton {
|
||||
|
||||
public ProfileListItem(Profile profile) {
|
||||
this.profile = profile;
|
||||
getStyleClass().clear();
|
||||
getStyleClass().setAll("navigation-drawer-item");
|
||||
setUserData(profile);
|
||||
|
||||
title.set(Profiles.getProfileDisplayName(profile));
|
||||
|
||||
@@ -18,20 +18,14 @@
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
|
||||
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
public ProfileListItemSkin(ProfileListItem skinnable) {
|
||||
@@ -39,28 +33,9 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().set(newImage("/assets/img/craft_table.png"));
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
root.setCenter(item);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
@@ -73,9 +48,6 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
root.getStyleClass().add("card");
|
||||
root.setStyle("-fx-padding: 8 8 8 0");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
|
||||
@@ -17,35 +17,43 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final Tooltip tooltip;
|
||||
private final ImageView imageView;
|
||||
|
||||
public GameAdvancedListItem() {
|
||||
tooltip = new Tooltip();
|
||||
|
||||
Pair<Node, ImageView> view = createImageView(null);
|
||||
setLeftGraphic(view.getKey());
|
||||
imageView = view.getValue();
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
if (version != null && Profiles.getSelectedProfile() != null &&
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
setTitle(version);
|
||||
setSubtitle(null);
|
||||
setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
tooltip.setText(version);
|
||||
} else {
|
||||
Tooltip.uninstall(this,tooltip);
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
setImage(newImage("/assets/img/grass.png"));
|
||||
imageView.setImage(newImage("/assets/img/grass.png"));
|
||||
tooltip.setText("");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameList extends ListPageBase<GameListItem> implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("version.manage")));
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
|
||||
public GameList() {
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == Profiles.getSelectedProfile().getRepository())
|
||||
runInFX(() -> setLoading(true));
|
||||
});
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
toggleGroup = new ToggleGroup();
|
||||
WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
toggleGroup.getProperties().put("ReferenceHolder", listenerHolder);
|
||||
List<GameListItem> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(version -> new GameListItem(toggleGroup, profile, version.getId()))
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
setLoading(false);
|
||||
itemsProperty().setAll(children);
|
||||
children.forEach(GameListItem::checkSelection);
|
||||
|
||||
profile.selectedVersionProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||
FXUtils.checkFxUserThread();
|
||||
children.forEach(it -> it.selectedProperty().set(false));
|
||||
children.stream()
|
||||
.filter(it -> it.getVersion().equals(newValue))
|
||||
.findFirst()
|
||||
.ifPresent(it -> it.selectedProperty().set(true));
|
||||
}));
|
||||
}
|
||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||
if (toggle == null) return;
|
||||
GameListItem model = (GameListItem) toggle.getUserData();
|
||||
model.getProfile().setSelectedVersion(model.getVersion());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameListSkin createDefaultSkin() {
|
||||
return new GameListSkin();
|
||||
}
|
||||
|
||||
public static void addNewGame() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(profile), i18n("install.new_game"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void importModpack() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile), i18n("install.modpack"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void refreshList() {
|
||||
Profiles.getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
public void modifyGlobalGameSettings() {
|
||||
Versions.modifyGlobalSettings(Profiles.getSelectedProfile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private class GameListSkin extends ToolbarListPageSkin<GameList> {
|
||||
|
||||
public GameListSkin() {
|
||||
super(GameList.this);
|
||||
|
||||
HBox hbox = new HBox(
|
||||
createToolbarButton(i18n("install.new_game"), SVG::plus, GameList::addNewGame),
|
||||
createToolbarButton(i18n("install.modpack"), SVG::importIcon, GameList::importModpack),
|
||||
createToolbarButton(i18n("button.refresh"), SVG::refresh, GameList::refreshList),
|
||||
createToolbarButton(i18n("settings.type.global.manage"), SVG::gear, GameList.this::modifyGlobalGameSettings)
|
||||
);
|
||||
hbox.setPickOnBounds(false);
|
||||
|
||||
state.set(new State(i18n("version.manage"), hbox, true, false, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(GameList skinnable) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileListItem;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfilePage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class GameListPage extends ListPageBase<GameListItem> implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("version.manage")));
|
||||
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ObservableList<ProfileListItem> profileListItems;
|
||||
private final ObjectProperty<Profile> selectedProfile;
|
||||
|
||||
private ToggleGroup toggleGroup;
|
||||
|
||||
public GameListPage() {
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == Profiles.getSelectedProfile().getRepository())
|
||||
runInFX(() -> setLoading(true));
|
||||
});
|
||||
profileListItems = MappedObservableList.create(profilesProperty(), ProfileListItem::new);
|
||||
selectedProfile = createSelectedItemPropertyFor(profileListItems, Profile.class);
|
||||
}
|
||||
|
||||
public ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public ObservableList<Profile> getProfiles() {
|
||||
return profiles.get();
|
||||
}
|
||||
|
||||
public ListProperty<Profile> profilesProperty() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public void setProfiles(ObservableList<Profile> profiles) {
|
||||
this.profiles.set(profiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameListPageSkin createDefaultSkin() {
|
||||
return new GameListPageSkin();
|
||||
}
|
||||
|
||||
public void modifyGlobalGameSettings() {
|
||||
Versions.modifyGlobalSettings(Profiles.getSelectedProfile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private class GameListPageSkin extends SkinBase<GameListPage> {
|
||||
|
||||
protected GameListPageSkin() {
|
||||
super(GameListPage.this);
|
||||
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
GameList gameList = new GameList();
|
||||
|
||||
{
|
||||
BorderPane left = new BorderPane();
|
||||
FXUtils.setLimitWidth(left, 300);
|
||||
root.setLeft(left);
|
||||
|
||||
{
|
||||
AdvancedListItem addProfileItem = new AdvancedListItem();
|
||||
addProfileItem.getStyleClass().add("navigation-drawer-item");
|
||||
addProfileItem.setTitle(i18n("profile.new"));
|
||||
addProfileItem.setActionButtonVisible(false);
|
||||
addProfileItem.setLeftGraphic(VersionPage.wrap(SVG.plus(Theme.blackFillBinding(), -1, -1)));
|
||||
addProfileItem.setOnAction(e -> Controllers.navigate(new ProfilePage(null)));
|
||||
|
||||
ScrollPane pane = new ScrollPane();
|
||||
VBox wrapper = new VBox();
|
||||
VBox box = new VBox();
|
||||
Bindings.bindContent(box.getChildren(), profileListItems);
|
||||
wrapper.getChildren().setAll(box, addProfileItem);
|
||||
pane.setContent(wrapper);
|
||||
left.setCenter(pane);
|
||||
}
|
||||
|
||||
{
|
||||
AdvancedListItem installNewGameItem = new AdvancedListItem();
|
||||
installNewGameItem.getStyleClass().add("navigation-drawer-item");
|
||||
installNewGameItem.setTitle(i18n("install.new_game"));
|
||||
installNewGameItem.setActionButtonVisible(false);
|
||||
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.plus(Theme.blackFillBinding(), -1, -1)));
|
||||
installNewGameItem.setOnAction(e -> Versions.addNewGame());
|
||||
|
||||
AdvancedListItem installModpackItem = new AdvancedListItem();
|
||||
installModpackItem.getStyleClass().add("navigation-drawer-item");
|
||||
installModpackItem.setTitle(i18n("install.modpack"));
|
||||
installModpackItem.setActionButtonVisible(false);
|
||||
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.importIcon(Theme.blackFillBinding(), -1, -1)));
|
||||
installModpackItem.setOnAction(e -> Versions.importModpack());
|
||||
|
||||
AdvancedListItem refreshItem = new AdvancedListItem();
|
||||
refreshItem.getStyleClass().add("navigation-drawer-item");
|
||||
refreshItem.setTitle(i18n("button.refresh"));
|
||||
refreshItem.setActionButtonVisible(false);
|
||||
refreshItem.setLeftGraphic(VersionPage.wrap(SVG.refresh(Theme.blackFillBinding(), -1, -1)));
|
||||
refreshItem.setOnAction(e -> gameList.refreshList());
|
||||
|
||||
AdvancedListItem globalManageItem = new AdvancedListItem();
|
||||
globalManageItem.getStyleClass().add("navigation-drawer-item");
|
||||
globalManageItem.setTitle(i18n("settings.type.global.manage"));
|
||||
globalManageItem.setActionButtonVisible(false);
|
||||
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.gear(Theme.blackFillBinding(), -1, -1)));
|
||||
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
|
||||
|
||||
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
|
||||
.add(installNewGameItem)
|
||||
.add(installModpackItem)
|
||||
.add(refreshItem)
|
||||
.add(globalManageItem);
|
||||
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 18);
|
||||
left.setBottom(bottomLeftCornerList);
|
||||
}
|
||||
}
|
||||
|
||||
root.setCenter(gameList);
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
|
||||
private class GameList extends ListPageBase<GameListItem> {
|
||||
public GameList() {
|
||||
super();
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
toggleGroup = new ToggleGroup();
|
||||
WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
toggleGroup.getProperties().put("ReferenceHolder", listenerHolder);
|
||||
List<GameListItem> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(version -> new GameListItem(toggleGroup, profile, version.getId()))
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
setLoading(false);
|
||||
itemsProperty().setAll(children);
|
||||
children.forEach(GameListItem::checkSelection);
|
||||
|
||||
profile.selectedVersionProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||
FXUtils.checkFxUserThread();
|
||||
children.forEach(it -> it.selectedProperty().set(false));
|
||||
children.stream()
|
||||
.filter(it -> it.getVersion().equals(newValue))
|
||||
.findFirst()
|
||||
.ifPresent(it -> it.selectedProperty().set(true));
|
||||
}));
|
||||
}
|
||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||
if (toggle == null) return;
|
||||
GameListItem model = (GameListItem) toggle.getUserData();
|
||||
model.getProfile().setSelectedVersion(model.getVersion());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void refreshList() {
|
||||
Profiles.getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameListSkin createDefaultSkin() {
|
||||
return new GameListSkin();
|
||||
}
|
||||
|
||||
private class GameListSkin extends ToolbarListPageSkin<GameList> {
|
||||
|
||||
public GameListSkin() {
|
||||
super(GameList.this);
|
||||
|
||||
state.set(new State(i18n("version.manage"), null, true, false, true, false, 300));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(GameList skinnable) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,48 +17,35 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListCell;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.util.Callback;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.ToolbarListPageSkin;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class VersionPage extends Control implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||
private final JFXListView<String> listView = new JFXListView<>();
|
||||
private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab");
|
||||
private final VersionSettingsPage versionSettingsPage = new VersionSettingsPage();
|
||||
private final TabHeader.Tab modListTab = new TabHeader.Tab("modListTab");
|
||||
@@ -70,71 +57,39 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final ObjectProperty<TabHeader.Tab> selectedTab = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
|
||||
private Profile profile;
|
||||
private String version;
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
{
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
listView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null && !Objects.equals(oldValue, newValue))
|
||||
loadVersion(newValue, profile);
|
||||
if (newValue == null && !Objects.equals(oldValue, newValue)) {
|
||||
if (listView.getItems().contains(preferredVersionName)) {
|
||||
loadVersion(preferredVersionName, profile);
|
||||
} else if (!listView.getItems().isEmpty()) {
|
||||
loadVersion(listView.getItems().get(0), profile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
versionSettingsTab.setNode(versionSettingsPage);
|
||||
modListTab.setNode(modListPage);
|
||||
installerListTab.setNode(installerListPage);
|
||||
worldListTab.setNode(worldListPage);
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
List<String> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(Version::getId)
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
this.profile = profile;
|
||||
loading.set(false);
|
||||
listView.getItems().setAll(children);
|
||||
}
|
||||
selectedTab.set(versionSettingsTab);
|
||||
FXUtils.onChangeAndOperate(selectedTab, newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
});
|
||||
}
|
||||
|
||||
public void setVersion(String version, Profile profile) {
|
||||
this.version = version;
|
||||
this.profile = profile;
|
||||
|
||||
profile.setSelectedVersion(version);
|
||||
this.version.set(new Profile.ProfileVersion(profile, version));
|
||||
}
|
||||
|
||||
public void loadVersion(String version, Profile profile) {
|
||||
// If we jumped to game list page and deleted this version
|
||||
// and back to this page, we should return to main page.
|
||||
if (!this.profile.getRepository().isLoaded() ||
|
||||
!this.profile.getRepository().hasVersion(version)) {
|
||||
if (this.version.get() != null && (!getProfile().getRepository().isLoaded() ||
|
||||
!getProfile().getRepository().hasVersion(version))) {
|
||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||
return;
|
||||
}
|
||||
|
||||
setVersion(version, profile);
|
||||
preferredVersionName = version;
|
||||
listView.getSelectionModel().select(version);
|
||||
|
||||
versionSettingsPage.loadVersion(profile, version);
|
||||
currentVersionUpgradable.set(profile.getRepository().isModpack(version));
|
||||
@@ -146,63 +101,71 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
private void onNavigated(Navigator.NavigationEvent event) {
|
||||
if (this.version == null || this.profile == null)
|
||||
if (this.version.get() == null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
// If we jumped to game list page and deleted this version
|
||||
// and back to this page, we should return to main page.
|
||||
if (!this.profile.getRepository().isLoaded() ||
|
||||
!this.profile.getRepository().hasVersion(version)) {
|
||||
if (!getProfile().getRepository().isLoaded() ||
|
||||
!getProfile().getRepository().hasVersion(getVersion())) {
|
||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||
return;
|
||||
}
|
||||
|
||||
loadVersion(this.version, this.profile);
|
||||
loadVersion(getVersion(), getProfile());
|
||||
}
|
||||
|
||||
private void onBrowse(String sub) {
|
||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
||||
FXUtils.openFolder(new File(getProfile().getRepository().getRunDirectory(getVersion()), sub));
|
||||
}
|
||||
|
||||
private void redownloadAssetIndex() {
|
||||
Versions.updateGameAssets(profile, version);
|
||||
Versions.updateGameAssets(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void clearLibraries() {
|
||||
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
|
||||
FileUtils.deleteDirectoryQuietly(new File(getProfile().getRepository().getBaseDirectory(), "libraries"));
|
||||
}
|
||||
|
||||
private void clearJunkFiles() {
|
||||
Versions.cleanVersion(profile, version);
|
||||
Versions.cleanVersion(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void testGame() {
|
||||
Versions.testGame(profile, version);
|
||||
Versions.testGame(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void updateGame() {
|
||||
Versions.updateVersion(profile, version);
|
||||
Versions.updateVersion(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void generateLaunchScript() {
|
||||
Versions.generateLaunchScript(profile, version);
|
||||
Versions.generateLaunchScript(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void export() {
|
||||
Versions.exportVersion(profile, version);
|
||||
Versions.exportVersion(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void rename() {
|
||||
Versions.renameVersion(profile, version)
|
||||
.thenApply(newVersionName -> this.preferredVersionName = newVersionName);
|
||||
Versions.renameVersion(getProfile(), getVersion())
|
||||
.thenApply(newVersionName -> this.preferredVersionName = newVersionName);
|
||||
}
|
||||
|
||||
private void remove() {
|
||||
Versions.deleteVersion(profile, version);
|
||||
Versions.deleteVersion(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
private void duplicate() {
|
||||
Versions.duplicateVersion(profile, version);
|
||||
Versions.duplicateVersion(getProfile(), getVersion());
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return Optional.ofNullable(version.get()).map(Profile.ProfileVersion::getProfile).orElse(null);
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return Optional.ofNullable(version.get()).map(Profile.ProfileVersion::getVersion).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,7 +180,6 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
|
||||
public static class Skin extends SkinBase<VersionPage> {
|
||||
|
||||
String currentVersion;
|
||||
private JFXPopup listViewItemPopup;
|
||||
|
||||
/**
|
||||
@@ -232,46 +194,30 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
listViewItemPopup = new JFXPopup(menu);
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.testGame(getSkinnable().profile, currentVersion);
|
||||
Versions.testGame(getSkinnable().getProfile(), getSkinnable().getVersion());
|
||||
}, listViewItemPopup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.generateLaunchScript(getSkinnable().profile, currentVersion);
|
||||
Versions.generateLaunchScript(getSkinnable().getProfile(), getSkinnable().getVersion());
|
||||
}, listViewItemPopup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.renameVersion(getSkinnable().profile, currentVersion).thenApply(name -> getSkinnable().preferredVersionName = name);
|
||||
Versions.renameVersion(getSkinnable().getProfile(), getSkinnable().getVersion()).thenApply(name -> getSkinnable().preferredVersionName = name);
|
||||
}, listViewItemPopup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.copy(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.duplicate"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.duplicateVersion(getSkinnable().profile, currentVersion);
|
||||
Versions.duplicateVersion(getSkinnable().getProfile(), getSkinnable().getVersion());
|
||||
}, listViewItemPopup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.deleteVersion(getSkinnable().profile, currentVersion);
|
||||
Versions.deleteVersion(getSkinnable().getProfile(), getSkinnable().getVersion());
|
||||
}, listViewItemPopup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.exportVersion(getSkinnable().profile, currentVersion);
|
||||
Versions.exportVersion(getSkinnable().getProfile(), getSkinnable().getVersion());
|
||||
}, listViewItemPopup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> {
|
||||
Versions.openFolder(getSkinnable().profile, currentVersion);
|
||||
Versions.openFolder(getSkinnable().getProfile(), getSkinnable().getVersion());
|
||||
}, listViewItemPopup))
|
||||
);
|
||||
|
||||
control.listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
control.listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
|
||||
@Override
|
||||
public ListCell<String> call(ListView<String> param) {
|
||||
JFXListCell<String> cell = new JFXListCell<>();
|
||||
cell.setOnMouseClicked(e -> {
|
||||
if (cell.getItem() == null) return;
|
||||
currentVersion = cell.getItem();
|
||||
if (e.getButton() == MouseButton.SECONDARY) {
|
||||
listViewItemPopup.show(cell, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
return cell;
|
||||
}
|
||||
});
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||
|
||||
@@ -281,45 +227,48 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
{
|
||||
BorderPane left = new BorderPane();
|
||||
FXUtils.setLimitWidth(left, 200);
|
||||
left.setCenter(control.listView);
|
||||
root.setLeft(left);
|
||||
|
||||
JFXButton btnAddGame = ToolbarListPageSkin.createToolbarButton(null, SVG::plus, GameList::addNewGame);
|
||||
FXUtils.installFastTooltip(btnAddGame, new Tooltip(i18n("install.new_game")));
|
||||
AdvancedListItem versionSettingsItem = new AdvancedListItem();
|
||||
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
|
||||
versionSettingsItem.setTitle(i18n("settings"));
|
||||
versionSettingsItem.setLeftGraphic(wrap(SVG.gear(Theme.blackFillBinding(), 20, 20)));
|
||||
versionSettingsItem.setActionButtonVisible(false);
|
||||
versionSettingsItem.activeProperty().bind(control.selectedTab.isEqualTo(control.versionSettingsTab));
|
||||
versionSettingsItem.setOnAction(e -> control.selectedTab.set(control.versionSettingsTab));
|
||||
|
||||
JFXButton btnImportModpack = ToolbarListPageSkin.createToolbarButton(null, SVG::importIcon, GameList::importModpack);
|
||||
FXUtils.installFastTooltip(btnImportModpack, new Tooltip(i18n("install.modpack")));
|
||||
AdvancedListItem modListItem = new AdvancedListItem();
|
||||
modListItem.getStyleClass().add("navigation-drawer-item");
|
||||
modListItem.setTitle(i18n("mods"));
|
||||
modListItem.setLeftGraphic(wrap(SVG.puzzle(Theme.blackFillBinding(), 20, 20)));
|
||||
modListItem.setActionButtonVisible(false);
|
||||
modListItem.activeProperty().bind(control.selectedTab.isEqualTo(control.modListTab));
|
||||
modListItem.setOnAction(e -> control.selectedTab.set(control.modListTab));
|
||||
|
||||
JFXButton btnRefresh = ToolbarListPageSkin.createToolbarButton(null, SVG::refresh, GameList::refreshList);
|
||||
FXUtils.installFastTooltip(btnRefresh, new Tooltip(i18n("button.refresh")));
|
||||
AdvancedListItem installerListItem = new AdvancedListItem();
|
||||
installerListItem.getStyleClass().add("navigation-drawer-item");
|
||||
installerListItem.setTitle(i18n("settings.tabs.installers"));
|
||||
installerListItem.setLeftGraphic(wrap(SVG.cube(Theme.blackFillBinding(), 20, 20)));
|
||||
installerListItem.setActionButtonVisible(false);
|
||||
installerListItem.activeProperty().bind(control.selectedTab.isEqualTo(control.installerListTab));
|
||||
installerListItem.setOnAction(e -> control.selectedTab.set(control.installerListTab));
|
||||
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.setPickOnBounds(false);
|
||||
toolbar.getChildren().setAll(btnAddGame, btnImportModpack, btnRefresh);
|
||||
left.setTop(toolbar);
|
||||
}
|
||||
AdvancedListItem worldListItem = new AdvancedListItem();
|
||||
worldListItem.getStyleClass().add("navigation-drawer-item");
|
||||
worldListItem.setTitle(i18n("world"));
|
||||
worldListItem.setLeftGraphic(wrap(SVG.gamepad(Theme.blackFillBinding(), 20, 20)));
|
||||
worldListItem.setActionButtonVisible(false);
|
||||
worldListItem.activeProperty().bind(control.selectedTab.isEqualTo(control.worldListTab));
|
||||
worldListItem.setOnAction(e -> control.selectedTab.set(control.worldListTab));
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.add(versionSettingsItem)
|
||||
.add(modListItem)
|
||||
.add(installerListItem)
|
||||
.add(worldListItem);
|
||||
left.setCenter(sideBar);
|
||||
|
||||
TabHeader tabPane = new TabHeader();
|
||||
tabPane.setPickOnBounds(false);
|
||||
tabPane.getStyleClass().add("jfx-decorator-tab");
|
||||
control.versionSettingsTab.setText(i18n("settings"));
|
||||
control.modListTab.setText(i18n("mods"));
|
||||
control.installerListTab.setText(i18n("settings.tabs.installers"));
|
||||
control.worldListTab.setText(i18n("world"));
|
||||
tabPane.getTabs().setAll(
|
||||
control.versionSettingsTab,
|
||||
control.modListTab,
|
||||
control.installerListTab,
|
||||
control.worldListTab);
|
||||
control.selectedTab.bind(tabPane.getSelectionModel().selectedItemProperty());
|
||||
FXUtils.onChangeAndOperate(tabPane.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
control.transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
});
|
||||
|
||||
HBox toolBar = new HBox();
|
||||
toolBar.setAlignment(Pos.TOP_RIGHT);
|
||||
toolBar.setPickOnBounds(false);
|
||||
{
|
||||
PopupMenu browseList = new PopupMenu();
|
||||
JFXPopup browsePopup = new JFXPopup(browseList);
|
||||
browseList.getContent().setAll(
|
||||
@@ -347,44 +296,49 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(control::clearJunkFiles, managementPopup)).addTooltip(i18n("version.manage.clean.tooltip"))
|
||||
);
|
||||
|
||||
JFXButton upgradeButton = new JFXButton();
|
||||
upgradeButton.setGraphic(SVG.update(null, 20, 20));
|
||||
upgradeButton.getStyleClass().add("jfx-decorator-button");
|
||||
upgradeButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
upgradeButton.setOnAction(event -> control.updateGame());
|
||||
upgradeButton.visibleProperty().bind(control.currentVersionUpgradable);
|
||||
FXUtils.installFastTooltip(upgradeButton, i18n("version.update"));
|
||||
AdvancedListItem upgradeItem = new AdvancedListItem();
|
||||
upgradeItem.getStyleClass().add("navigation-drawer-item");
|
||||
upgradeItem.setTitle(i18n("version.update"));
|
||||
upgradeItem.setLeftGraphic(wrap(SVG.update(Theme.blackFillBinding(), 20, 20)));
|
||||
upgradeItem.setActionButtonVisible(false);
|
||||
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
||||
upgradeItem.setOnAction(e -> control.updateGame());
|
||||
|
||||
JFXButton testGameButton = new JFXButton();
|
||||
testGameButton.setGraphic(SVG.launch(null, 20, 20));
|
||||
testGameButton.getStyleClass().add("jfx-decorator-button");
|
||||
testGameButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
testGameButton.setOnAction(event -> control.testGame());
|
||||
FXUtils.installFastTooltip(testGameButton, i18n("version.launch.test"));
|
||||
AdvancedListItem testGameItem = new AdvancedListItem();
|
||||
testGameItem.getStyleClass().add("navigation-drawer-item");
|
||||
testGameItem.setTitle(i18n("version.launch.test"));
|
||||
testGameItem.setLeftGraphic(wrap(SVG.launch(Theme.blackFillBinding(), 20, 20)));
|
||||
testGameItem.setActionButtonVisible(false);
|
||||
testGameItem.setOnAction(e -> control.testGame());
|
||||
|
||||
JFXButton browseMenuButton = new JFXButton();
|
||||
browseMenuButton.setGraphic(SVG.folderOpen(null, 20, 20));
|
||||
browseMenuButton.getStyleClass().add("jfx-decorator-button");
|
||||
browseMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
browseMenuButton.setOnAction(event -> browsePopup.show(browseMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, browseMenuButton.getHeight()));
|
||||
FXUtils.installFastTooltip(browseMenuButton, i18n("settings.game.exploration"));
|
||||
AdvancedListItem browseMenuItem = new AdvancedListItem();
|
||||
browseMenuItem.getStyleClass().add("navigation-drawer-item");
|
||||
browseMenuItem.setTitle(i18n("settings.game.exploration"));
|
||||
browseMenuItem.setLeftGraphic(wrap(SVG.folderOpen(Theme.blackFillBinding(), 20, 20)));
|
||||
browseMenuItem.setActionButtonVisible(false);
|
||||
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
||||
|
||||
JFXButton managementMenuButton = new JFXButton();
|
||||
FXUtils.setLimitWidth(managementMenuButton, 40);
|
||||
FXUtils.setLimitHeight(managementMenuButton, 40);
|
||||
managementMenuButton.setGraphic(SVG.wrench(null, 20, 20));
|
||||
managementMenuButton.getStyleClass().add("jfx-decorator-button");
|
||||
managementMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
managementMenuButton.setOnAction(event -> managementPopup.show(managementMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, managementMenuButton.getHeight()));
|
||||
FXUtils.installFastTooltip(managementMenuButton, i18n("settings.game.management"));
|
||||
AdvancedListItem managementItem = new AdvancedListItem();
|
||||
managementItem.getStyleClass().add("navigation-drawer-item");
|
||||
managementItem.setTitle(i18n("settings.game.management"));
|
||||
managementItem.setLeftGraphic(wrap(SVG.wrench(Theme.blackFillBinding(), 20, 20)));
|
||||
managementItem.setActionButtonVisible(false);
|
||||
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
||||
|
||||
toolBar.getChildren().setAll(upgradeButton, testGameButton, browseMenuButton, managementMenuButton);
|
||||
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.add(upgradeItem)
|
||||
.add(testGameItem)
|
||||
.add(browseMenuItem)
|
||||
.add(managementItem);
|
||||
toolbar.getStyleClass().add("advanced-list-box-clear-padding");
|
||||
FXUtils.setLimitHeight(toolbar, 40 * 4 + 18);
|
||||
left.setBottom(toolbar);
|
||||
}
|
||||
|
||||
BorderPane titleBar = new BorderPane();
|
||||
titleBar.setLeft(tabPane);
|
||||
titleBar.setRight(toolBar);
|
||||
control.state.set(new State(i18n("version.manage.manage"), titleBar, true, false, true, false, 200));
|
||||
control.state.bind(Bindings.createObjectBinding(() ->
|
||||
new State(i18n("version.manage.manage.title", getSkinnable().getVersion()), null, true, false, true, false, 200),
|
||||
getSkinnable().version));
|
||||
|
||||
//control.transitionPane.getStyleClass().add("gray-background");
|
||||
//FXUtils.setOverflowHidden(control.transitionPane, 8);
|
||||
@@ -395,4 +349,14 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
getChildren().setAll(spinnerPane);
|
||||
}
|
||||
}
|
||||
|
||||
public static Node wrap(Node node) {
|
||||
StackPane stackPane = new StackPane();
|
||||
FXUtils.setLimitWidth(stackPane, 30);
|
||||
FXUtils.setLimitHeight(stackPane, 20);
|
||||
stackPane.setPadding(new Insets(0, 10, 0, 0));
|
||||
stackPane.getChildren().setAll(node);
|
||||
return stackPane;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -33,6 +34,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -50,13 +52,27 @@ public final class Versions {
|
||||
private Versions() {
|
||||
}
|
||||
|
||||
public static void addNewGame() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(profile), i18n("install.new_game"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void importModpack() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile), i18n("install.modpack"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;
|
||||
boolean isMovingToTrashSupported = FileUtils.isMovingToTrashSupported();
|
||||
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
|
||||
isMovingToTrashSupported ? i18n("version.manage.remove.confirm.trash", version, version + "_removed") :
|
||||
i18n("version.manage.remove.confirm", version);
|
||||
Controllers.confirm(message, i18n("message.confirm"), () -> {
|
||||
Controllers.confirm(message, i18n("message.warning"), MessageDialogPane.MessageType.WARNING, () -> {
|
||||
profile.getRepository().removeVersionFromDisk(version);
|
||||
}, null);
|
||||
}
|
||||
@@ -158,7 +174,7 @@ public final class Versions {
|
||||
Controllers.getRootPage().checkAccount();
|
||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
||||
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
|
||||
Controllers.getRootPage().getSelectionModel().select(Controllers.getRootPage().getGameTab());
|
||||
Controllers.navigate(Controllers.getGameListPage());
|
||||
});
|
||||
else
|
||||
return true;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
-fx-base-color: %base-color%;
|
||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-rippler-color: %base-rippler-color%;
|
||||
-fx-base-rippler-color: derive(%base-rippler-color%, 100%);
|
||||
-fx-base-disabled-text-fill: %disabled-font-color%;
|
||||
-fx-base-text-fill: %font-color%;
|
||||
|
||||
|
||||
@@ -49,17 +49,54 @@
|
||||
}
|
||||
|
||||
.rippler-container {
|
||||
-jfx-rippler-fill: -fx-base-check-color;
|
||||
-jfx-rippler-fill: derive(-fx-base-color, 100%);
|
||||
}
|
||||
|
||||
.rippler-container:selected .label {
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
}
|
||||
|
||||
.advanced-list-item .container {
|
||||
-fx-padding: 10 16 10 16;
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
.advanced-list-item .container VBox {
|
||||
-fx-padding: 0 0 0 10;
|
||||
}
|
||||
|
||||
.advanced-list-item .container .image-container {
|
||||
-fx-max-width: 32;
|
||||
-fx-min-width: 32;
|
||||
-fx-max-height: 32;
|
||||
-fx-min-height: 32;
|
||||
}
|
||||
|
||||
.advanced-list-item .container .title {
|
||||
-fx-max-width: 90;
|
||||
-fx-font-size: 13;
|
||||
-fx-text-alignment: justify;
|
||||
}
|
||||
|
||||
.advanced-list-item .container .subtitle {
|
||||
-fx-max-width: 90;
|
||||
-fx-font-size: 10;
|
||||
-fx-text-alignment: justify;
|
||||
}
|
||||
|
||||
.advanced-list-item:selected .container {
|
||||
-fx-background-color: -fx-base-rippler-color;
|
||||
}
|
||||
|
||||
.advanced-list-item:selected .container .title {
|
||||
-fx-text-fill: -fx-base-color;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.navigation-drawer-item .container VBox {
|
||||
-fx-padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.notice-pane > .label {
|
||||
-fx-text-fill: #0079FF;
|
||||
-fx-font-size: 20;
|
||||
@@ -69,15 +106,15 @@
|
||||
|
||||
.class-title {
|
||||
-fx-font-size: 12px;
|
||||
-fx-padding: 0 16 0 16;
|
||||
-fx-padding: 16 16 8 16;
|
||||
}
|
||||
|
||||
.advanced-list-box-item {
|
||||
-fx-padding: 0 16 0 16;
|
||||
}
|
||||
|
||||
.advanced-list-box-content {
|
||||
-fx-padding: 20 0 20 0;
|
||||
-fx-padding: 18 0 0 0;
|
||||
-fx-spacing: 0;
|
||||
}
|
||||
|
||||
.iconed-item {
|
||||
|
||||
@@ -461,6 +461,7 @@ version.manage.duplicate.duplicate_save=複製存檔
|
||||
version.manage.duplicate.prompt=請輸入新遊戲實例名稱
|
||||
version.manage.duplicate.confirm=將鎖定複製產生的新遊戲實例:強制版本隔離、遊戲設置獨立。
|
||||
version.manage.manage=遊戲管理
|
||||
version.manage.manage.title=遊戲管理 - %1s
|
||||
version.manage.redownload_assets_index=更新遊戲資源檔案
|
||||
version.manage.remove=刪除該版本
|
||||
version.manage.remove.confirm=真的要刪除版本 %s 嗎? 你將無法找回被刪除的檔案!
|
||||
|
||||
@@ -22,7 +22,7 @@ about.copyright.statement=版权所有 (c) 2021 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鸣谢
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有通过 Issues、Pull Requests 等方式参与本项目的贡献者
|
||||
about.thanks_to.statement=yushijinhun (authlib-injector 相关支持)\nbangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有通过 Issues、Pull Requests 等方式参与本项目的贡献者
|
||||
about.dependency=依赖
|
||||
# 由于篇幅限制,仅列出第一作者
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)\nConstant Pool Scanner (jenkins-ci, CDDL, GPL 2)\nOpenNBT (Steveice10, BSD 3-Clause)
|
||||
@@ -347,7 +347,7 @@ profile.instance_directory=游戏路径
|
||||
profile.instance_directory.choose=选择游戏路径
|
||||
profile.manage=游戏目录列表
|
||||
profile.name=名称
|
||||
profile.new=新建配置
|
||||
profile.new=添加游戏目录
|
||||
profile.title=游戏目录
|
||||
profile.selected=已选中
|
||||
profile.use_relative_path=若可能,游戏目录使用相对路径
|
||||
@@ -468,6 +468,7 @@ version.manage.duplicate.duplicate_save=复制存档
|
||||
version.manage.duplicate.prompt=请输入新游戏实例名称
|
||||
version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:强制版本隔离、游戏设置独立。
|
||||
version.manage.manage=游戏管理
|
||||
version.manage.manage.title=游戏管理 - %1s
|
||||
version.manage.redownload_assets_index=更新游戏资源文件
|
||||
version.manage.remove=删除该版本
|
||||
version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!
|
||||
|
||||
Reference in New Issue
Block a user