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);
|
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> {
|
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(Profile src, Type typeOfSrc, JsonSerializationContext context) {
|
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.Metadata;
|
||||||
import org.jackhuang.hmcl.download.java.JavaRepository;
|
import org.jackhuang.hmcl.download.java.JavaRepository;
|
||||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||||
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
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.PromptDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
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.main.RootPage;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.GameListPage;
|
||||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||||
import org.jackhuang.hmcl.util.FutureCallback;
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -62,6 +66,7 @@ public final class Controllers {
|
|||||||
private static Scene scene;
|
private static Scene scene;
|
||||||
private static Stage stage;
|
private static Stage stage;
|
||||||
private static VersionPage versionPage = null;
|
private static VersionPage versionPage = null;
|
||||||
|
private static GameListPage gameListPage = null;
|
||||||
private static AuthlibInjectorServersPage serversPage = null;
|
private static AuthlibInjectorServersPage serversPage = null;
|
||||||
private static RootPage rootPage;
|
private static RootPage rootPage;
|
||||||
private static DecoratorController decorator;
|
private static DecoratorController decorator;
|
||||||
@@ -84,6 +89,20 @@ public final class Controllers {
|
|||||||
return versionPage;
|
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
|
// FXThread
|
||||||
public static RootPage getRootPage() {
|
public static RootPage getRootPage() {
|
||||||
if (rootPage == null)
|
if (rootPage == null)
|
||||||
@@ -167,7 +186,11 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void confirm(String text, String title, Runnable onAccept, Runnable onCancel) {
|
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) {
|
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) {
|
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);
|
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;
|
package org.jackhuang.hmcl.ui.account;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
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.collections.ObservableList;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
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.ui.FXUtils.newImage;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class AccountAdvancedListItem extends AdvancedListItem {
|
public class AccountAdvancedListItem extends AdvancedListItem {
|
||||||
private final Tooltip tooltip;
|
private final Tooltip tooltip;
|
||||||
|
private final ImageView imageView;
|
||||||
|
|
||||||
private ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
|
private ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
|
||||||
|
|
||||||
@@ -47,23 +50,26 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
|||||||
titleProperty().unbind();
|
titleProperty().unbind();
|
||||||
setTitle(i18n("account.missing"));
|
setTitle(i18n("account.missing"));
|
||||||
setSubtitle(i18n("account.missing.add"));
|
setSubtitle(i18n("account.missing.add"));
|
||||||
imageProperty().unbind();
|
imageView.imageProperty().unbind();
|
||||||
setImage(newImage("/assets/img/craft_table.png"));
|
imageView.setImage(newImage("/assets/img/craft_table.png"));
|
||||||
tooltip.setText("");
|
tooltip.setText("");
|
||||||
} else {
|
} else {
|
||||||
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
|
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
|
||||||
setSubtitle(accountSubtitle(account));
|
setSubtitle(accountSubtitle(account));
|
||||||
imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
|
imageView.imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
|
||||||
tooltip.setText(account.getCharacter() + " " + accountTooltip(account));
|
tooltip.setText(account.getCharacter() + " " + accountTooltip(account));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public AccountAdvancedListItem() {
|
public AccountAdvancedListItem() {
|
||||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
|
||||||
tooltip = new Tooltip();
|
tooltip = new Tooltip();
|
||||||
FXUtils.installFastTooltip(this, tooltip);
|
FXUtils.installFastTooltip(this, tooltip);
|
||||||
|
|
||||||
|
Pair<Node, ImageView> view = createImageView(null);
|
||||||
|
setLeftGraphic(view.getKey());
|
||||||
|
imageView = view.getValue();
|
||||||
|
|
||||||
setOnScroll(event -> {
|
setOnScroll(event -> {
|
||||||
Account current = account.get();
|
Account current = account.get();
|
||||||
if (current == null) return;
|
if (current == null) return;
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public class AdvancedListBox extends ScrollPane {
|
|||||||
setFitToWidth(true);
|
setFitToWidth(true);
|
||||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||||
|
|
||||||
container.setSpacing(5);
|
|
||||||
container.getStyleClass().add("advanced-list-box-content");
|
container.getStyleClass().add("advanced-list-box-content");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,16 @@ import javafx.scene.Node;
|
|||||||
import javafx.scene.control.Control;
|
import javafx.scene.control.Control;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.MouseEvent;
|
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 {
|
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 ObjectProperty<Node> rightGraphic = new SimpleObjectProperty<>(this, "rightGraphic");
|
||||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||||
private final BooleanProperty active = new SimpleBooleanProperty(this, "active");
|
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()));
|
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Image getImage() {
|
public Node getLeftGraphic() {
|
||||||
return image.get();
|
return leftGraphic.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<Image> imageProperty() {
|
public ObjectProperty<Node> leftGraphicProperty() {
|
||||||
return image;
|
return leftGraphic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImage(Image image) {
|
public void setLeftGraphic(Node leftGraphic) {
|
||||||
this.image.set(image);
|
this.leftGraphic.set(leftGraphic);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Node getRightGraphic() {
|
public Node getRightGraphic() {
|
||||||
@@ -134,4 +140,17 @@ public class AdvancedListItem extends Control {
|
|||||||
protected Skin<?> createDefaultSkin() {
|
protected Skin<?> createDefaultSkin() {
|
||||||
return new AdvancedListItemSkin(this);
|
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;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.text.TextAlignment;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
|
||||||
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||||
@@ -48,35 +45,22 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
|||||||
root.setPickOnBounds(false);
|
root.setPickOnBounds(false);
|
||||||
|
|
||||||
HBox left = new HBox();
|
HBox left = new HBox();
|
||||||
left.setAlignment(Pos.CENTER);
|
left.setAlignment(Pos.CENTER_LEFT);
|
||||||
left.setMouseTransparent(true);
|
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();
|
VBox vbox = new VBox();
|
||||||
|
root.setCenter(vbox);
|
||||||
|
vbox.setMouseTransparent(true);
|
||||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||||
vbox.setPadding(new Insets(0, 0, 0, 10));
|
|
||||||
|
|
||||||
Label title = new Label();
|
Label title = new Label();
|
||||||
title.textProperty().bind(skinnable.titleProperty());
|
title.textProperty().bind(skinnable.titleProperty());
|
||||||
title.setMaxWidth(90);
|
title.getStyleClass().add("title");
|
||||||
title.setStyle("-fx-font-size: 15;");
|
|
||||||
title.setTextAlignment(TextAlignment.JUSTIFY);
|
|
||||||
vbox.getChildren().add(title);
|
vbox.getChildren().add(title);
|
||||||
|
|
||||||
Label subtitle = new Label();
|
Label subtitle = new Label();
|
||||||
subtitle.textProperty().bind(skinnable.subtitleProperty());
|
subtitle.textProperty().bind(skinnable.subtitleProperty());
|
||||||
subtitle.setMaxWidth(90);
|
subtitle.getStyleClass().add("subtitle");
|
||||||
subtitle.setStyle("-fx-font-size: 10;");
|
|
||||||
subtitle.setTextAlignment(TextAlignment.JUSTIFY);
|
|
||||||
vbox.getChildren().add(subtitle);
|
vbox.getChildren().add(subtitle);
|
||||||
|
|
||||||
FXUtils.onChangeAndOperate(skinnable.subtitleProperty(), subtitleString -> {
|
FXUtils.onChangeAndOperate(skinnable.subtitleProperty(), subtitleString -> {
|
||||||
@@ -84,7 +68,14 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
|||||||
else vbox.getChildren().setAll(title, subtitle);
|
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);
|
root.setLeft(left);
|
||||||
|
|
||||||
HBox right = new HBox();
|
HBox right = new HBox();
|
||||||
@@ -105,8 +96,6 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
|||||||
FXUtils.onChangeAndOperate(skinnable.actionButtonVisibleProperty(),
|
FXUtils.onChangeAndOperate(skinnable.actionButtonVisibleProperty(),
|
||||||
visible -> root.setRight(visible ? right : null));
|
visible -> root.setRight(visible ? right : null));
|
||||||
|
|
||||||
stackPane.setStyle("-fx-padding: 10 16 10 16;");
|
|
||||||
stackPane.getStyleClass().add("transparent");
|
|
||||||
stackPane.setPickOnBounds(false);
|
stackPane.setPickOnBounds(false);
|
||||||
stackPane.getChildren().setAll(root);
|
stackPane.getChildren().setAll(root);
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ public final class MessageDialogPane extends StackPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
|
public MessageDialogPane(String text, String title, MessageType type, Runnable onAccept, Runnable onCancel) {
|
||||||
this(text, title, MessageType.QUESTION, onAccept);
|
this(text, title, type, onAccept);
|
||||||
|
|
||||||
cancelButton.setVisible(true);
|
cancelButton.setVisible(true);
|
||||||
cancelButton.setOnMouseClicked(e -> {
|
cancelButton.setOnMouseClicked(e -> {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ObservableList<Tab> tabs = FXCollections.observableArrayList();
|
private ObservableList<Tab> tabs = FXCollections.observableArrayList();
|
||||||
|
private ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObservableList<Tab> getTabs() {
|
public ObservableList<Tab> getTabs() {
|
||||||
@@ -69,6 +70,27 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
this.selectionModel.set(selectionModel);
|
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
|
@Override
|
||||||
protected Skin<?> createDefaultSkin() {
|
protected Skin<?> createDefaultSkin() {
|
||||||
return new TabHeaderSkin(this);
|
return new TabHeaderSkin(this);
|
||||||
@@ -115,7 +137,7 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
protected class HeaderContainer extends StackPane {
|
protected class HeaderContainer extends StackPane {
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
private StackPane selectedTabLine;
|
private StackPane selectedTabLine;
|
||||||
private StackPane headersRegion;
|
private HeadersRegion headersRegion;
|
||||||
private Scale scale = new Scale(1, 1, 0, 0);
|
private Scale scale = new Scale(1, 1, 0, 0);
|
||||||
private Rotate rotate = new Rotate(0, 0, 1);
|
private Rotate rotate = new Rotate(0, 0, 1);
|
||||||
private double selectedTabLineOffset;
|
private double selectedTabLineOffset;
|
||||||
@@ -125,9 +147,115 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
getStyleClass().add("tab-header-area");
|
getStyleClass().add("tab-header-area");
|
||||||
setPickOnBounds(false);
|
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
|
@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;
|
double width = 0;
|
||||||
for (Node child : getChildren()) {
|
for (Node child : getChildren()) {
|
||||||
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||||
@@ -137,7 +265,7 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected double computePrefHeight(double width) {
|
public double computePrefHeight(double width) {
|
||||||
double height = 0;
|
double height = 0;
|
||||||
for (Node child : getChildren()) {
|
for (Node child : getChildren()) {
|
||||||
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||||
@@ -146,13 +274,81 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
return snapSize(height) + snappedTopInset() + snappedBottomInset();
|
return snapSize(height) + snappedTopInset() + snappedBottomInset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void runTimeline(double newTransX, double newWidth) {
|
||||||
protected void layoutChildren() {
|
double lineWidth = selectedTabLine.prefWidth(-1.0D);
|
||||||
if (isSelectingTab) {
|
if (isAnimating()) {
|
||||||
animateSelectionLine();
|
timeline.stop();
|
||||||
isSelectingTab = false;
|
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 headerHeight = snapSize(prefHeight(-1));
|
||||||
double tabStartX = 0;
|
double tabStartX = 0;
|
||||||
for (Node node : getChildren()) {
|
for (Node node : getChildren()) {
|
||||||
@@ -171,115 +367,171 @@ public class TabHeader extends Control implements TabControl {
|
|||||||
snapSize(selectedTabLine.prefWidth(-1)),
|
snapSize(selectedTabLine.prefWidth(-1)),
|
||||||
snapSize(selectedTabLine.prefHeight(-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();
|
private class Bottom extends Horizontal {
|
||||||
double oldWidth = lineWidth * oldScaleX;
|
@Override
|
||||||
double oldTransX = this.selectedTabLine.getTranslateX();
|
public void layoutChildren() {
|
||||||
double newScaleX = newWidth * oldScaleX / oldWidth;
|
super.layoutChildren();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timeline = new Timeline(
|
double headerHeight = snapSize(prefHeight(-1));
|
||||||
new KeyFrame(
|
double tabStartX = 0;
|
||||||
Duration.ZERO,
|
for (Node node : getChildren()) {
|
||||||
new KeyValue(this.selectedTabLine.translateXProperty(), this.selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)
|
if (!(node instanceof TabHeaderContainer)) continue;
|
||||||
),
|
TabHeaderContainer child = (TabHeaderContainer) node;
|
||||||
new KeyFrame(
|
double w = snapSize(child.prefWidth(-1));
|
||||||
Duration.seconds(0.24D),
|
double h = snapSize(child.prefHeight(-1));
|
||||||
new KeyValue(this.scale.xProperty(), newScaleX, Interpolator.EASE_BOTH),
|
child.resize(w, h);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
child.relocate(tabStartX, snappedTopInset());
|
||||||
this.timeline.play();
|
tabStartX += w;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.construct.TabHeader;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorTabPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorTabPage;
|
||||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
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.GameAdvancedListItem;
|
||||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
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 {
|
public class RootPage extends DecoratorTabPage {
|
||||||
private MainPage mainPage = null;
|
private MainPage mainPage = null;
|
||||||
private SettingsPage settingsPage = null;
|
private SettingsPage settingsPage = null;
|
||||||
private GameList gameListPage = null;
|
|
||||||
private AccountList accountListPage = null;
|
private AccountList accountListPage = null;
|
||||||
private ProfileList profileListPage = null;
|
|
||||||
|
|
||||||
private final TabHeader.Tab mainTab = new TabHeader.Tab("main");
|
private final TabHeader.Tab mainTab = new TabHeader.Tab("main");
|
||||||
private final TabHeader.Tab settingsTab = new TabHeader.Tab("settings");
|
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 accountTab = new TabHeader.Tab("account");
|
||||||
private final TabHeader.Tab profileTab = new TabHeader.Tab("profile");
|
|
||||||
|
|
||||||
public RootPage() {
|
public RootPage() {
|
||||||
setLeftPaneWidth(200);
|
setLeftPaneWidth(200);
|
||||||
@@ -86,10 +79,8 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
|
|
||||||
mainTab.setNodeSupplier(this::getMainPage);
|
mainTab.setNodeSupplier(this::getMainPage);
|
||||||
settingsTab.setNodeSupplier(this::getSettingsPage);
|
settingsTab.setNodeSupplier(this::getSettingsPage);
|
||||||
gameTab.setNodeSupplier(this::getGameListPage);
|
|
||||||
accountTab.setNodeSupplier(this::getAccountListPage);
|
accountTab.setNodeSupplier(this::getAccountListPage);
|
||||||
profileTab.setNodeSupplier(this::getProfileListPage);
|
getTabs().setAll(mainTab, settingsTab, accountTab);
|
||||||
getTabs().setAll(mainTab, settingsTab, gameTab, accountTab, profileTab);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -151,17 +142,6 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
return settingsPage;
|
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() {
|
private AccountList getAccountListPage() {
|
||||||
if (accountListPage == null) {
|
if (accountListPage == null) {
|
||||||
accountListPage = new AccountList();
|
accountListPage = new AccountList();
|
||||||
@@ -171,15 +151,6 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
return accountListPage;
|
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() {
|
public Tab getMainTab() {
|
||||||
return mainTab;
|
return mainTab;
|
||||||
}
|
}
|
||||||
@@ -188,18 +159,10 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
return settingsTab;
|
return settingsTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tab getGameTab() {
|
|
||||||
return gameTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Tab getAccountTab() {
|
public Tab getAccountTab() {
|
||||||
return accountTab;
|
return accountTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tab getProfileTab() {
|
|
||||||
return profileTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectPage(Tab tab) {
|
private void selectPage(Tab tab) {
|
||||||
if (getSelectionModel().getSelectedItem() == tab) {
|
if (getSelectionModel().getSelectedItem() == tab) {
|
||||||
getSelectionModel().select(getMainTab());
|
getSelectionModel().select(getMainTab());
|
||||||
@@ -226,7 +189,7 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
Profile profile = Profiles.getSelectedProfile();
|
Profile profile = Profiles.getSelectedProfile();
|
||||||
String version = Profiles.getSelectedVersion();
|
String version = Profiles.getSelectedVersion();
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
control.selectPage(control.gameTab);
|
Controllers.navigate(Controllers.getGameListPage());
|
||||||
} else {
|
} else {
|
||||||
Versions.modifyGameSettings(profile, version);
|
Versions.modifyGameSettings(profile, version);
|
||||||
}
|
}
|
||||||
@@ -234,21 +197,14 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
|
|
||||||
// third item in left sidebar
|
// third item in left sidebar
|
||||||
AdvancedListItem gameItem = new AdvancedListItem();
|
AdvancedListItem gameItem = new AdvancedListItem();
|
||||||
gameItem.activeProperty().bind(control.gameTab.selectedProperty());
|
gameItem.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/bookshelf.png")).getKey());
|
||||||
gameItem.setImage(newImage("/assets/img/bookshelf.png"));
|
|
||||||
gameItem.setTitle(i18n("version.manage"));
|
gameItem.setTitle(i18n("version.manage"));
|
||||||
gameItem.setOnAction(e -> control.selectPage(control.gameTab));
|
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||||
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
// fifth item in left sidebar
|
// fifth item in left sidebar
|
||||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||||
launcherSettingsItem.activeProperty().bind(control.settingsTab.selectedProperty());
|
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.setTitle(i18n("settings.launcher"));
|
||||||
launcherSettingsItem.setOnAction(e -> control.selectPage(control.settingsTab));
|
launcherSettingsItem.setOnAction(e -> control.selectPage(control.settingsTab));
|
||||||
|
|
||||||
@@ -259,8 +215,6 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
.startCategory(i18n("version").toUpperCase())
|
.startCategory(i18n("version").toUpperCase())
|
||||||
.add(gameListItem)
|
.add(gameListItem)
|
||||||
.add(gameItem)
|
.add(gameItem)
|
||||||
.startCategory(i18n("profile.title").toUpperCase())
|
|
||||||
.add(profileListItem)
|
|
||||||
.startCategory(i18n("launcher").toUpperCase())
|
.startCategory(i18n("launcher").toUpperCase())
|
||||||
.add(launcherSettingsItem);
|
.add(launcherSettingsItem);
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class ProfileAdvancedListItem extends AdvancedListItem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public ProfileAdvancedListItem() {
|
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));
|
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) {
|
public ProfileListItem(Profile profile) {
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
getStyleClass().clear();
|
getStyleClass().setAll("navigation-drawer-item");
|
||||||
setUserData(profile);
|
setUserData(profile);
|
||||||
|
|
||||||
title.set(Profiles.getProfileDisplayName(profile));
|
title.set(Profiles.getProfileDisplayName(profile));
|
||||||
|
|||||||
@@ -18,20 +18,14 @@
|
|||||||
package org.jackhuang.hmcl.ui.profile;
|
package org.jackhuang.hmcl.ui.profile;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXRadioButton;
|
|
||||||
import com.jfoenix.effects.JFXDepthManager;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
|
||||||
|
|
||||||
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||||
|
|
||||||
public ProfileListItemSkin(ProfileListItem skinnable) {
|
public ProfileListItemSkin(ProfileListItem skinnable) {
|
||||||
@@ -39,28 +33,9 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
|||||||
|
|
||||||
BorderPane root = new BorderPane();
|
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();
|
TwoLineListItem item = new TwoLineListItem();
|
||||||
BorderPane.setAlignment(item, Pos.CENTER);
|
BorderPane.setAlignment(item, Pos.CENTER);
|
||||||
center.getChildren().setAll(imageView, item);
|
root.setCenter(item);
|
||||||
root.setCenter(center);
|
|
||||||
|
|
||||||
HBox right = new HBox();
|
HBox right = new HBox();
|
||||||
right.setAlignment(Pos.CENTER_RIGHT);
|
right.setAlignment(Pos.CENTER_RIGHT);
|
||||||
@@ -73,9 +48,6 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
|||||||
right.getChildren().add(btnRemove);
|
right.getChildren().add(btnRemove);
|
||||||
root.setRight(right);
|
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.titleProperty().bind(skinnable.titleProperty());
|
||||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||||
|
|
||||||
|
|||||||
@@ -17,35 +17,43 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
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.ui.FXUtils.newImage;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class GameAdvancedListItem extends AdvancedListItem {
|
public class GameAdvancedListItem extends AdvancedListItem {
|
||||||
private final Tooltip tooltip;
|
private final Tooltip tooltip;
|
||||||
|
private final ImageView imageView;
|
||||||
|
|
||||||
public GameAdvancedListItem() {
|
public GameAdvancedListItem() {
|
||||||
tooltip = new Tooltip();
|
tooltip = new Tooltip();
|
||||||
|
|
||||||
|
Pair<Node, ImageView> view = createImageView(null);
|
||||||
|
setLeftGraphic(view.getKey());
|
||||||
|
imageView = view.getValue();
|
||||||
|
|
||||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||||
if (version != null && Profiles.getSelectedProfile() != null &&
|
if (version != null && Profiles.getSelectedProfile() != null &&
|
||||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||||
FXUtils.installFastTooltip(this, tooltip);
|
FXUtils.installFastTooltip(this, tooltip);
|
||||||
setTitle(version);
|
setTitle(version);
|
||||||
setSubtitle(null);
|
setSubtitle(null);
|
||||||
setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||||
tooltip.setText(version);
|
tooltip.setText(version);
|
||||||
} else {
|
} else {
|
||||||
Tooltip.uninstall(this,tooltip);
|
Tooltip.uninstall(this,tooltip);
|
||||||
setTitle(i18n("version.empty"));
|
setTitle(i18n("version.empty"));
|
||||||
setSubtitle(i18n("version.empty.add"));
|
setSubtitle(i18n("version.empty.add"));
|
||||||
setImage(newImage("/assets/img/grass.png"));
|
imageView.setImage(newImage("/assets/img/grass.png"));
|
||||||
tooltip.setText("");
|
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;
|
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 com.jfoenix.controls.JFXPopup;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.util.Callback;
|
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
|
||||||
import org.jackhuang.hmcl.game.Version;
|
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
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.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Comparator;
|
import java.util.Optional;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
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;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class VersionPage extends Control implements DecoratorPage {
|
public class VersionPage extends Control implements DecoratorPage {
|
||||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||||
private final JFXListView<String> listView = new JFXListView<>();
|
|
||||||
private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab");
|
private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab");
|
||||||
private final VersionSettingsPage versionSettingsPage = new VersionSettingsPage();
|
private final VersionSettingsPage versionSettingsPage = new VersionSettingsPage();
|
||||||
private final TabHeader.Tab modListTab = new TabHeader.Tab("modListTab");
|
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 TransitionPane transitionPane = new TransitionPane();
|
||||||
private final ObjectProperty<TabHeader.Tab> selectedTab = new SimpleObjectProperty<>();
|
private final ObjectProperty<TabHeader.Tab> selectedTab = new SimpleObjectProperty<>();
|
||||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||||
|
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||||
private Profile profile;
|
|
||||||
private String version;
|
|
||||||
|
|
||||||
private String preferredVersionName = null;
|
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);
|
versionSettingsTab.setNode(versionSettingsPage);
|
||||||
modListTab.setNode(modListPage);
|
modListTab.setNode(modListPage);
|
||||||
installerListTab.setNode(installerListPage);
|
installerListTab.setNode(installerListPage);
|
||||||
worldListTab.setNode(worldListPage);
|
worldListTab.setNode(worldListPage);
|
||||||
|
|
||||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||||
}
|
|
||||||
|
|
||||||
private void loadVersions(Profile profile) {
|
selectedTab.set(versionSettingsTab);
|
||||||
HMCLGameRepository repository = profile.getRepository();
|
FXUtils.onChangeAndOperate(selectedTab, newValue -> {
|
||||||
List<String> children = repository.getVersions().parallelStream()
|
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||||
.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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVersion(String version, Profile profile) {
|
public void setVersion(String version, Profile profile) {
|
||||||
this.version = version;
|
this.version.set(new Profile.ProfileVersion(profile, version));
|
||||||
this.profile = profile;
|
|
||||||
|
|
||||||
profile.setSelectedVersion(version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadVersion(String version, Profile profile) {
|
public void loadVersion(String version, Profile profile) {
|
||||||
// If we jumped to game list page and deleted this version
|
// If we jumped to game list page and deleted this version
|
||||||
// and back to this page, we should return to main page.
|
// and back to this page, we should return to main page.
|
||||||
if (!this.profile.getRepository().isLoaded() ||
|
if (this.version.get() != null && (!getProfile().getRepository().isLoaded() ||
|
||||||
!this.profile.getRepository().hasVersion(version)) {
|
!getProfile().getRepository().hasVersion(version))) {
|
||||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setVersion(version, profile);
|
setVersion(version, profile);
|
||||||
preferredVersionName = version;
|
preferredVersionName = version;
|
||||||
listView.getSelectionModel().select(version);
|
|
||||||
|
|
||||||
versionSettingsPage.loadVersion(profile, version);
|
versionSettingsPage.loadVersion(profile, version);
|
||||||
currentVersionUpgradable.set(profile.getRepository().isModpack(version));
|
currentVersionUpgradable.set(profile.getRepository().isModpack(version));
|
||||||
@@ -146,63 +101,71 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onNavigated(Navigator.NavigationEvent event) {
|
private void onNavigated(Navigator.NavigationEvent event) {
|
||||||
if (this.version == null || this.profile == null)
|
if (this.version.get() == null)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
||||||
// If we jumped to game list page and deleted this version
|
// If we jumped to game list page and deleted this version
|
||||||
// and back to this page, we should return to main page.
|
// and back to this page, we should return to main page.
|
||||||
if (!this.profile.getRepository().isLoaded() ||
|
if (!getProfile().getRepository().isLoaded() ||
|
||||||
!this.profile.getRepository().hasVersion(version)) {
|
!getProfile().getRepository().hasVersion(getVersion())) {
|
||||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadVersion(this.version, this.profile);
|
loadVersion(getVersion(), getProfile());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBrowse(String sub) {
|
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() {
|
private void redownloadAssetIndex() {
|
||||||
Versions.updateGameAssets(profile, version);
|
Versions.updateGameAssets(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearLibraries() {
|
private void clearLibraries() {
|
||||||
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
|
FileUtils.deleteDirectoryQuietly(new File(getProfile().getRepository().getBaseDirectory(), "libraries"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearJunkFiles() {
|
private void clearJunkFiles() {
|
||||||
Versions.cleanVersion(profile, version);
|
Versions.cleanVersion(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testGame() {
|
private void testGame() {
|
||||||
Versions.testGame(profile, version);
|
Versions.testGame(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGame() {
|
private void updateGame() {
|
||||||
Versions.updateVersion(profile, version);
|
Versions.updateVersion(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateLaunchScript() {
|
private void generateLaunchScript() {
|
||||||
Versions.generateLaunchScript(profile, version);
|
Versions.generateLaunchScript(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void export() {
|
private void export() {
|
||||||
Versions.exportVersion(profile, version);
|
Versions.exportVersion(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rename() {
|
private void rename() {
|
||||||
Versions.renameVersion(profile, version)
|
Versions.renameVersion(getProfile(), getVersion())
|
||||||
.thenApply(newVersionName -> this.preferredVersionName = newVersionName);
|
.thenApply(newVersionName -> this.preferredVersionName = newVersionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void remove() {
|
private void remove() {
|
||||||
Versions.deleteVersion(profile, version);
|
Versions.deleteVersion(getProfile(), getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void duplicate() {
|
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
|
@Override
|
||||||
@@ -217,7 +180,6 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
|
|
||||||
public static class Skin extends SkinBase<VersionPage> {
|
public static class Skin extends SkinBase<VersionPage> {
|
||||||
|
|
||||||
String currentVersion;
|
|
||||||
private JFXPopup listViewItemPopup;
|
private JFXPopup listViewItemPopup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,46 +194,30 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
listViewItemPopup = new JFXPopup(menu);
|
listViewItemPopup = new JFXPopup(menu);
|
||||||
menu.getContent().setAll(
|
menu.getContent().setAll(
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(() -> {
|
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)),
|
}, listViewItemPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(() -> {
|
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)),
|
}, listViewItemPopup)),
|
||||||
new MenuSeparator(),
|
new MenuSeparator(),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> {
|
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)),
|
}, listViewItemPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.copy(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.duplicate"), FXUtils.withJFXPopupClosing(() -> {
|
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)),
|
}, listViewItemPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> {
|
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)),
|
}, listViewItemPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(() -> {
|
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)),
|
}, listViewItemPopup)),
|
||||||
new MenuSeparator(),
|
new MenuSeparator(),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> {
|
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))
|
}, 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 spinnerPane = new SpinnerPane();
|
||||||
spinnerPane.getStyleClass().add("large-spinner-pane");
|
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||||
|
|
||||||
@@ -281,45 +227,48 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
{
|
{
|
||||||
BorderPane left = new BorderPane();
|
BorderPane left = new BorderPane();
|
||||||
FXUtils.setLimitWidth(left, 200);
|
FXUtils.setLimitWidth(left, 200);
|
||||||
left.setCenter(control.listView);
|
|
||||||
root.setLeft(left);
|
root.setLeft(left);
|
||||||
|
|
||||||
JFXButton btnAddGame = ToolbarListPageSkin.createToolbarButton(null, SVG::plus, GameList::addNewGame);
|
AdvancedListItem versionSettingsItem = new AdvancedListItem();
|
||||||
FXUtils.installFastTooltip(btnAddGame, new Tooltip(i18n("install.new_game")));
|
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);
|
AdvancedListItem modListItem = new AdvancedListItem();
|
||||||
FXUtils.installFastTooltip(btnImportModpack, new Tooltip(i18n("install.modpack")));
|
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);
|
AdvancedListItem installerListItem = new AdvancedListItem();
|
||||||
FXUtils.installFastTooltip(btnRefresh, new Tooltip(i18n("button.refresh")));
|
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();
|
AdvancedListItem worldListItem = new AdvancedListItem();
|
||||||
toolbar.setPickOnBounds(false);
|
worldListItem.getStyleClass().add("navigation-drawer-item");
|
||||||
toolbar.getChildren().setAll(btnAddGame, btnImportModpack, btnRefresh);
|
worldListItem.setTitle(i18n("world"));
|
||||||
left.setTop(toolbar);
|
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();
|
PopupMenu browseList = new PopupMenu();
|
||||||
JFXPopup browsePopup = new JFXPopup(browseList);
|
JFXPopup browsePopup = new JFXPopup(browseList);
|
||||||
browseList.getContent().setAll(
|
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"))
|
new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(control::clearJunkFiles, managementPopup)).addTooltip(i18n("version.manage.clean.tooltip"))
|
||||||
);
|
);
|
||||||
|
|
||||||
JFXButton upgradeButton = new JFXButton();
|
AdvancedListItem upgradeItem = new AdvancedListItem();
|
||||||
upgradeButton.setGraphic(SVG.update(null, 20, 20));
|
upgradeItem.getStyleClass().add("navigation-drawer-item");
|
||||||
upgradeButton.getStyleClass().add("jfx-decorator-button");
|
upgradeItem.setTitle(i18n("version.update"));
|
||||||
upgradeButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
upgradeItem.setLeftGraphic(wrap(SVG.update(Theme.blackFillBinding(), 20, 20)));
|
||||||
upgradeButton.setOnAction(event -> control.updateGame());
|
upgradeItem.setActionButtonVisible(false);
|
||||||
upgradeButton.visibleProperty().bind(control.currentVersionUpgradable);
|
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
||||||
FXUtils.installFastTooltip(upgradeButton, i18n("version.update"));
|
upgradeItem.setOnAction(e -> control.updateGame());
|
||||||
|
|
||||||
JFXButton testGameButton = new JFXButton();
|
AdvancedListItem testGameItem = new AdvancedListItem();
|
||||||
testGameButton.setGraphic(SVG.launch(null, 20, 20));
|
testGameItem.getStyleClass().add("navigation-drawer-item");
|
||||||
testGameButton.getStyleClass().add("jfx-decorator-button");
|
testGameItem.setTitle(i18n("version.launch.test"));
|
||||||
testGameButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
testGameItem.setLeftGraphic(wrap(SVG.launch(Theme.blackFillBinding(), 20, 20)));
|
||||||
testGameButton.setOnAction(event -> control.testGame());
|
testGameItem.setActionButtonVisible(false);
|
||||||
FXUtils.installFastTooltip(testGameButton, i18n("version.launch.test"));
|
testGameItem.setOnAction(e -> control.testGame());
|
||||||
|
|
||||||
JFXButton browseMenuButton = new JFXButton();
|
AdvancedListItem browseMenuItem = new AdvancedListItem();
|
||||||
browseMenuButton.setGraphic(SVG.folderOpen(null, 20, 20));
|
browseMenuItem.getStyleClass().add("navigation-drawer-item");
|
||||||
browseMenuButton.getStyleClass().add("jfx-decorator-button");
|
browseMenuItem.setTitle(i18n("settings.game.exploration"));
|
||||||
browseMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
browseMenuItem.setLeftGraphic(wrap(SVG.folderOpen(Theme.blackFillBinding(), 20, 20)));
|
||||||
browseMenuButton.setOnAction(event -> browsePopup.show(browseMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, browseMenuButton.getHeight()));
|
browseMenuItem.setActionButtonVisible(false);
|
||||||
FXUtils.installFastTooltip(browseMenuButton, i18n("settings.game.exploration"));
|
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
||||||
|
|
||||||
JFXButton managementMenuButton = new JFXButton();
|
AdvancedListItem managementItem = new AdvancedListItem();
|
||||||
FXUtils.setLimitWidth(managementMenuButton, 40);
|
managementItem.getStyleClass().add("navigation-drawer-item");
|
||||||
FXUtils.setLimitHeight(managementMenuButton, 40);
|
managementItem.setTitle(i18n("settings.game.management"));
|
||||||
managementMenuButton.setGraphic(SVG.wrench(null, 20, 20));
|
managementItem.setLeftGraphic(wrap(SVG.wrench(Theme.blackFillBinding(), 20, 20)));
|
||||||
managementMenuButton.getStyleClass().add("jfx-decorator-button");
|
managementItem.setActionButtonVisible(false);
|
||||||
managementMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
||||||
managementMenuButton.setOnAction(event -> managementPopup.show(managementMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, managementMenuButton.getHeight()));
|
|
||||||
FXUtils.installFastTooltip(managementMenuButton, i18n("settings.game.management"));
|
|
||||||
|
|
||||||
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();
|
control.state.bind(Bindings.createObjectBinding(() ->
|
||||||
titleBar.setLeft(tabPane);
|
new State(i18n("version.manage.manage.title", getSkinnable().getVersion()), null, true, false, true, false, 200),
|
||||||
titleBar.setRight(toolBar);
|
getSkinnable().version));
|
||||||
control.state.set(new State(i18n("version.manage.manage"), titleBar, true, false, true, false, 200));
|
|
||||||
|
|
||||||
//control.transitionPane.getStyleClass().add("gray-background");
|
//control.transitionPane.getStyleClass().add("gray-background");
|
||||||
//FXUtils.setOverflowHidden(control.transitionPane, 8);
|
//FXUtils.setOverflowHidden(control.transitionPane, 8);
|
||||||
@@ -395,4 +349,14 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
getChildren().setAll(spinnerPane);
|
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.game.LauncherHelper;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
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.PromptDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
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.ui.export.ExportWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
@@ -50,13 +52,27 @@ public final class Versions {
|
|||||||
private 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) {
|
public static void deleteVersion(Profile profile, String version) {
|
||||||
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;
|
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;
|
||||||
boolean isMovingToTrashSupported = FileUtils.isMovingToTrashSupported();
|
boolean isMovingToTrashSupported = FileUtils.isMovingToTrashSupported();
|
||||||
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
|
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
|
||||||
isMovingToTrashSupported ? i18n("version.manage.remove.confirm.trash", version, version + "_removed") :
|
isMovingToTrashSupported ? i18n("version.manage.remove.confirm.trash", version, version + "_removed") :
|
||||||
i18n("version.manage.remove.confirm", version);
|
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);
|
profile.getRepository().removeVersionFromDisk(version);
|
||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
@@ -158,7 +174,7 @@ public final class Versions {
|
|||||||
Controllers.getRootPage().checkAccount();
|
Controllers.getRootPage().checkAccount();
|
||||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
||||||
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
|
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
|
||||||
Controllers.getRootPage().getSelectionModel().select(Controllers.getRootPage().getGameTab());
|
Controllers.navigate(Controllers.getGameListPage());
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
-fx-base-color: %base-color%;
|
-fx-base-color: %base-color%;
|
||||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
-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-disabled-text-fill: %disabled-font-color%;
|
||||||
-fx-base-text-fill: %font-color%;
|
-fx-base-text-fill: %font-color%;
|
||||||
|
|
||||||
|
|||||||
@@ -49,17 +49,54 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rippler-container {
|
.rippler-container {
|
||||||
-jfx-rippler-fill: -fx-base-check-color;
|
-jfx-rippler-fill: derive(-fx-base-color, 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rippler-container:selected .label {
|
.rippler-container:selected .label {
|
||||||
-fx-text-fill: -fx-base-text-fill;
|
-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 {
|
.advanced-list-item:selected .container {
|
||||||
-fx-background-color: -fx-base-rippler-color;
|
-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 {
|
.notice-pane > .label {
|
||||||
-fx-text-fill: #0079FF;
|
-fx-text-fill: #0079FF;
|
||||||
-fx-font-size: 20;
|
-fx-font-size: 20;
|
||||||
@@ -69,15 +106,15 @@
|
|||||||
|
|
||||||
.class-title {
|
.class-title {
|
||||||
-fx-font-size: 12px;
|
-fx-font-size: 12px;
|
||||||
-fx-padding: 0 16 0 16;
|
-fx-padding: 16 16 8 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-list-box-item {
|
.advanced-list-box-item {
|
||||||
-fx-padding: 0 16 0 16;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-list-box-content {
|
.advanced-list-box-content {
|
||||||
-fx-padding: 20 0 20 0;
|
-fx-padding: 18 0 0 0;
|
||||||
|
-fx-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconed-item {
|
.iconed-item {
|
||||||
|
|||||||
@@ -461,6 +461,7 @@ version.manage.duplicate.duplicate_save=複製存檔
|
|||||||
version.manage.duplicate.prompt=請輸入新遊戲實例名稱
|
version.manage.duplicate.prompt=請輸入新遊戲實例名稱
|
||||||
version.manage.duplicate.confirm=將鎖定複製產生的新遊戲實例:強制版本隔離、遊戲設置獨立。
|
version.manage.duplicate.confirm=將鎖定複製產生的新遊戲實例:強制版本隔離、遊戲設置獨立。
|
||||||
version.manage.manage=遊戲管理
|
version.manage.manage=遊戲管理
|
||||||
|
version.manage.manage.title=遊戲管理 - %1s
|
||||||
version.manage.redownload_assets_index=更新遊戲資源檔案
|
version.manage.redownload_assets_index=更新遊戲資源檔案
|
||||||
version.manage.remove=刪除該版本
|
version.manage.remove=刪除該版本
|
||||||
version.manage.remove.confirm=真的要刪除版本 %s 嗎? 你將無法找回被刪除的檔案!
|
version.manage.remove.confirm=真的要刪除版本 %s 嗎? 你將無法找回被刪除的檔案!
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ about.copyright.statement=版权所有 (c) 2021 huangyuhui.
|
|||||||
about.author=作者
|
about.author=作者
|
||||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||||
about.thanks_to=鸣谢
|
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=依赖
|
||||||
# 由于篇幅限制,仅列出第一作者
|
# 由于篇幅限制,仅列出第一作者
|
||||||
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)
|
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.instance_directory.choose=选择游戏路径
|
||||||
profile.manage=游戏目录列表
|
profile.manage=游戏目录列表
|
||||||
profile.name=名称
|
profile.name=名称
|
||||||
profile.new=新建配置
|
profile.new=添加游戏目录
|
||||||
profile.title=游戏目录
|
profile.title=游戏目录
|
||||||
profile.selected=已选中
|
profile.selected=已选中
|
||||||
profile.use_relative_path=若可能,游戏目录使用相对路径
|
profile.use_relative_path=若可能,游戏目录使用相对路径
|
||||||
@@ -468,6 +468,7 @@ version.manage.duplicate.duplicate_save=复制存档
|
|||||||
version.manage.duplicate.prompt=请输入新游戏实例名称
|
version.manage.duplicate.prompt=请输入新游戏实例名称
|
||||||
version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:强制版本隔离、游戏设置独立。
|
version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:强制版本隔离、游戏设置独立。
|
||||||
version.manage.manage=游戏管理
|
version.manage.manage=游戏管理
|
||||||
|
version.manage.manage.title=游戏管理 - %1s
|
||||||
version.manage.redownload_assets_index=更新游戏资源文件
|
version.manage.redownload_assets_index=更新游戏资源文件
|
||||||
version.manage.remove=删除该版本
|
version.manage.remove=删除该版本
|
||||||
version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!
|
version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!
|
||||||
|
|||||||
Reference in New Issue
Block a user