Refactor decorator as well as navigation
This commit is contained in:
@@ -60,21 +60,11 @@ public class AdvancedListItem2 extends Control {
|
|||||||
return onActionProperty().get();
|
return onActionProperty().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
|
private ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<EventHandler<ActionEvent>>(this, "onAction") {
|
||||||
@Override
|
@Override
|
||||||
protected void invalidated() {
|
protected void invalidated() {
|
||||||
setEventHandler(ActionEvent.ACTION, get());
|
setEventHandler(ActionEvent.ACTION, get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBean() {
|
|
||||||
return AdvancedListItem2.this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "onAction";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
|||||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||||
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.profile.ProfileList;
|
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
import org.jackhuang.hmcl.ui.versions.GameList;
|
||||||
import org.jackhuang.hmcl.util.FutureCallback;
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
@@ -53,7 +54,7 @@ public final class Controllers {
|
|||||||
private static ProfileList profileListPage = null;
|
private static ProfileList profileListPage = null;
|
||||||
private static AuthlibInjectorServersPage serversPage = null;
|
private static AuthlibInjectorServersPage serversPage = null;
|
||||||
private static LeftPaneController leftPaneController;
|
private static LeftPaneController leftPaneController;
|
||||||
private static Decorator decorator;
|
private static DecoratorController decorator;
|
||||||
|
|
||||||
public static Scene getScene() {
|
public static Scene getScene() {
|
||||||
return scene;
|
return scene;
|
||||||
@@ -106,7 +107,7 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FXThread
|
// FXThread
|
||||||
public static Decorator getDecorator() {
|
public static DecoratorController getDecorator() {
|
||||||
return decorator;
|
return decorator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,20 +126,14 @@ public final class Controllers {
|
|||||||
|
|
||||||
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
||||||
|
|
||||||
decorator = new Decorator(stage, getMainPage(), Metadata.TITLE, false, true);
|
decorator = new DecoratorController(stage, getMainPage());
|
||||||
decorator.showPage(null);
|
leftPaneController = new LeftPaneController();
|
||||||
leftPaneController = new LeftPaneController(decorator.getLeftPane());
|
decorator.getDecorator().drawerProperty().setAll(leftPaneController);
|
||||||
|
|
||||||
Task.of(JavaVersion::initialize).start();
|
Task.of(JavaVersion::initialize).start();
|
||||||
|
|
||||||
decorator.setCustomMaximize(false);
|
scene = new Scene(decorator.getDecorator(), 800, 519);
|
||||||
|
|
||||||
scene = new Scene(decorator, 804, 521);
|
|
||||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||||
stage.setMinWidth(804);
|
|
||||||
stage.setMaxWidth(804);
|
|
||||||
stage.setMinHeight(521);
|
|
||||||
stage.setMaxHeight(521);
|
|
||||||
|
|
||||||
stage.getIcons().add(new Image("/assets/img/icon.png"));
|
stage.getIcons().add(new Image("/assets/img/icon.png"));
|
||||||
stage.setTitle(Metadata.TITLE);
|
stage.setTitle(Metadata.TITLE);
|
||||||
@@ -189,10 +184,7 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void navigate(Node node) {
|
public static void navigate(Node node) {
|
||||||
if (decorator.getNowPage() == node)
|
decorator.getNavigator().navigate(node);
|
||||||
decorator.showPage(null);
|
|
||||||
else
|
|
||||||
decorator.showPage(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStopped() {
|
public static boolean isStopped() {
|
||||||
|
|||||||
@@ -1,733 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* 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 {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui;
|
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
|
||||||
import com.jfoenix.controls.JFXDialog;
|
|
||||||
import com.jfoenix.controls.JFXDrawer;
|
|
||||||
import com.jfoenix.controls.JFXHamburger;
|
|
||||||
import com.jfoenix.svg.SVGGlyph;
|
|
||||||
import javafx.animation.Interpolator;
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import javafx.animation.KeyValue;
|
|
||||||
import javafx.animation.Timeline;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.event.EventHandler;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.geometry.BoundingBox;
|
|
||||||
import javafx.geometry.Bounds;
|
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.geometry.Rectangle2D;
|
|
||||||
import javafx.scene.Cursor;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.input.DragEvent;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.*;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.shape.Rectangle;
|
|
||||||
import javafx.stage.Screen;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.stage.StageStyle;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import org.jackhuang.hmcl.Launcher;
|
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
|
||||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
|
||||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
|
||||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
|
||||||
import org.jackhuang.hmcl.ui.wizard.*;
|
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
|
||||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
|
||||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
|
||||||
|
|
||||||
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
|
|
||||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
|
||||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
|
||||||
private static final SVGGlyph resizeMax = Lang.apply(new SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE),
|
|
||||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
|
||||||
private static final SVGGlyph resizeMin = Lang.apply(new SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE),
|
|
||||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
|
||||||
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
|
|
||||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
|
||||||
|
|
||||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Decorator.class.getName() + ".dialog.closeListener";
|
|
||||||
|
|
||||||
private final ObjectProperty<Runnable> onCloseButtonAction;
|
|
||||||
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
|
|
||||||
|
|
||||||
private final Stage primaryStage;
|
|
||||||
private final Node mainPage;
|
|
||||||
private final boolean max, min;
|
|
||||||
private final WizardController wizardController = new WizardController(this);
|
|
||||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
|
||||||
private boolean allowMove, isDragging, maximized;
|
|
||||||
private BoundingBox originalBox, maximizedBox;
|
|
||||||
private final TransitionHandler animationHandler;
|
|
||||||
|
|
||||||
private JFXDialog dialog;
|
|
||||||
private StackContainerPane dialogPane;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private StackPane contentPlaceHolder;
|
|
||||||
@FXML
|
|
||||||
private StackPane drawerWrapper;
|
|
||||||
@FXML
|
|
||||||
private BorderPane titleContainer;
|
|
||||||
@FXML
|
|
||||||
private BorderPane leftRootPane;
|
|
||||||
@FXML
|
|
||||||
private HBox buttonsContainer;
|
|
||||||
@FXML
|
|
||||||
private JFXButton backNavButton;
|
|
||||||
@FXML
|
|
||||||
private JFXButton refreshNavButton;
|
|
||||||
@FXML
|
|
||||||
private JFXButton closeNavButton;
|
|
||||||
@FXML
|
|
||||||
private JFXButton refreshMenuButton;
|
|
||||||
@FXML
|
|
||||||
private Label titleLabel;
|
|
||||||
@FXML
|
|
||||||
private Label lblTitle;
|
|
||||||
@FXML
|
|
||||||
private AdvancedListBox leftPane;
|
|
||||||
@FXML
|
|
||||||
private JFXDrawer drawer;
|
|
||||||
@FXML
|
|
||||||
private StackPane titleBurgerContainer;
|
|
||||||
@FXML
|
|
||||||
private JFXHamburger titleBurger;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnMin;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnMax;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnClose;
|
|
||||||
@FXML
|
|
||||||
private HBox navLeft;
|
|
||||||
@FXML
|
|
||||||
private ImageView welcomeView;
|
|
||||||
@FXML
|
|
||||||
private Rectangle separator;
|
|
||||||
|
|
||||||
public Decorator(Stage primaryStage, Node mainPage, String title) {
|
|
||||||
this(primaryStage, mainPage, title, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Decorator(Stage primaryStage, Node mainPage, String title, boolean max, boolean min) {
|
|
||||||
this.primaryStage = primaryStage;
|
|
||||||
this.mainPage = mainPage;
|
|
||||||
this.max = max;
|
|
||||||
this.min = min;
|
|
||||||
|
|
||||||
FXUtils.loadFXML(this, "/assets/fxml/decorator.fxml");
|
|
||||||
|
|
||||||
onCloseButtonAction = new SimpleObjectProperty<>(this, "onCloseButtonAction", Launcher::stopApplication);
|
|
||||||
|
|
||||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
|
||||||
btnClose.setGraphic(close);
|
|
||||||
btnMin.setGraphic(minus);
|
|
||||||
btnMax.setGraphic(resizeMax);
|
|
||||||
|
|
||||||
close.fillProperty().bind(Theme.foregroundFillBinding());
|
|
||||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
|
||||||
resizeMax.fillProperty().bind(Theme.foregroundFillBinding());
|
|
||||||
resizeMin.fillProperty().bind(Theme.foregroundFillBinding());
|
|
||||||
|
|
||||||
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), 15, 15));
|
|
||||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), 15, 15));
|
|
||||||
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), 15, 15));
|
|
||||||
|
|
||||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
|
||||||
|
|
||||||
lblTitle.setText(title);
|
|
||||||
|
|
||||||
buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
|
|
||||||
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
|
||||||
if (event.getClickCount() == 2)
|
|
||||||
btnMax.fire();
|
|
||||||
});
|
|
||||||
|
|
||||||
welcomeView.setCursor(Cursor.HAND);
|
|
||||||
welcomeView.setOnMouseClicked(e -> {
|
|
||||||
Timeline nowAnimation = new Timeline();
|
|
||||||
nowAnimation.getKeyFrames().addAll(
|
|
||||||
new KeyFrame(Duration.ZERO, new KeyValue(welcomeView.opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(new Duration(300), new KeyValue(welcomeView.opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(new Duration(300), e2 -> drawerWrapper.getChildren().remove(welcomeView))
|
|
||||||
);
|
|
||||||
nowAnimation.play();
|
|
||||||
});
|
|
||||||
if (!ConfigHolder.isNewlyCreated() || config().getLocalization().getLocale() != Locale.CHINA)
|
|
||||||
drawerWrapper.getChildren().remove(welcomeView);
|
|
||||||
|
|
||||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
|
||||||
if (!max) buttonsContainer.getChildren().remove(btnMax);
|
|
||||||
|
|
||||||
//JFXDepthManager.setDepth(titleContainer, 1);
|
|
||||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
|
||||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
|
||||||
if (!isDragging) allowMove = false;
|
|
||||||
});
|
|
||||||
Rectangle rectangle = new Rectangle(0, 0, 0, 0);
|
|
||||||
rectangle.widthProperty().bind(titleContainer.widthProperty());
|
|
||||||
rectangle.heightProperty().bind(Bindings.createDoubleBinding(() -> titleContainer.getHeight() + 100, titleContainer.heightProperty()));
|
|
||||||
titleContainer.setClip(rectangle);
|
|
||||||
|
|
||||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
|
||||||
|
|
||||||
setupBackground();
|
|
||||||
setupAuthlibInjectorDnD();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==== Background ====
|
|
||||||
private void setupBackground() {
|
|
||||||
drawerWrapper.backgroundProperty().bind(
|
|
||||||
Bindings.createObjectBinding(
|
|
||||||
() -> {
|
|
||||||
Image image = null;
|
|
||||||
if (config().getBackgroundImageType() == EnumBackgroundImage.CUSTOM && config().getBackgroundImage() != null) {
|
|
||||||
image = tryLoadImage(Paths.get(config().getBackgroundImage()))
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
if (image == null) {
|
|
||||||
image = loadDefaultBackgroundImage();
|
|
||||||
}
|
|
||||||
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
|
|
||||||
},
|
|
||||||
config().backgroundImageTypeProperty(),
|
|
||||||
config().backgroundImageProperty()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load background image from bg/, background.png, background.jpg
|
|
||||||
*/
|
|
||||||
private Image loadDefaultBackgroundImage() {
|
|
||||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
|
||||||
if (!image.isPresent()) {
|
|
||||||
image = tryLoadImage(Paths.get("background.png"));
|
|
||||||
}
|
|
||||||
if (!image.isPresent()) {
|
|
||||||
image = tryLoadImage(Paths.get("background.jpg"));
|
|
||||||
}
|
|
||||||
return image.orElse(defaultBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Image> randomImageIn(Path imageDir) {
|
|
||||||
if (!Files.isDirectory(imageDir)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Path> candidates;
|
|
||||||
try {
|
|
||||||
candidates = Files.list(imageDir)
|
|
||||||
.filter(Files::isRegularFile)
|
|
||||||
.filter(it -> {
|
|
||||||
String filename = it.getFileName().toString();
|
|
||||||
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
|
||||||
})
|
|
||||||
.collect(toList());
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Random rnd = new Random();
|
|
||||||
while (candidates.size() > 0) {
|
|
||||||
int selected = rnd.nextInt(candidates.size());
|
|
||||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
|
||||||
if (loaded.isPresent()) {
|
|
||||||
return Optional.of(loaded.get());
|
|
||||||
} else {
|
|
||||||
candidates.remove(selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Image> tryLoadImage(Path path) {
|
|
||||||
if (Files.isRegularFile(path)) {
|
|
||||||
try {
|
|
||||||
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMaximized() {
|
|
||||||
switch (OperatingSystem.CURRENT_OS) {
|
|
||||||
case OSX:
|
|
||||||
Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
|
|
||||||
return primaryStage.getWidth() >= bounds.getWidth() && primaryStage.getHeight() >= bounds.getHeight();
|
|
||||||
default:
|
|
||||||
return primaryStage.isMaximized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
|
||||||
if (!isMaximized() && !primaryStage.isFullScreen() && !maximized) {
|
|
||||||
if (!primaryStage.isResizable())
|
|
||||||
updateInitMouseValues(mouseEvent);
|
|
||||||
else {
|
|
||||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
|
||||||
Bounds boundsInParent = getBoundsInParent();
|
|
||||||
if (getBorder() != null && getBorder().getStrokes().size() > 0) {
|
|
||||||
double borderWidth = this.contentPlaceHolder.snappedLeftInset();
|
|
||||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
|
||||||
if (y < borderWidth) {
|
|
||||||
setCursor(Cursor.NE_RESIZE);
|
|
||||||
} else if (y > this.getHeight() - borderWidth) {
|
|
||||||
setCursor(Cursor.SE_RESIZE);
|
|
||||||
} else {
|
|
||||||
setCursor(Cursor.E_RESIZE);
|
|
||||||
}
|
|
||||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
|
||||||
if (y < borderWidth) {
|
|
||||||
setCursor(Cursor.NW_RESIZE);
|
|
||||||
} else if (y > this.getHeight() - borderWidth) {
|
|
||||||
setCursor(Cursor.SW_RESIZE);
|
|
||||||
} else {
|
|
||||||
setCursor(Cursor.W_RESIZE);
|
|
||||||
}
|
|
||||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
|
||||||
setCursor(Cursor.N_RESIZE);
|
|
||||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
|
||||||
setCursor(Cursor.S_RESIZE);
|
|
||||||
} else {
|
|
||||||
setCursor(Cursor.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateInitMouseValues(mouseEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setCursor(Cursor.DEFAULT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onMouseReleased() {
|
|
||||||
isDragging = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onMouseDragged(MouseEvent mouseEvent) {
|
|
||||||
this.isDragging = true;
|
|
||||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
|
||||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress() && !isMaximized() && !this.maximized) {
|
|
||||||
this.newX = mouseEvent.getScreenX();
|
|
||||||
this.newY = mouseEvent.getScreenY();
|
|
||||||
double deltaX = this.newX - this.initX;
|
|
||||||
double deltaY = this.newY - this.initY;
|
|
||||||
Cursor cursor = this.getCursor();
|
|
||||||
if (Cursor.E_RESIZE == cursor) {
|
|
||||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.NE_RESIZE == cursor) {
|
|
||||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
|
||||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.SE_RESIZE == cursor) {
|
|
||||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
|
||||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.S_RESIZE == cursor) {
|
|
||||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.W_RESIZE == cursor) {
|
|
||||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
|
||||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.SW_RESIZE == cursor) {
|
|
||||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
|
||||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.NW_RESIZE == cursor) {
|
|
||||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
|
||||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
|
||||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (Cursor.N_RESIZE == cursor) {
|
|
||||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
|
||||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseEvent.consume();
|
|
||||||
} else if (this.allowMove) {
|
|
||||||
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
|
||||||
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
|
||||||
mouseEvent.consume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onMin() {
|
|
||||||
primaryStage.setIconified(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onMax() {
|
|
||||||
if (!max) return;
|
|
||||||
if (!this.isCustomMaximize()) {
|
|
||||||
this.primaryStage.setMaximized(!this.primaryStage.isMaximized());
|
|
||||||
this.maximized = this.primaryStage.isMaximized();
|
|
||||||
if (this.primaryStage.isMaximized()) {
|
|
||||||
this.btnMax.setGraphic(resizeMin);
|
|
||||||
this.btnMax.setTooltip(new Tooltip("Restore Down"));
|
|
||||||
} else {
|
|
||||||
this.btnMax.setGraphic(resizeMax);
|
|
||||||
this.btnMax.setTooltip(new Tooltip("Maximize"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!this.maximized) {
|
|
||||||
this.originalBox = new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
|
|
||||||
Screen screen = Screen.getScreensForRectangle(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()).get(0);
|
|
||||||
Rectangle2D bounds = screen.getVisualBounds();
|
|
||||||
this.maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
|
|
||||||
primaryStage.setX(this.maximizedBox.getMinX());
|
|
||||||
primaryStage.setY(this.maximizedBox.getMinY());
|
|
||||||
primaryStage.setWidth(this.maximizedBox.getWidth());
|
|
||||||
primaryStage.setHeight(this.maximizedBox.getHeight());
|
|
||||||
this.btnMax.setGraphic(resizeMin);
|
|
||||||
this.btnMax.setTooltip(new Tooltip("Restore Down"));
|
|
||||||
} else {
|
|
||||||
primaryStage.setX(this.originalBox.getMinX());
|
|
||||||
primaryStage.setY(this.originalBox.getMinY());
|
|
||||||
primaryStage.setWidth(this.originalBox.getWidth());
|
|
||||||
primaryStage.setHeight(this.originalBox.getHeight());
|
|
||||||
this.originalBox = null;
|
|
||||||
this.btnMax.setGraphic(resizeMax);
|
|
||||||
this.btnMax.setTooltip(new Tooltip("Maximize"));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.maximized = !this.maximized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onClose() {
|
|
||||||
onCloseButtonAction.get().run();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
|
||||||
initX = mouseEvent.getScreenX();
|
|
||||||
initY = mouseEvent.getScreenY();
|
|
||||||
xOffset = mouseEvent.getSceneX();
|
|
||||||
yOffset = mouseEvent.getSceneY();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
|
||||||
return x < getWidth() && x > getWidth() - contentPlaceHolder.snappedLeftInset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
|
|
||||||
return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
|
|
||||||
return y < getHeight() && y > getHeight() - contentPlaceHolder.snappedLeftInset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
|
|
||||||
return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setStageWidth(double width) {
|
|
||||||
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
|
||||||
primaryStage.setWidth(width);
|
|
||||||
initX = newX;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
|
||||||
primaryStage.setWidth(titleContainer.getMinWidth());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setStageHeight(double height) {
|
|
||||||
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
|
||||||
primaryStage.setHeight(height);
|
|
||||||
initY = newY;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
|
||||||
primaryStage.setHeight(titleContainer.getHeight());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaximized(boolean maximized) {
|
|
||||||
if (this.maximized != maximized) {
|
|
||||||
Platform.runLater(btnMax::fire);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showCloseNavButton() {
|
|
||||||
navLeft.getChildren().add(closeNavButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideCloseNavButton() {
|
|
||||||
navLeft.getChildren().remove(closeNavButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setContent(Node content, AnimationProducer animation) {
|
|
||||||
isWizardPageNow = false;
|
|
||||||
animationHandler.setContent(content, animation);
|
|
||||||
|
|
||||||
if (content instanceof Region) {
|
|
||||||
((Region) content).setMinSize(0, 0);
|
|
||||||
FXUtils.setOverflowHidden((Region) content, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshNavButton.setVisible(content instanceof Refreshable);
|
|
||||||
backNavButton.setVisible(content != mainPage);
|
|
||||||
|
|
||||||
String prefix = category == null ? "" : category + " - ";
|
|
||||||
|
|
||||||
titleLabel.textProperty().unbind();
|
|
||||||
|
|
||||||
if (content instanceof WizardPage)
|
|
||||||
titleLabel.setText(prefix + ((WizardPage) content).getTitle());
|
|
||||||
|
|
||||||
if (content instanceof DecoratorPage)
|
|
||||||
titleLabel.textProperty().bind(((DecoratorPage) content).titleProperty());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String category;
|
|
||||||
private Node nowPage;
|
|
||||||
private boolean isWizardPageNow;
|
|
||||||
|
|
||||||
public Node getNowPage() {
|
|
||||||
return nowPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showPage(Node content) {
|
|
||||||
FXUtils.checkFxUserThread();
|
|
||||||
|
|
||||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
|
||||||
if (content != null)
|
|
||||||
contentPlaceHolder.getStyleClass().add("gray-background");
|
|
||||||
|
|
||||||
Node c = content == null ? mainPage : content;
|
|
||||||
onEnd();
|
|
||||||
if (nowPage instanceof DecoratorPage)
|
|
||||||
((DecoratorPage) nowPage).onClose();
|
|
||||||
nowPage = content;
|
|
||||||
|
|
||||||
setContent(c, ContainerAnimations.FADE.getAnimationProducer());
|
|
||||||
|
|
||||||
if (c instanceof Region) {
|
|
||||||
// Let root pane fix window size.
|
|
||||||
StackPane parent = (StackPane) c.getParent();
|
|
||||||
((Region) c).prefWidthProperty().bind(parent.widthProperty());
|
|
||||||
((Region) c).prefHeightProperty().bind(parent.heightProperty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showDialog(Node node) {
|
|
||||||
FXUtils.checkFxUserThread();
|
|
||||||
|
|
||||||
if (dialog == null) {
|
|
||||||
dialog = new JFXDialog();
|
|
||||||
dialogPane = new StackContainerPane();
|
|
||||||
|
|
||||||
dialog.setContent(dialogPane);
|
|
||||||
dialog.setDialogContainer(drawerWrapper);
|
|
||||||
dialog.setOverlayClose(false);
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
dialogPane.push(node);
|
|
||||||
|
|
||||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
|
||||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
|
||||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void closeDialog(Node node) {
|
|
||||||
FXUtils.checkFxUserThread();
|
|
||||||
|
|
||||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
|
||||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
|
||||||
|
|
||||||
if (dialog != null) {
|
|
||||||
dialogPane.pop(node);
|
|
||||||
|
|
||||||
if (dialogPane.getChildren().isEmpty()) {
|
|
||||||
dialog.close();
|
|
||||||
dialog = null;
|
|
||||||
dialogPane = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startWizard(WizardProvider wizardProvider) {
|
|
||||||
startWizard(wizardProvider, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
|
||||||
FXUtils.checkFxUserThread();
|
|
||||||
|
|
||||||
this.category = category;
|
|
||||||
wizardController.setProvider(wizardProvider);
|
|
||||||
wizardController.onStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
backNavButton.setVisible(true);
|
|
||||||
backNavButton.setDisable(false);
|
|
||||||
showCloseNavButton();
|
|
||||||
refreshNavButton.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnd() {
|
|
||||||
backNavButton.setVisible(false);
|
|
||||||
hideCloseNavButton();
|
|
||||||
refreshNavButton.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
|
||||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
|
||||||
contentPlaceHolder.getStyleClass().add("white-background");
|
|
||||||
setContent(page, nav.getAnimation().getAnimationProducer());
|
|
||||||
isWizardPageNow = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onRefresh() {
|
|
||||||
((Refreshable) contentPlaceHolder.getChildren().get(0)).refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onCloseNav() {
|
|
||||||
wizardController.onCancel();
|
|
||||||
showPage(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onBack() {
|
|
||||||
if (isWizardPageNow && wizardController.canPrev())
|
|
||||||
wizardController.onPrev(true);
|
|
||||||
else
|
|
||||||
onCloseNav();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Queue<Object> getCancelQueue() {
|
|
||||||
return cancelQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Runnable getOnCloseButtonAction() {
|
|
||||||
return onCloseButtonAction.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
|
||||||
return onCloseButtonAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
|
||||||
this.onCloseButtonAction.set(onCloseButtonAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCustomMaximize() {
|
|
||||||
return customMaximize.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty customMaximizeProperty() {
|
|
||||||
return customMaximize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomMaximize(boolean customMaximize) {
|
|
||||||
this.customMaximize.set(customMaximize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WizardController getWizardController() {
|
|
||||||
return wizardController;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdvancedListBox getLeftPane() {
|
|
||||||
return leftPane;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupAuthlibInjectorDnD() {
|
|
||||||
addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
|
|
||||||
addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
|
|
||||||
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,9 +43,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class LeftPaneController {
|
public final class LeftPaneController extends AdvancedListBox {
|
||||||
|
|
||||||
|
public LeftPaneController() {
|
||||||
|
|
||||||
public LeftPaneController(AdvancedListBox leftPane) {
|
|
||||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||||
@@ -67,10 +68,10 @@ public final class LeftPaneController {
|
|||||||
.then(Color.RED)
|
.then(Color.RED)
|
||||||
.otherwise(Color.BLACK));
|
.otherwise(Color.BLACK));
|
||||||
|
|
||||||
launcherSettingsItem.maxWidthProperty().bind(leftPane.widthProperty());
|
launcherSettingsItem.maxWidthProperty().bind(widthProperty());
|
||||||
launcherSettingsItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
launcherSettingsItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||||
|
|
||||||
leftPane
|
this
|
||||||
.startCategory(i18n("account").toUpperCase())
|
.startCategory(i18n("account").toUpperCase())
|
||||||
.add(accountListItem)
|
.add(accountListItem)
|
||||||
.startCategory(i18n("version").toUpperCase())
|
.startCategory(i18n("version").toUpperCase())
|
||||||
|
|||||||
@@ -23,18 +23,17 @@ import javafx.fxml.FXML;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class MainPage extends StackPane implements DecoratorPage {
|
public final class MainPage extends StackPane implements DecoratorPage {
|
||||||
|
|
||||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("main_page"));
|
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("main_page"));
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import javafx.scene.paint.Color;
|
|||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import org.jackhuang.hmcl.setting.*;
|
import org.jackhuang.hmcl.setting.*;
|
||||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import javafx.scene.control.Tab;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
|
||||||
import org.jackhuang.hmcl.util.FileUtils;
|
import org.jackhuang.hmcl.util.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import javafx.scene.control.ToggleGroup;
|
|||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import javafx.scene.layout.Region;
|
|||||||
*/
|
*/
|
||||||
public class DialogCloseEvent extends Event {
|
public class DialogCloseEvent extends Event {
|
||||||
|
|
||||||
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("CLOSE");
|
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("DIALOG_CLOSE");
|
||||||
|
|
||||||
public DialogCloseEvent() {
|
public DialogCloseEvent() {
|
||||||
super(CLOSE);
|
super(CLOSE);
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.event.Event;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.event.EventTarget;
|
||||||
|
import javafx.event.EventType;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
public class Navigator extends StackPane {
|
||||||
|
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + ".closeListener";
|
||||||
|
|
||||||
|
private final Stack<Node> stack = new Stack<>();
|
||||||
|
private final TransitionHandler animationHandler = new TransitionHandler(this);
|
||||||
|
private final ReadOnlyBooleanWrapper canGoBack = new ReadOnlyBooleanWrapper();
|
||||||
|
|
||||||
|
public Navigator(Node init) {
|
||||||
|
stack.push(init);
|
||||||
|
getChildren().setAll(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigate(Node node) {
|
||||||
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
|
Node from = stack.peek();
|
||||||
|
if (from == node)
|
||||||
|
return;
|
||||||
|
|
||||||
|
stack.push(node);
|
||||||
|
fireEvent(new NavigationEvent(this, from, NavigationEvent.NAVIGATING));
|
||||||
|
setContent(node);
|
||||||
|
fireEvent(new NavigationEvent(this, node, NavigationEvent.NAVIGATED));
|
||||||
|
|
||||||
|
EventHandler<PageCloseEvent> handler = event -> close(node);
|
||||||
|
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||||
|
node.addEventHandler(PageCloseEvent.CLOSE, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
close(stack.peek());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void close(Node from) {
|
||||||
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
|
stack.remove(from);
|
||||||
|
Node node = stack.peek();
|
||||||
|
fireEvent(new NavigationEvent(this, from, NavigationEvent.NAVIGATING));
|
||||||
|
setContent(node);
|
||||||
|
fireEvent(new NavigationEvent(this, node, NavigationEvent.NAVIGATED));
|
||||||
|
|
||||||
|
Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||||
|
.ifPresent(handler -> from.removeEventHandler(PageCloseEvent.CLOSE, (EventHandler<PageCloseEvent>) handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getCurrentPage() {
|
||||||
|
return stack.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoBack() {
|
||||||
|
return stack.size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setContent(Node content) {
|
||||||
|
animationHandler.setContent(content, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
|
||||||
|
if (content instanceof Region) {
|
||||||
|
((Region) content).setMinSize(0, 0);
|
||||||
|
FXUtils.setOverflowHidden((Region) content, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventHandler<NavigationEvent> getOnNavigated() {
|
||||||
|
return onNavigated.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<EventHandler<NavigationEvent>> onNavigatedProperty() {
|
||||||
|
return onNavigated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnNavigated(EventHandler<NavigationEvent> onNavigated) {
|
||||||
|
this.onNavigated.set(onNavigated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectProperty<EventHandler<NavigationEvent>> onNavigated = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigated") {
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
setEventHandler(NavigationEvent.NAVIGATED, get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public EventHandler<NavigationEvent> getOnNavigating() {
|
||||||
|
return onNavigating.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<EventHandler<NavigationEvent>> onNavigatingProperty() {
|
||||||
|
return onNavigating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnNavigating(EventHandler<NavigationEvent> onNavigating) {
|
||||||
|
this.onNavigating.set(onNavigating);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectProperty<EventHandler<NavigationEvent>> onNavigating = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigating") {
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
setEventHandler(NavigationEvent.NAVIGATING, get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static class NavigationEvent extends Event {
|
||||||
|
public static final EventType<NavigationEvent> NAVIGATED = new EventType<>("NAVIGATED");
|
||||||
|
public static final EventType<NavigationEvent> NAVIGATING = new EventType<>("NAVIGATING");
|
||||||
|
|
||||||
|
private final Node node;
|
||||||
|
|
||||||
|
public NavigationEvent(Object source, Node target, EventType<? extends Event> eventType) {
|
||||||
|
super(source, target, eventType);
|
||||||
|
|
||||||
|
this.node = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
|
import javafx.event.Event;
|
||||||
|
import javafx.event.EventTarget;
|
||||||
|
import javafx.event.EventType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates a close operation on the navigator page.
|
||||||
|
*
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
public class PageCloseEvent extends Event {
|
||||||
|
|
||||||
|
public static final EventType<PageCloseEvent> CLOSE = new EventType<>("PAGE_CLOSE");
|
||||||
|
|
||||||
|
public PageCloseEvent() {
|
||||||
|
super(CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageCloseEvent(Object source, EventTarget target) {
|
||||||
|
super(source, target, CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
|
import javafx.beans.property.*;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
|
import javafx.scene.layout.Background;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
|
|
||||||
|
public class DecoratorControl extends Control {
|
||||||
|
private final ListProperty<Node> drawer = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
|
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
|
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
|
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
||||||
|
private final StringProperty title = new SimpleStringProperty();
|
||||||
|
private final StringProperty drawerTitle = new SimpleStringProperty();
|
||||||
|
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
||||||
|
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||||
|
private final ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonAction = new SimpleObjectProperty<>();
|
||||||
|
private final ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonAction = new SimpleObjectProperty<>();
|
||||||
|
private final BooleanProperty closeNavButtonVisible = new SimpleBooleanProperty(true);
|
||||||
|
private final BooleanProperty canRefresh = new SimpleBooleanProperty(false);
|
||||||
|
private final BooleanProperty canBack = new SimpleBooleanProperty(false);
|
||||||
|
private final BooleanProperty canClose = new SimpleBooleanProperty(false);
|
||||||
|
private final Stage primaryStage;
|
||||||
|
private StackPane drawerWrapper;
|
||||||
|
|
||||||
|
public DecoratorControl(Stage primaryStage) {
|
||||||
|
this.primaryStage = primaryStage;
|
||||||
|
|
||||||
|
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stage getPrimaryStage() {
|
||||||
|
return primaryStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackPane getDrawerWrapper() {
|
||||||
|
return drawerWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDrawerWrapper(StackPane drawerWrapper) {
|
||||||
|
this.drawerWrapper = drawerWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<Node> getDrawer() {
|
||||||
|
return drawer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListProperty<Node> drawerProperty() {
|
||||||
|
return drawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawer(ObservableList<Node> drawer) {
|
||||||
|
this.drawer.set(drawer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<Node> getContent() {
|
||||||
|
return content.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListProperty<Node> contentProperty() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(ObservableList<Node> content) {
|
||||||
|
this.content.set(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty titleProperty() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title.set(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDrawerTitle() {
|
||||||
|
return drawerTitle.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty drawerTitleProperty() {
|
||||||
|
return drawerTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawerTitle(String drawerTitle) {
|
||||||
|
this.drawerTitle.set(drawerTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runnable getOnCloseButtonAction() {
|
||||||
|
return onCloseButtonAction.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
||||||
|
return onCloseButtonAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
||||||
|
this.onCloseButtonAction.set(onCloseButtonAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCloseNavButtonVisible() {
|
||||||
|
return closeNavButtonVisible.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty closeNavButtonVisibleProperty() {
|
||||||
|
return closeNavButtonVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCloseNavButtonVisible(boolean closeNavButtonVisible) {
|
||||||
|
this.closeNavButtonVisible.set(closeNavButtonVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<Node> getContainer() {
|
||||||
|
return container.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListProperty<Node> containerProperty() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContainer(ObservableList<Node> container) {
|
||||||
|
this.container.set(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Background getContentBackground() {
|
||||||
|
return contentBackground.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Background> contentBackgroundProperty() {
|
||||||
|
return contentBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentBackground(Background contentBackground) {
|
||||||
|
this.contentBackground.set(contentBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty canRefreshProperty() {
|
||||||
|
return canRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty canBackProperty() {
|
||||||
|
return canBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty canCloseProperty() {
|
||||||
|
return canClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonActionProperty() {
|
||||||
|
return onBackNavButtonAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonActionProperty() {
|
||||||
|
return onCloseNavButtonAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonActionProperty() {
|
||||||
|
return onRefreshNavButtonAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin<?> createDefaultSkin() {
|
||||||
|
return new DecoratorSkin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void minimize() {
|
||||||
|
primaryStage.setIconified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
onCloseButtonAction.get().run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXDialog;
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.event.EventTarget;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.DragEvent;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.jackhuang.hmcl.Launcher;
|
||||||
|
import org.jackhuang.hmcl.Metadata;
|
||||||
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||||
|
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||||
|
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||||
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
|
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||||
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
|
||||||
|
public class DecoratorController {
|
||||||
|
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
|
||||||
|
|
||||||
|
private final DecoratorControl decorator;
|
||||||
|
private final ImageView welcomeView;
|
||||||
|
private final Navigator navigator;
|
||||||
|
private final Node mainPage;
|
||||||
|
|
||||||
|
private JFXDialog dialog;
|
||||||
|
private StackContainerPane dialogPane;
|
||||||
|
|
||||||
|
public DecoratorController(Stage stage, Node mainPage) {
|
||||||
|
this.mainPage = mainPage;
|
||||||
|
|
||||||
|
decorator = new DecoratorControl(stage);
|
||||||
|
decorator.titleProperty().set(Metadata.TITLE);
|
||||||
|
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||||
|
|
||||||
|
navigator = new Navigator(mainPage);
|
||||||
|
navigator.setOnNavigating(this::onNavigating);
|
||||||
|
navigator.setOnNavigated(this::onNavigated);
|
||||||
|
|
||||||
|
decorator.getContent().setAll(navigator);
|
||||||
|
decorator.onCloseNavButtonActionProperty().set(e -> close());
|
||||||
|
decorator.onBackNavButtonActionProperty().set(e -> back());
|
||||||
|
decorator.onRefreshNavButtonActionProperty().set(e -> refresh());
|
||||||
|
|
||||||
|
welcomeView = new ImageView();
|
||||||
|
welcomeView.setImage(new Image("/assets/img/welcome.png"));
|
||||||
|
welcomeView.setCursor(Cursor.HAND);
|
||||||
|
welcomeView.setOnMouseClicked(e -> {
|
||||||
|
Timeline nowAnimation = new Timeline();
|
||||||
|
nowAnimation.getKeyFrames().addAll(
|
||||||
|
new KeyFrame(Duration.ZERO, new KeyValue(welcomeView.opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||||
|
new KeyFrame(new Duration(300), new KeyValue(welcomeView.opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||||
|
new KeyFrame(new Duration(300), e2 -> decorator.getContainer().remove(welcomeView))
|
||||||
|
);
|
||||||
|
nowAnimation.play();
|
||||||
|
});
|
||||||
|
if (ConfigHolder.isNewlyCreated() && config().getLocalization().getLocale() == Locale.CHINA)
|
||||||
|
decorator.getContainer().setAll(welcomeView);
|
||||||
|
|
||||||
|
setupBackground();
|
||||||
|
|
||||||
|
setupAuthlibInjectorDnD();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecoratorControl getDecorator() {
|
||||||
|
return decorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Background ====
|
||||||
|
|
||||||
|
private void setupBackground() {
|
||||||
|
decorator.backgroundProperty().bind(
|
||||||
|
Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
Image image = null;
|
||||||
|
if (config().getBackgroundImageType() == EnumBackgroundImage.CUSTOM && config().getBackgroundImage() != null) {
|
||||||
|
image = tryLoadImage(Paths.get(config().getBackgroundImage()))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if (image == null) {
|
||||||
|
image = loadDefaultBackgroundImage();
|
||||||
|
}
|
||||||
|
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
|
||||||
|
},
|
||||||
|
config().backgroundImageTypeProperty(),
|
||||||
|
config().backgroundImageProperty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load background image from bg/, background.png, background.jpg
|
||||||
|
*/
|
||||||
|
private Image loadDefaultBackgroundImage() {
|
||||||
|
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||||
|
if (!image.isPresent()) {
|
||||||
|
image = tryLoadImage(Paths.get("background.png"));
|
||||||
|
}
|
||||||
|
if (!image.isPresent()) {
|
||||||
|
image = tryLoadImage(Paths.get("background.jpg"));
|
||||||
|
}
|
||||||
|
return image.orElse(defaultBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Image> randomImageIn(Path imageDir) {
|
||||||
|
if (!Files.isDirectory(imageDir)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Path> candidates;
|
||||||
|
try {
|
||||||
|
candidates = Files.list(imageDir)
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(it -> {
|
||||||
|
String filename = it.getFileName().toString();
|
||||||
|
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
||||||
|
})
|
||||||
|
.collect(toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Random rnd = new Random();
|
||||||
|
while (candidates.size() > 0) {
|
||||||
|
int selected = rnd.nextInt(candidates.size());
|
||||||
|
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||||
|
if (loaded.isPresent()) {
|
||||||
|
return loaded;
|
||||||
|
} else {
|
||||||
|
candidates.remove(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Image> tryLoadImage(Path path) {
|
||||||
|
if (Files.isRegularFile(path)) {
|
||||||
|
try {
|
||||||
|
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Navigation ====
|
||||||
|
|
||||||
|
public Navigator getNavigator() {
|
||||||
|
return navigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close() {
|
||||||
|
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||||
|
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||||
|
|
||||||
|
page.onForceToClose();
|
||||||
|
} else {
|
||||||
|
navigator.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void back() {
|
||||||
|
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||||
|
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||||
|
|
||||||
|
if (page.onClose())
|
||||||
|
navigator.close();
|
||||||
|
} else {
|
||||||
|
navigator.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
if (navigator.getCurrentPage() instanceof Refreshable) {
|
||||||
|
Refreshable refreshable = (Refreshable) navigator.getCurrentPage();
|
||||||
|
|
||||||
|
if (refreshable.canRefreshProperty().get())
|
||||||
|
refreshable.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNavigating(Navigator.NavigationEvent event) {
|
||||||
|
Node from = event.getNode();
|
||||||
|
|
||||||
|
if (from instanceof DecoratorPage)
|
||||||
|
((DecoratorPage) from).onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNavigated(Navigator.NavigationEvent event) {
|
||||||
|
Node to = event.getNode();
|
||||||
|
|
||||||
|
if (to instanceof Refreshable) {
|
||||||
|
decorator.canRefreshProperty().bind(((Refreshable) to).canRefreshProperty());
|
||||||
|
} else {
|
||||||
|
decorator.canRefreshProperty().unbind();
|
||||||
|
decorator.canRefreshProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to instanceof DecoratorPage) {
|
||||||
|
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
||||||
|
decorator.canCloseProperty().set(((DecoratorPage) to).canForceToClose());
|
||||||
|
} else {
|
||||||
|
decorator.drawerTitleProperty().unbind();
|
||||||
|
decorator.drawerTitleProperty().set("");
|
||||||
|
decorator.canCloseProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
decorator.canBackProperty().set(navigator.canGoBack());
|
||||||
|
|
||||||
|
if (navigator.canGoBack()) {
|
||||||
|
decorator.setContentBackground(new Background(new BackgroundFill(Color.rgb(244, 244, 244, 0.5), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
|
} else {
|
||||||
|
decorator.setContentBackground(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to instanceof Region) {
|
||||||
|
Region region = (Region) to;
|
||||||
|
// Let root pane fix window size.
|
||||||
|
StackPane parent = (StackPane) region.getParent();
|
||||||
|
region.prefWidthProperty().bind(parent.widthProperty());
|
||||||
|
region.prefHeightProperty().bind(parent.heightProperty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Dialog ====
|
||||||
|
|
||||||
|
public void showDialog(Node node) {
|
||||||
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
|
if (dialog == null) {
|
||||||
|
dialog = new JFXDialog();
|
||||||
|
dialogPane = new StackContainerPane();
|
||||||
|
|
||||||
|
dialog.setContent(dialogPane);
|
||||||
|
dialog.setDialogContainer(decorator.getDrawerWrapper());
|
||||||
|
dialog.setOverlayClose(false);
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogPane.push(node);
|
||||||
|
|
||||||
|
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||||
|
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||||
|
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void closeDialog(Node node) {
|
||||||
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
|
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||||
|
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||||
|
|
||||||
|
if (dialog != null) {
|
||||||
|
dialogPane.pop(node);
|
||||||
|
|
||||||
|
if (dialogPane.getChildren().isEmpty()) {
|
||||||
|
dialog.close();
|
||||||
|
dialog = null;
|
||||||
|
dialogPane = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDialog(String text) {
|
||||||
|
showDialog(text, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDialog(String text, String title) {
|
||||||
|
showDialog(text, title, MessageBox.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDialog(String text, String title, int type) {
|
||||||
|
showDialog(text, title, type, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDialog(String text, String title, int type, Runnable onAccept) {
|
||||||
|
showDialog(new MessageDialogPane(text, title, type, onAccept));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showConfirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||||
|
showDialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputDialogPane showInputDialog(String text, FutureCallback<String> onResult) {
|
||||||
|
InputDialogPane pane = new InputDialogPane(text, onResult);
|
||||||
|
showDialog(pane);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Region showTaskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||||
|
return showTaskDialog(executor, title, subtitle, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Region showTaskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
|
||||||
|
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||||
|
pane.setTitle(title);
|
||||||
|
pane.setSubtitle(subtitle);
|
||||||
|
pane.setExecutor(executor);
|
||||||
|
showDialog(pane);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Wizard ====
|
||||||
|
|
||||||
|
public void startWizard(WizardProvider wizardProvider) {
|
||||||
|
startWizard(wizardProvider, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||||
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
|
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Authlib Injector DnD ====
|
||||||
|
|
||||||
|
private void setupAuthlibInjectorDnD() {
|
||||||
|
decorator.addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
|
||||||
|
decorator.addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
|
||||||
|
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,13 +15,21 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.wizard;
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
public interface DecoratorPage {
|
public interface DecoratorPage {
|
||||||
StringProperty titleProperty();
|
StringProperty titleProperty();
|
||||||
|
|
||||||
default void onClose() {
|
default boolean canForceToClose() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean onClose() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onForceToClose() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.svg.SVGGlyph;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.geometry.BoundingBox;
|
||||||
|
import javafx.geometry.Bounds;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
|
public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||||
|
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||||
|
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||||
|
|
||||||
|
private final BorderPane titleContainer;
|
||||||
|
private final StackPane contentPlaceHolder;
|
||||||
|
private final JFXButton refreshNavButton;
|
||||||
|
private final JFXButton closeNavButton;
|
||||||
|
private final HBox navLeft;
|
||||||
|
private final Stage primaryStage;
|
||||||
|
|
||||||
|
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||||
|
private boolean allowMove, isDragging;
|
||||||
|
private BoundingBox originalBox, maximizedBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for all SkinBase instances.
|
||||||
|
*
|
||||||
|
* @param control The control for which this Skin should attach to.
|
||||||
|
*/
|
||||||
|
public DecoratorSkin(DecoratorControl control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
primaryStage = control.getPrimaryStage();
|
||||||
|
|
||||||
|
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||||
|
|
||||||
|
DecoratorControl skinnable = getSkinnable();
|
||||||
|
|
||||||
|
BorderPane root = new BorderPane();
|
||||||
|
root.getStyleClass().setAll("jfx-decorator", "resize-border");
|
||||||
|
root.setMaxHeight(519);
|
||||||
|
root.setMaxWidth(800);
|
||||||
|
|
||||||
|
StackPane drawerWrapper = new StackPane();
|
||||||
|
skinnable.setDrawerWrapper(drawerWrapper);
|
||||||
|
drawerWrapper.getStyleClass().setAll("jfx-decorator-drawer");
|
||||||
|
drawerWrapper.backgroundProperty().bind(skinnable.backgroundProperty());
|
||||||
|
FXUtils.setOverflowHidden(drawerWrapper, true);
|
||||||
|
{
|
||||||
|
BorderPane drawer = new BorderPane();
|
||||||
|
|
||||||
|
{
|
||||||
|
BorderPane leftRootPane = new BorderPane();
|
||||||
|
FXUtils.setLimitWidth(leftRootPane, 200);
|
||||||
|
leftRootPane.getStyleClass().setAll("jfx-decorator-content-container");
|
||||||
|
|
||||||
|
StackPane drawerContainer = new StackPane();
|
||||||
|
drawerContainer.getStyleClass().setAll("gray-background");
|
||||||
|
Bindings.bindContent(drawerContainer.getChildren(), skinnable.drawerProperty());
|
||||||
|
leftRootPane.setCenter(drawerContainer);
|
||||||
|
|
||||||
|
Rectangle separator = new Rectangle();
|
||||||
|
separator.heightProperty().bind(drawer.heightProperty());
|
||||||
|
separator.setWidth(1);
|
||||||
|
separator.setFill(Color.GRAY);
|
||||||
|
|
||||||
|
leftRootPane.setRight(separator);
|
||||||
|
|
||||||
|
drawer.setLeft(leftRootPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
contentPlaceHolder = new StackPane();
|
||||||
|
contentPlaceHolder.getStyleClass().setAll("jfx-decorator-content-container");
|
||||||
|
contentPlaceHolder.backgroundProperty().bind(skinnable.contentBackgroundProperty());
|
||||||
|
FXUtils.setOverflowHidden(contentPlaceHolder, true);
|
||||||
|
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
||||||
|
|
||||||
|
drawer.setCenter(contentPlaceHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawerWrapper.getChildren().add(drawer);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
StackPane container = new StackPane();
|
||||||
|
Bindings.bindContent(container.getChildren(), skinnable.containerProperty());
|
||||||
|
ListChangeListener<Node> listener = new ListChangeListener<Node>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(Change<? extends Node> c) {
|
||||||
|
if (skinnable.getContainer().isEmpty()) {
|
||||||
|
container.setMouseTransparent(true);
|
||||||
|
container.setVisible(false);
|
||||||
|
} else {
|
||||||
|
container.setMouseTransparent(false);
|
||||||
|
container.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
skinnable.containerProperty().addListener(listener);
|
||||||
|
listener.onChanged(null);
|
||||||
|
|
||||||
|
drawerWrapper.getChildren().add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setCenter(drawerWrapper);
|
||||||
|
|
||||||
|
titleContainer = new BorderPane();
|
||||||
|
titleContainer.setOnMouseReleased(this::onMouseReleased);
|
||||||
|
titleContainer.setOnMouseDragged(this::onMouseDragged);
|
||||||
|
titleContainer.setOnMouseMoved(this::onMouseMoved);
|
||||||
|
titleContainer.setPickOnBounds(false);
|
||||||
|
titleContainer.setMinHeight(40);
|
||||||
|
titleContainer.getStyleClass().setAll("jfx-tool-bar");
|
||||||
|
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
||||||
|
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
||||||
|
if (!isDragging) allowMove = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
Rectangle rectangle = new Rectangle(0, 0, 0, 0);
|
||||||
|
rectangle.widthProperty().bind(titleContainer.widthProperty());
|
||||||
|
rectangle.heightProperty().bind(Bindings.createDoubleBinding(() -> titleContainer.getHeight() + 100, titleContainer.heightProperty()));
|
||||||
|
titleContainer.setClip(rectangle);
|
||||||
|
{
|
||||||
|
BorderPane titleWrapper = new BorderPane();
|
||||||
|
FXUtils.setLimitWidth(titleWrapper, 200);
|
||||||
|
{
|
||||||
|
Label lblTitle = new Label();
|
||||||
|
BorderPane.setMargin(lblTitle, new Insets(0, 0, 0, 3));
|
||||||
|
lblTitle.setStyle("-fx-background-color: transparent; -fx-text-fill: -fx-base-text-fill; -fx-font-size: 15px;");
|
||||||
|
lblTitle.setMouseTransparent(true);
|
||||||
|
lblTitle.textProperty().bind(skinnable.titleProperty());
|
||||||
|
BorderPane.setAlignment(lblTitle, Pos.CENTER);
|
||||||
|
titleWrapper.setCenter(lblTitle);
|
||||||
|
|
||||||
|
Rectangle separator = new Rectangle();
|
||||||
|
separator.heightProperty().bind(titleWrapper.heightProperty());
|
||||||
|
separator.setWidth(1);
|
||||||
|
separator.setFill(Color.GRAY);
|
||||||
|
titleWrapper.setRight(separator);
|
||||||
|
}
|
||||||
|
titleContainer.setLeft(titleWrapper);
|
||||||
|
|
||||||
|
BorderPane navBar = new BorderPane();
|
||||||
|
{
|
||||||
|
navLeft = new HBox();
|
||||||
|
navLeft.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
navLeft.setPadding(new Insets(0, 5, 0, 5));
|
||||||
|
{
|
||||||
|
JFXButton backNavButton = new JFXButton();
|
||||||
|
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1));
|
||||||
|
backNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||||
|
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
|
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||||
|
backNavButton.visibleProperty().bind(skinnable.canBackProperty());
|
||||||
|
|
||||||
|
closeNavButton = new JFXButton();
|
||||||
|
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||||
|
closeNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||||
|
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
|
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||||
|
|
||||||
|
navLeft.getChildren().setAll(backNavButton);
|
||||||
|
|
||||||
|
skinnable.canCloseProperty().addListener((a, b, newValue) -> {
|
||||||
|
if (newValue) navLeft.getChildren().setAll(backNavButton, closeNavButton);
|
||||||
|
else navLeft.getChildren().setAll(backNavButton);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
navBar.setLeft(navLeft);
|
||||||
|
|
||||||
|
VBox navCenter = new VBox();
|
||||||
|
navCenter.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
Label titleLabel = new Label();
|
||||||
|
titleLabel.getStyleClass().setAll("jfx-decorator-title");
|
||||||
|
titleLabel.textProperty().bind(skinnable.drawerTitleProperty());
|
||||||
|
navCenter.getChildren().setAll(titleLabel);
|
||||||
|
navBar.setCenter(navCenter);
|
||||||
|
|
||||||
|
HBox navRight = new HBox();
|
||||||
|
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
refreshNavButton = new JFXButton();
|
||||||
|
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||||
|
refreshNavButton.getStyleClass().setAll("jfx-decorator-button");
|
||||||
|
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
|
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||||
|
refreshNavButton.visibleProperty().bind(skinnable.canRefreshProperty());
|
||||||
|
navRight.getChildren().setAll(refreshNavButton);
|
||||||
|
navBar.setRight(navRight);
|
||||||
|
}
|
||||||
|
titleContainer.setCenter(navBar);
|
||||||
|
|
||||||
|
HBox buttonsContainer = new HBox();
|
||||||
|
buttonsContainer.setStyle("-fx-background-color: transparent;");
|
||||||
|
buttonsContainer.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
buttonsContainer.setPadding(new Insets(4));
|
||||||
|
{
|
||||||
|
Rectangle separator = new Rectangle();
|
||||||
|
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||||
|
separator.heightProperty().bind(navBar.heightProperty());
|
||||||
|
|
||||||
|
JFXButton btnMin = new JFXButton();
|
||||||
|
StackPane pane = new StackPane(minus);
|
||||||
|
pane.setAlignment(Pos.CENTER);
|
||||||
|
btnMin.setGraphic(pane);
|
||||||
|
btnMin.getStyleClass().setAll("jfx-decorator-button");
|
||||||
|
btnMin.setOnAction(e -> skinnable.minimize());
|
||||||
|
|
||||||
|
JFXButton btnClose = new JFXButton();
|
||||||
|
btnClose.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||||
|
btnClose.getStyleClass().setAll("jfx-decorator-button");
|
||||||
|
btnClose.setOnAction(e -> skinnable.close());
|
||||||
|
|
||||||
|
buttonsContainer.getChildren().setAll(separator, btnMin, btnClose);
|
||||||
|
}
|
||||||
|
titleContainer.setRight(buttonsContainer);
|
||||||
|
}
|
||||||
|
root.setTop(titleContainer);
|
||||||
|
|
||||||
|
getChildren().setAll(root);
|
||||||
|
|
||||||
|
getSkinnable().closeNavButtonVisibleProperty().addListener((a, b, newValue) -> {
|
||||||
|
if (newValue) navLeft.getChildren().add(closeNavButton);
|
||||||
|
else navLeft.getChildren().remove(closeNavButton);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
||||||
|
initX = mouseEvent.getScreenX();
|
||||||
|
initY = mouseEvent.getScreenY();
|
||||||
|
xOffset = mouseEvent.getSceneX();
|
||||||
|
yOffset = mouseEvent.getSceneY();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
||||||
|
return x < getSkinnable().getWidth() && x > getSkinnable().getWidth() - contentPlaceHolder.snappedLeftInset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
|
||||||
|
return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
|
||||||
|
return y < getSkinnable().getHeight() && y > getSkinnable().getHeight() - contentPlaceHolder.snappedLeftInset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
|
||||||
|
return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setStageWidth(double width) {
|
||||||
|
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
||||||
|
primaryStage.setWidth(width);
|
||||||
|
initX = newX;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
||||||
|
primaryStage.setWidth(titleContainer.getMinWidth());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setStageHeight(double height) {
|
||||||
|
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
||||||
|
primaryStage.setHeight(height);
|
||||||
|
initY = newY;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
||||||
|
primaryStage.setHeight(titleContainer.getHeight());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
|
||||||
|
protected void onMouseMoved(MouseEvent mouseEvent) {
|
||||||
|
if (!primaryStage.isFullScreen()) {
|
||||||
|
if (!primaryStage.isResizable())
|
||||||
|
updateInitMouseValues(mouseEvent);
|
||||||
|
else {
|
||||||
|
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||||
|
Bounds boundsInParent = getSkinnable().getBoundsInParent();
|
||||||
|
if (getSkinnable().getBorder() != null && getSkinnable().getBorder().getStrokes().size() > 0) {
|
||||||
|
double borderWidth = contentPlaceHolder.snappedLeftInset();
|
||||||
|
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||||
|
if (y < borderWidth) {
|
||||||
|
getSkinnable().setCursor(Cursor.NE_RESIZE);
|
||||||
|
} else if (y > getSkinnable().getHeight() - borderWidth) {
|
||||||
|
getSkinnable().setCursor(Cursor.SE_RESIZE);
|
||||||
|
} else {
|
||||||
|
getSkinnable().setCursor(Cursor.E_RESIZE);
|
||||||
|
}
|
||||||
|
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||||
|
if (y < borderWidth) {
|
||||||
|
getSkinnable().setCursor(Cursor.NW_RESIZE);
|
||||||
|
} else if (y > getSkinnable().getHeight() - borderWidth) {
|
||||||
|
getSkinnable().setCursor(Cursor.SW_RESIZE);
|
||||||
|
} else {
|
||||||
|
getSkinnable().setCursor(Cursor.W_RESIZE);
|
||||||
|
}
|
||||||
|
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||||
|
getSkinnable().setCursor(Cursor.N_RESIZE);
|
||||||
|
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||||
|
getSkinnable().setCursor(Cursor.S_RESIZE);
|
||||||
|
} else {
|
||||||
|
getSkinnable().setCursor(Cursor.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateInitMouseValues(mouseEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getSkinnable().setCursor(Cursor.DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onMouseReleased(MouseEvent mouseEvent) {
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onMouseDragged(MouseEvent mouseEvent) {
|
||||||
|
this.isDragging = true;
|
||||||
|
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||||
|
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) {
|
||||||
|
this.newX = mouseEvent.getScreenX();
|
||||||
|
this.newY = mouseEvent.getScreenY();
|
||||||
|
double deltaX = this.newX - this.initX;
|
||||||
|
double deltaY = this.newY - this.initY;
|
||||||
|
Cursor cursor = getSkinnable().getCursor();
|
||||||
|
if (Cursor.E_RESIZE == cursor) {
|
||||||
|
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.NE_RESIZE == cursor) {
|
||||||
|
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||||
|
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.SE_RESIZE == cursor) {
|
||||||
|
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||||
|
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.S_RESIZE == cursor) {
|
||||||
|
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.W_RESIZE == cursor) {
|
||||||
|
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||||
|
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.SW_RESIZE == cursor) {
|
||||||
|
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||||
|
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.NW_RESIZE == cursor) {
|
||||||
|
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||||
|
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||||
|
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (Cursor.N_RESIZE == cursor) {
|
||||||
|
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||||
|
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEvent.consume();
|
||||||
|
} else if (this.allowMove) {
|
||||||
|
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
||||||
|
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
||||||
|
mouseEvent.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.*;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
|
public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorDialogWizardDisplayer, Refreshable, DecoratorPage {
|
||||||
|
private final StringProperty title = new SimpleStringProperty();
|
||||||
|
private final BooleanProperty canRefresh = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||||
|
private final WizardController wizardController = new WizardController(this);
|
||||||
|
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
private final String category;
|
||||||
|
|
||||||
|
private Node nowPage;
|
||||||
|
|
||||||
|
public DecoratorWizardDisplayer(WizardProvider provider) {
|
||||||
|
this(provider, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecoratorWizardDisplayer(WizardProvider provider, String category) {
|
||||||
|
this.category = category;
|
||||||
|
|
||||||
|
wizardController.setProvider(provider);
|
||||||
|
wizardController.onStart();
|
||||||
|
|
||||||
|
getStyleClass().setAll("white-background");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringProperty titleProperty() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BooleanProperty canRefreshProperty() {
|
||||||
|
return canRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WizardController getWizardController() {
|
||||||
|
return wizardController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Queue<Object> getCancelQueue() {
|
||||||
|
return cancelQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd() {
|
||||||
|
fireEvent(new PageCloseEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||||
|
nowPage = page;
|
||||||
|
|
||||||
|
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||||
|
|
||||||
|
canRefresh.set(page instanceof Refreshable);
|
||||||
|
|
||||||
|
String prefix = category == null ? "" : category + " - ";
|
||||||
|
|
||||||
|
if (page instanceof WizardPage)
|
||||||
|
title.set(prefix + ((WizardPage) page).getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canForceToClose() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onForceToClose() {
|
||||||
|
wizardController.onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onClose() {
|
||||||
|
if (wizardController.canPrev()) {
|
||||||
|
wizardController.onPrev(true);
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
((Refreshable) nowPage).refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.mod.Modpack;
|
|||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
import org.jackhuang.hmcl.setting.Settings;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||||
@@ -53,7 +54,8 @@ public final class DownloadWizardProvider implements WizardProvider {
|
|||||||
private Task finishVersionDownloadingAsync(Map<String, Object> settings) {
|
private Task finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||||
GameBuilder builder = profile.getDependency().gameBuilder();
|
GameBuilder builder = profile.getDependency().gameBuilder();
|
||||||
|
|
||||||
builder.name((String) settings.get("name"));
|
String name = (String) settings.get("name");
|
||||||
|
builder.name(name);
|
||||||
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
|
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
|
||||||
|
|
||||||
if (settings.containsKey("forge"))
|
if (settings.containsKey("forge"))
|
||||||
@@ -65,7 +67,8 @@ public final class DownloadWizardProvider implements WizardProvider {
|
|||||||
if (settings.containsKey("optifine"))
|
if (settings.containsKey("optifine"))
|
||||||
builder.version((RemoteVersion) settings.get("optifine"));
|
builder.version((RemoteVersion) settings.get("optifine"));
|
||||||
|
|
||||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions());
|
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions())
|
||||||
|
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||||
@@ -77,7 +80,8 @@ public final class DownloadWizardProvider implements WizardProvider {
|
|||||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||||
if (selected == null || modpack == null || name == null) return null;
|
if (selected == null || modpack == null || name == null) return null;
|
||||||
|
|
||||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack);
|
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||||
|
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import javafx.scene.control.ToggleGroup;
|
|||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||||
|
|||||||
@@ -27,11 +27,10 @@ import javafx.scene.layout.StackPane;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
|||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.VersionNumber;
|
import org.jackhuang.hmcl.util.VersionNumber;
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
|
|
||||||
@@ -78,6 +78,13 @@ public class GameList extends Control implements DecoratorPage {
|
|||||||
loading.set(false);
|
loading.set(false);
|
||||||
items.setAll(children);
|
items.setAll(children);
|
||||||
children.forEach(GameListItem::checkSelection);
|
children.forEach(GameListItem::checkSelection);
|
||||||
|
|
||||||
|
profile.selectedVersionProperty().addListener((a, b, newValue) -> {
|
||||||
|
toggleGroup.getToggles().stream()
|
||||||
|
.filter(it -> ((GameListItem) it.getUserData()).getVersion().equals(newValue))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(it -> it.setSelected(true));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> {
|
||||||
GameListItem model = (GameListItem) toggle.getUserData();
|
GameListItem model = (GameListItem) toggle.getUserData();
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ public class GameListItem extends Control {
|
|||||||
|
|
||||||
public void modifyGameSettings() {
|
public void modifyGameSettings() {
|
||||||
Controllers.getVersionPage().load(version, profile);
|
Controllers.getVersionPage().load(version, profile);
|
||||||
Controllers.getDecorator().showPage(Controllers.getVersionPage());
|
Controllers.navigate(Controllers.getVersionPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateLaunchScript() {
|
public void generateLaunchScript() {
|
||||||
|
|||||||
@@ -17,6 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.wizard;
|
package org.jackhuang.hmcl.ui.wizard;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
||||||
public interface Refreshable {
|
public interface Refreshable {
|
||||||
void refresh();
|
void refresh();
|
||||||
|
|
||||||
|
default BooleanProperty canRefreshProperty() {
|
||||||
|
return new SimpleBooleanProperty(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1062,6 +1062,10 @@
|
|||||||
.jfx-decorator-drawer {
|
.jfx-decorator-drawer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jfx-decorator-title {
|
||||||
|
-fx-text-fill: -fx-base-text-fill; -fx-font-size: 15;
|
||||||
|
}
|
||||||
|
|
||||||
.resize-border {
|
.resize-border {
|
||||||
-fx-border-color: -fx-base-color;
|
-fx-border-color: -fx-base-color;
|
||||||
-fx-border-width: 0 2 2 2;
|
-fx-border-width: 0 2 2 2;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
maxWidth="800">
|
maxWidth="800">
|
||||||
<center>
|
<center>
|
||||||
<StackPane fx:id="drawerWrapper" styleClass="jfx-decorator-drawer" FXUtils.overflowHidden="true">
|
<StackPane fx:id="drawerWrapper" styleClass="jfx-decorator-drawer" FXUtils.overflowHidden="true">
|
||||||
<BorderPane>
|
<BorderPane fx:id="drawer">
|
||||||
<left>
|
<left>
|
||||||
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
|
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
|
||||||
<BorderPane fx:id="leftRootPane">
|
<BorderPane fx:id="leftRootPane">
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
<?import com.jfoenix.controls.JFXButton?>
|
<?import com.jfoenix.controls.JFXButton?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<fx:root
|
<fx:root type="StackPane" pickOnBounds="false" style="-fx-padding: 25;"
|
||||||
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
|
||||||
type="StackPane" pickOnBounds="false" style="-fx-padding: 25;"
|
|
||||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<JFXButton prefWidth="150" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
|
<JFXButton prefWidth="150" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
|
||||||
style="-fx-font-size: 15;" onMouseClicked="#launch"
|
style="-fx-font-size: 15;" onMouseClicked="#launch"
|
||||||
|
|||||||
Reference in New Issue
Block a user