diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index f5be906a6..cf725ef77 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -28,7 +28,6 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.beans.value.WeakChangeListener; import javafx.beans.value.WritableValue; -import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -52,7 +51,6 @@ import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ResourceNotFoundError; -import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; @@ -279,17 +277,6 @@ public final class FXUtils { JFXScrollPane.smoothScrolling(scrollPane); } - public static void loadFXML(Node node, String absolutePath) { - FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), I18n.getResourceBundle()); - loader.setRoot(node); - loader.setController(node); - try { - loader.load(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - public static void installFastTooltip(Node node, Tooltip tooltip) { installTooltip(node, 50, 5000, 0, tooltip); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index f22b62c46..283d9e6e9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -54,6 +54,8 @@ public final class SVG { return svg; } + public static final String ARROW_RIGHT = "M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"; + // default fill: white, width: 24, height 24 public static Node gear(ObjectBinding fill, double width, double height) { @@ -273,7 +275,7 @@ public final class SVG { } public static Node arrowRight(ObjectBinding fill, double width, double height) { - return createSVGPath("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z", fill, width, + return createSVGPath(ARROW_RIGHT, fill, width, height); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java index ff8881b7b..3f77a8349 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java @@ -20,10 +20,8 @@ package org.jackhuang.hmcl.ui.account; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXTextField; -import javafx.fxml.FXML; import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -32,29 +30,28 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.DialogAware; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; import java.util.logging.Level; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.loadFXML; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware { +public final class AddAuthlibInjectorServerPane extends TransitionPane implements DialogAware { - @FXML private TransitionPane root; - @FXML private Label lblServerUrl; - @FXML private Label lblServerName; - @FXML private Label lblCreationWarning; - @FXML private Label lblServerWarning; - @FXML private JFXTextField txtServerUrl; - @FXML private JFXDialogLayout addServerPane; - @FXML private JFXDialogLayout confirmServerPane; - @FXML private SpinnerPane nextPane; - @FXML private JFXButton btnAddNext; + private final Label lblServerUrl; + private final Label lblServerName; + private final Label lblCreationWarning; + private final Label lblServerWarning; + private final JFXTextField txtServerUrl; + private final JFXDialogLayout addServerPane; + private final JFXDialogLayout confirmServerPane; + private final SpinnerPane nextPane; + private final JFXButton btnAddNext; private AuthlibInjectorServer serverBeingAdded; @@ -65,8 +62,92 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa } public AddAuthlibInjectorServerPane() { - loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml"); - root.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); + addServerPane = new JFXDialogLayout(); + addServerPane.setHeading(new Label(i18n("account.injector.add"))); + { + txtServerUrl = new JFXTextField(); + txtServerUrl.setPromptText(i18n("account.injector.server_url")); + txtServerUrl.setOnAction(e -> onAddNext()); + + lblCreationWarning = new Label(); + lblCreationWarning.setWrapText(true); + HBox actions = new HBox(); + { + JFXButton cancel = new JFXButton(i18n("button.cancel")); + cancel.getStyleClass().add("dialog-accept"); + cancel.setOnAction(e -> onAddCancel()); + + nextPane = new SpinnerPane(); + nextPane.getStyleClass().add("small-spinner-pane"); + btnAddNext = new JFXButton(i18n("wizard.next")); + btnAddNext.getStyleClass().add("dialog-accept"); + btnAddNext.setOnAction(e -> onAddNext()); + nextPane.setContent(btnAddNext); + + actions.getChildren().setAll(cancel, nextPane); + } + + addServerPane.setBody(txtServerUrl); + addServerPane.setActions(lblCreationWarning, actions); + } + + confirmServerPane = new JFXDialogLayout(); + confirmServerPane.setHeading(new Label(i18n("account.injector.add"))); + { + GridPane body = new GridPane(); + body.setStyle("-fx-padding: 15 0 0 0;"); + body.setVgap(15); + body.setHgap(15); + { + body.getColumnConstraints().setAll( + Lang.apply(new ColumnConstraints(), c -> c.setMaxWidth(100)), + new ColumnConstraints() + ); + + lblServerUrl = new Label(); + GridPane.setColumnIndex(lblServerUrl, 1); + GridPane.setRowIndex(lblServerUrl, 0); + + lblServerName = new Label(); + GridPane.setColumnIndex(lblServerName, 1); + GridPane.setRowIndex(lblServerName, 1); + + lblServerWarning = new Label(i18n("account.injector.http")); + lblServerWarning.setStyle("-fx-text-fill: red;"); + GridPane.setColumnIndex(lblServerWarning, 0); + GridPane.setRowIndex(lblServerWarning, 2); + GridPane.setColumnSpan(lblServerWarning, 2); + + body.getChildren().setAll( + Lang.apply(new Label(i18n("account.injector.server_url")), l -> { + GridPane.setColumnIndex(l, 0); + GridPane.setRowIndex(l, 0); + }), + Lang.apply(new Label(i18n("account.injector.server_name")), l -> { + GridPane.setColumnIndex(l, 0); + GridPane.setRowIndex(l, 1); + }), + lblServerUrl, lblServerName, lblServerWarning + ); + } + + JFXButton prevButton = new JFXButton(i18n("wizard.prev")); + prevButton.getStyleClass().add("dialog-cancel"); + prevButton.setOnAction(e -> onAddPrev()); + + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + cancelButton.setOnAction(e -> onAddCancel()); + + JFXButton finishButton = new JFXButton(i18n("wizard.finish")); + finishButton.getStyleClass().add("dialog-accept"); + finishButton.setOnAction(e -> onAddFinish()); + + confirmServerPane.setBody(body); + confirmServerPane.setActions(prevButton, cancelButton, finishButton); + } + + this.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty()); btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty()); @@ -88,12 +169,10 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa } } - @FXML private void onAddCancel() { fireEvent(new DialogCloseEvent()); } - @FXML private void onAddNext() { if (btnAddNext.isDisabled()) return; @@ -117,7 +196,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol())); - root.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); + this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); } else { LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception); lblCreationWarning.setText(resolveFetchExceptionMessage(exception)); @@ -126,12 +205,10 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa } - @FXML private void onAddPrev() { - root.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer()); + this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer()); } - @FXML private void onAddFinish() { if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) { config().getAuthlibInjectorServers().add(serverBeingAdded); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java index 791c3739c..770c4af9c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java @@ -18,40 +18,45 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXTextField; -import javafx.fxml.FXML; import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.FutureCallback; import java.util.concurrent.CompletableFuture; -public class InputDialogPane extends StackPane { +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class InputDialogPane extends JFXDialogLayout { private final CompletableFuture future = new CompletableFuture<>(); - @FXML - private JFXButton acceptButton; - @FXML - private JFXButton cancelButton; - @FXML - private Label title; - @FXML - private VBox vbox; - @FXML - private Label lblCreationWarning; - @FXML - private SpinnerPane acceptPane; + private final JFXTextField textField; + private final Label lblCreationWarning; + private final SpinnerPane acceptPane; public InputDialogPane(String text, String initialValue, FutureCallback onResult) { - FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml"); - title.setText(text); - JFXTextField textField = new JFXTextField(); - textField.setText(initialValue); - vbox.getChildren().setAll(textField); - cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); - acceptButton.setOnMouseClicked(e -> { + textField = new JFXTextField(initialValue); + + this.setHeading(new HBox(new Label(text))); + this.setBody(new VBox(textField)); + + lblCreationWarning = new Label(); + + acceptPane = new SpinnerPane(); + acceptPane.getStyleClass().add("small-spinner-pane"); + JFXButton acceptButton = new JFXButton(i18n("button.ok")); + acceptButton.getStyleClass().add("dialog-accept"); + acceptPane.setContent(acceptButton); + + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + + this.setActions(lblCreationWarning, acceptPane, cancelButton); + + cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); + acceptButton.setOnAction(e -> { acceptPane.showSpinner(); onResult.call(textField.getText(), () -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 0dcbe3107..d212cbe1f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXButton; import javafx.event.ActionEvent; -import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.ButtonBase; import javafx.scene.control.Label; @@ -45,7 +44,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class MessageDialogPane extends StackPane { +public final class MessageDialogPane extends HBox { public enum MessageType { ERROR, @@ -59,35 +58,19 @@ public final class MessageDialogPane extends StackPane { } } - @FXML - private StackPane content; - @FXML - private Label graphic; - @FXML - private Label title; - @FXML - private HBox actions; + private final HBox actions; private @Nullable ButtonBase cancelButton; public MessageDialogPane(@NotNull String text, @Nullable String title, @NotNull MessageType type) { - FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml"); - - EnhancedTextFlow textFlow = new EnhancedTextFlow(); - textFlow.getChildren().setAll(FXUtils.parseSegment(text, Controllers::onHyperlinkAction)); - - if (textFlow.computePrefHeight(400.0) <= 350.0) - content.getChildren().setAll(textFlow); - else { - ScrollPane scrollPane = new ScrollPane(textFlow); - VBox.setVgrow(scrollPane, Priority.ALWAYS); - scrollPane.setFitToWidth(true); - content.getChildren().setAll(scrollPane); - } - - if (title != null) - this.title.setText(title); + this.setSpacing(8); + this.getStyleClass().add("jfx-dialog-layout"); + Label graphic = new Label(); + graphic.setTranslateX(10); + graphic.setTranslateY(10); + graphic.setMinSize(40, 40); + graphic.setMaxSize(40, 40); switch (type) { case INFO: graphic.setGraphic(SVG.infoCircle(Theme.blackFillBinding(), 40, 40)); @@ -108,6 +91,35 @@ public final class MessageDialogPane extends StackPane { throw new IllegalArgumentException("Unrecognized message box message type " + type); } + VBox vbox = new VBox(); + HBox.setHgrow(vbox, Priority.ALWAYS); + { + StackPane titlePane = new StackPane(); + titlePane.getStyleClass().addAll("jfx-layout-heading", "title"); + titlePane.getChildren().setAll(new Label(title != null ? title : i18n("message.info"))); + + StackPane content = new StackPane(); + content.getStyleClass().add("jfx-layout-body"); + EnhancedTextFlow textFlow = new EnhancedTextFlow(text); + textFlow.setStyle("-fx-font-size: 14px;"); + if (textFlow.computePrefHeight(400.0) <= 350.0) + content.getChildren().setAll(textFlow); + else { + ScrollPane scrollPane = new ScrollPane(textFlow); + scrollPane.setPrefHeight(350); + VBox.setVgrow(scrollPane, Priority.ALWAYS); + scrollPane.setFitToWidth(true); + content.getChildren().setAll(scrollPane); + } + + actions = new HBox(); + actions.getStyleClass().add("jfx-layout-actions"); + + vbox.getChildren().setAll(titlePane, content, actions); + } + + this.getChildren().setAll(graphic, vbox); + onEscPressed(this, () -> { if (cancelButton != null) { cancelButton.fire(); @@ -129,6 +141,10 @@ public final class MessageDialogPane extends StackPane { } private static final class EnhancedTextFlow extends TextFlow { + EnhancedTextFlow(String text) { + this.getChildren().setAll(FXUtils.parseSegment(text, Controllers::onHyperlinkAction)); + } + @Override public double computePrefHeight(double width) { return super.computePrefHeight(width); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index a9929c08e..bc1dc5520 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -20,9 +20,12 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXButton; import javafx.application.Platform; import javafx.beans.property.StringProperty; -import javafx.fxml.FXML; +import javafx.geometry.Insets; import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; @@ -35,23 +38,51 @@ import java.util.function.Consumer; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class TaskExecutorDialogPane extends StackPane { +public class TaskExecutorDialogPane extends BorderPane { private TaskExecutor executor; private TaskCancellationAction onCancel; private final Consumer speedEventHandler; - @FXML - private Label lblTitle; - @FXML - private Label lblProgress; - @FXML - private JFXButton btnCancel; - @FXML - private TaskListPane taskListPane; + private final Label lblTitle; + private final Label lblProgress; + private final JFXButton btnCancel; + private final TaskListPane taskListPane; public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { - FXUtils.loadFXML(this, "/assets/fxml/task-dialog.fxml"); + FXUtils.setLimitWidth(this, 500); + FXUtils.setLimitHeight(this, 300); + + VBox center = new VBox(); + this.setCenter(center); + center.setPadding(new Insets(16)); + { + lblTitle = new Label(); + lblTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: BOLD;"); + + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setFitToHeight(true); + scrollPane.setFitToWidth(true); + VBox.setVgrow(scrollPane, Priority.ALWAYS); + { + taskListPane = new TaskListPane(); + scrollPane.setContent(taskListPane); + } + + center.getChildren().setAll(lblTitle, scrollPane); + } + + BorderPane bottom = new BorderPane(); + this.setBottom(bottom); + bottom.setPadding(new Insets(0, 8, 8, 8)); + { + lblProgress = new Label(); + bottom.setLeft(lblProgress); + + btnCancel = new JFXButton(i18n("button.cancel")); + bottom.setRight(btnCancel); + } setCancel(cancel); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 308a9c290..cbffb8e29 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Control; @@ -119,7 +118,6 @@ public class InstallersPage extends Control implements WizardPage { public void cleanup(Map settings) { } - @FXML protected void onInstall() { controller.getSettings().put("name", txtName.getText()); controller.onFinish(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java index 2c8addf96..64c711543 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java @@ -17,15 +17,9 @@ */ package org.jackhuang.hmcl.ui.download; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXTextField; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.ManuallyCreatedModpackException; @@ -39,10 +33,8 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; -import org.jackhuang.hmcl.ui.wizard.WizardPage; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -52,52 +44,21 @@ import java.util.Map; import java.util.Optional; import java.util.logging.Level; -import static javafx.beans.binding.Bindings.createBooleanBinding; import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class LocalModpackPage extends StackPane implements WizardPage { - private final WizardController controller; - - private Modpack manifest = null; - - @FXML - private Region borderPane; - - @FXML - private Label lblName; - - @FXML - private Label lblVersion; - - @FXML - private Label lblAuthor; - - @FXML - private Label lblModpackLocation; - - @FXML - private JFXTextField txtModpackName; - - @FXML - private JFXButton btnInstall; - - @FXML - private SpinnerPane spinnerPane; +public final class LocalModpackPage extends ModpackPage { private final BooleanProperty installAsVersion = new SimpleBooleanProperty(true); + private Modpack manifest = null; private Charset charset; public LocalModpackPage(WizardController controller) { - this.controller = controller; - - FXUtils.loadFXML(this, "/assets/fxml/download/modpack.fxml"); + super(controller); Profile profile = (Profile) controller.getSettings().get("PROFILE"); - File selectedFile; - Optional name = tryCast(controller.getSettings().get(MODPACK_NAME), String.class); if (name.isPresent()) { txtModpackName.setText(name.get()); @@ -116,12 +77,9 @@ public final class LocalModpackPage extends StackPane implements WizardPage { new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); } }); - - btnInstall.disableProperty().bind( - createBooleanBinding(txtModpackName::validate, txtModpackName.textProperty()) - .not()); } + File selectedFile; Optional filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class); if (filePath.isPresent()) { selectedFile = filePath.get(); @@ -138,7 +96,7 @@ public final class LocalModpackPage extends StackPane implements WizardPage { controller.getSettings().put(MODPACK_FILE, selectedFile); } - spinnerPane.showSpinner(); + showSpinner(); Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath())) .thenApplyAsync(encoding -> { charset = encoding; @@ -147,7 +105,7 @@ public final class LocalModpackPage extends StackPane implements WizardPage { }) .whenComplete(Schedulers.javafx(), (manifest, exception) -> { if (exception instanceof ManuallyCreatedModpackException) { - spinnerPane.hideSpinner(); + hideSpinner(); lblName.setText(selectedFile.getName()); installAsVersion.set(false); lblModpackLocation.setText(selectedFile.getAbsolutePath()); @@ -167,7 +125,7 @@ public final class LocalModpackPage extends StackPane implements WizardPage { Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageDialogPane.MessageType.ERROR); Platform.runLater(controller::onEnd); } else { - spinnerPane.hideSpinner(); + hideSpinner(); controller.getSettings().put(MODPACK_MANIFEST, manifest); lblName.setText(manifest.getName()); lblVersion.setText(manifest.getVersion()); @@ -188,26 +146,19 @@ public final class LocalModpackPage extends StackPane implements WizardPage { settings.remove(MODPACK_FILE); } - @FXML - private void onInstall() { + protected void onInstall() { if (!txtModpackName.validate()) return; controller.getSettings().put(MODPACK_NAME, txtModpackName.getText()); controller.getSettings().put(MODPACK_CHARSET, charset); controller.onFinish(); } - @FXML - private void onDescribe() { + protected void onDescribe() { if (manifest != null) { FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); } } - @Override - public String getTitle() { - return i18n("modpack.task.install"); - } - public static final String MODPACK_FILE = "MODPACK_FILE"; public static final String MODPACK_NAME = "MODPACK_NAME"; public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java new file mode 100644 index 000000000..3c1ad859a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java @@ -0,0 +1,119 @@ +package org.jackhuang.hmcl.ui.download; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXTextField; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.wizard.WizardController; +import org.jackhuang.hmcl.ui.wizard.WizardPage; + +import static javafx.beans.binding.Bindings.createBooleanBinding; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public abstract class ModpackPage extends SpinnerPane implements WizardPage { + protected final WizardController controller; + + protected final Label lblName; + protected final Label lblVersion; + protected final Label lblAuthor; + protected final Label lblModpackLocation; + protected final JFXTextField txtModpackName; + protected final JFXButton btnInstall; + + protected ModpackPage(WizardController controller) { + this.controller = controller; + + this.getStyleClass().add("large-spinner-pane"); + + VBox borderPane = new VBox(); + borderPane.setAlignment(Pos.CENTER); + FXUtils.setLimitWidth(borderPane, 500); + + ComponentList componentList = new ComponentList(); + { + BorderPane locationPane = new BorderPane(); + { + locationPane.setLeft(new Label(i18n("modpack.task.install.will"))); + + lblModpackLocation = new Label(); + BorderPane.setAlignment(lblModpackLocation, Pos.CENTER_RIGHT); + locationPane.setCenter(lblModpackLocation); + } + + BorderPane archiveNamePane = new BorderPane(); + { + Label label = new Label(i18n("archive.name")); + BorderPane.setAlignment(label, Pos.CENTER_LEFT); + archiveNamePane.setLeft(label); + + txtModpackName = new JFXTextField(); + BorderPane.setMargin(txtModpackName, new Insets(0, 0, 8, 32)); + BorderPane.setAlignment(txtModpackName, Pos.CENTER_RIGHT); + archiveNamePane.setCenter(txtModpackName); + } + + BorderPane modpackNamePane = new BorderPane(); + { + modpackNamePane.setLeft(new Label(i18n("modpack.name"))); + + lblName = new Label(); + BorderPane.setAlignment(lblName, Pos.CENTER_RIGHT); + modpackNamePane.setCenter(lblName); + } + + BorderPane versionPane = new BorderPane(); + { + versionPane.setLeft(new Label(i18n("archive.version"))); + + lblVersion = new Label(); + BorderPane.setAlignment(lblVersion, Pos.CENTER_RIGHT); + versionPane.setCenter(lblVersion); + } + + BorderPane authorPane = new BorderPane(); + { + authorPane.setLeft(new Label(i18n("archive.author"))); + + lblAuthor = new Label(); + BorderPane.setAlignment(lblAuthor, Pos.CENTER_RIGHT); + authorPane.setCenter(lblAuthor); + } + + BorderPane descriptionPane = new BorderPane(); + { + JFXButton btnDescription = new JFXButton(i18n("modpack.description")); + btnDescription.getStyleClass().add("jfx-button-border"); + btnDescription.setOnAction(e -> onDescribe()); + descriptionPane.setLeft(btnDescription); + + btnInstall = new JFXButton(i18n("button.install")); + btnInstall.getStyleClass().add("jfx-button-raised"); + btnInstall.setButtonType(JFXButton.ButtonType.RAISED); + btnInstall.setOnAction(e -> onInstall()); + btnInstall.disableProperty().bind(createBooleanBinding(() -> !txtModpackName.validate(), txtModpackName.textProperty())); + descriptionPane.setRight(btnInstall); + } + + componentList.getContent().setAll( + locationPane, archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane); + } + + borderPane.getChildren().setAll(componentList); + this.setContent(borderPane); + } + + protected abstract void onInstall(); + + protected abstract void onDescribe(); + + @Override + public String getTitle() { + return i18n("modpack.task.install"); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index 8b132340a..31987d15f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -20,8 +20,12 @@ package org.jackhuang.hmcl.ui.download; import com.jfoenix.controls.JFXButton; import com.jfoenix.effects.JFXDepthManager; import javafx.application.Platform; -import javafx.fxml.FXML; -import javafx.scene.layout.StackPane; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.SVGPath; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.server.ServerModpackManifest; @@ -30,6 +34,8 @@ import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; import org.jackhuang.hmcl.util.TaskCancellationAction; @@ -48,18 +54,23 @@ import static org.jackhuang.hmcl.ui.download.RemoteModpackPage.MODPACK_SERVER_MA import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class ModpackSelectionPage extends StackPane implements WizardPage { +public final class ModpackSelectionPage extends VBox implements WizardPage { private final WizardController controller; - @FXML private JFXButton btnLocal; - @FXML private JFXButton btnRemote; - public ModpackSelectionPage(WizardController controller) { this.controller = controller; - FXUtils.loadFXML(this, "/assets/fxml/download/modpack-source.fxml"); - JFXDepthManager.setDepth(btnLocal, 1); - JFXDepthManager.setDepth(btnRemote, 1); + Label title = new Label(i18n("install.modpack")); + title.setPadding(new Insets(8)); + + this.getStyleClass().add("jfx-list-view"); + this.setMaxSize(400, 150); + this.setSpacing(8); + this.getChildren().setAll( + title, + createButton("local", this::onChooseLocalFile), + createButton("remote", this::onChooseRemoteFile) + ); Optional filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class); if (filePath.isPresent()) { @@ -74,7 +85,30 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage }); } - @FXML + private JFXButton createButton(String type, Runnable action) { + JFXButton button = new JFXButton(); + + button.getStyleClass().add("card"); + button.setStyle("-fx-cursor: HAND;"); + button.prefWidthProperty().bind(this.widthProperty()); + button.setOnAction(e -> action.run()); + + BorderPane graphic = new BorderPane(); + graphic.setMouseTransparent(true); + graphic.setLeft(new TwoLineListItem(i18n("modpack.choose." + type), i18n("modpack.choose." + type + ".detail"))); + + SVGPath arrow = new SVGPath(); + arrow.setContent(SVG.ARROW_RIGHT); + BorderPane.setAlignment(arrow, Pos.CENTER); + graphic.setRight(arrow); + + button.setGraphic(graphic); + + JFXDepthManager.setDepth(button, 1); + + return button; + } + private void onChooseLocalFile() { FileChooser chooser = new FileChooser(); chooser.setTitle(i18n("modpack.choose")); @@ -89,7 +123,6 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage controller.onNext(); } - @FXML private void onChooseRemoteFile() { Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (urlString, resolve, reject) -> { try { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java index 8da94cc27..f36ddbbcc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java @@ -17,14 +17,7 @@ */ package org.jackhuang.hmcl.ui.download; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXTextField; import javafx.application.Platform; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; - import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.mod.server.ServerModpackManifest; import org.jackhuang.hmcl.setting.Profile; @@ -32,59 +25,21 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; -import org.jackhuang.hmcl.ui.wizard.WizardPage; + import java.io.IOException; import java.util.Map; import java.util.Optional; -import static javafx.beans.binding.Bindings.createBooleanBinding; import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class RemoteModpackPage extends StackPane implements WizardPage { - private final WizardController controller; - +public final class RemoteModpackPage extends ModpackPage { private final ServerModpackManifest manifest; - @FXML - private Region borderPane; - - @FXML - private Label lblName; - - @FXML - private Label lblVersion; - - @FXML - private Label lblAuthor; - - @FXML - private Label lblModpackLocation; - - @FXML - private JFXTextField txtModpackName; - - @FXML - private JFXButton btnInstall; - - @FXML - private SpinnerPane spinnerPane; - public RemoteModpackPage(WizardController controller) { - this.controller = controller; - - FXUtils.loadFXML(this, "/assets/fxml/download/modpack.fxml"); - - Profile profile = (Profile) controller.getSettings().get("PROFILE"); - - Optional name = tryCast(controller.getSettings().get(MODPACK_NAME), String.class); - if (name.isPresent()) { - txtModpackName.setText(name.get()); - txtModpackName.setDisable(true); - } + super(controller); manifest = tryCast(controller.getSettings().get(MODPACK_SERVER_MANIFEST), ServerModpackManifest.class) .orElseThrow(() -> new IllegalStateException("MODPACK_SERVER_MANIFEST should exist")); @@ -102,16 +57,18 @@ public class RemoteModpackPage extends StackPane implements WizardPage { lblVersion.setText(manifest.getVersion()); lblAuthor.setText(manifest.getAuthor()); - if (!name.isPresent()) { + Profile profile = (Profile) controller.getSettings().get("PROFILE"); + Optional name = tryCast(controller.getSettings().get(MODPACK_NAME), String.class); + if (name.isPresent()) { + txtModpackName.setText(name.get()); + txtModpackName.setDisable(true); + } else { // trim: https://github.com/huanghongxun/HMCL/issues/962 txtModpackName.setText(manifest.getName().trim()); txtModpackName.getValidators().addAll( new RequiredValidator(), new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); - btnInstall.disableProperty().bind( - createBooleanBinding(txtModpackName::validate, txtModpackName.textProperty()) - .not()); } } @@ -120,23 +77,16 @@ public class RemoteModpackPage extends StackPane implements WizardPage { settings.remove(MODPACK_SERVER_MANIFEST); } - @FXML - private void onInstall() { + protected void onInstall() { if (!txtModpackName.validate()) return; controller.getSettings().put(MODPACK_NAME, txtModpackName.getText()); controller.onFinish(); } - @FXML - private void onDescribe() { + protected void onDescribe() { FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); } - @Override - public String getTitle() { - return i18n("modpack.task.install"); - } - public static final String MODPACK_SERVER_MANIFEST = "MODPACK_SERVER_MANIFEST"; public static final String MODPACK_NAME = "MODPACK_NAME"; public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index 35deea913..65d57953f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -23,14 +23,11 @@ import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXSpinner; import javafx.application.Platform; import javafx.beans.InvalidationListener; -import javafx.fxml.FXML; import javafx.geometry.Insets; +import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.image.Image; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import org.apache.commons.lang3.mutable.MutableObject; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; @@ -45,11 +42,11 @@ import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.VersionIconType; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.HintPane; import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.ui.wizard.Navigation; @@ -75,30 +72,18 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres private final String title; private final Navigation navigation; - @FXML - private JFXListView list; - @FXML - private JFXSpinner spinner; - @FXML - private StackPane failedPane; - @FXML - private StackPane emptyPane; - @FXML - private TransitionPane root; - @FXML - private JFXCheckBox chkRelease; - @FXML - private JFXCheckBox chkSnapshot; - @FXML - private JFXCheckBox chkOld; - @FXML - private JFXButton btnRefresh; - @FXML - private HBox checkPane; - @FXML - private ComponentList centrePane; - @FXML - private StackPane center; + private final JFXListView list; + private final JFXSpinner spinner; + private final StackPane failedPane; + private final StackPane emptyPane; + private final TransitionPane root; + private final JFXCheckBox chkRelease; + private final JFXCheckBox chkSnapshot; + private final JFXCheckBox chkOld; + private final JFXButton btnRefresh; + private final HBox checkPane; + private final ComponentList centrePane; + private final StackPane center; private final VersionList versionList; private CompletableFuture executor; @@ -109,7 +94,75 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres this.libraryId = libraryId; this.navigation = navigation; - FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml"); + HintPane hintPane = new HintPane(); + hintPane.setText(i18n("sponsor.bmclapi")); + hintPane.getStyleClass().add("sponsor-pane"); + hintPane.setOnMouseClicked(e -> onSponsor()); + BorderPane.setMargin(hintPane, new Insets(10, 10, 0, 10)); + this.setTop(hintPane); + + root = new TransitionPane(); + { + spinner = new JFXSpinner(); + + center = new StackPane(); + center.setStyle("-fx-padding: 10;"); + { + centrePane = new ComponentList(); + centrePane.getStyleClass().add("no-padding"); + { + checkPane = new HBox(); + checkPane.setSpacing(10); + { + chkRelease = new JFXCheckBox(i18n("version.game.releases")); + chkRelease.setSelected(true); + HBox.setMargin(chkRelease, new Insets(10, 0, 10, 0)); + + chkSnapshot = new JFXCheckBox(i18n("version.game.snapshots")); + HBox.setMargin(chkSnapshot, new Insets(10, 0, 10, 0)); + + chkOld = new JFXCheckBox(i18n("version.game.old")); + HBox.setMargin(chkOld, new Insets(10, 0, 10, 0)); + + HBox pane = new HBox(); + HBox.setHgrow(pane, Priority.ALWAYS); + + btnRefresh = new JFXButton(i18n("button.refresh")); + btnRefresh.getStyleClass().add("jfx-tool-bar-button"); + btnRefresh.setOnAction(e -> onRefresh()); + + checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld, pane, btnRefresh); + } + + list = new JFXListView<>(); + list.getStyleClass().add("jfx-list-view-float"); + VBox.setVgrow(list, Priority.ALWAYS); + + centrePane.getContent().setAll(checkPane, list); + } + + center.getChildren().setAll(centrePane); + } + + failedPane = new StackPane(); + failedPane.getStyleClass().add("notice-pane"); + { + Label label = new Label(i18n("download.failed.refresh")); + label.setOnMouseClicked(e -> onRefresh()); + + failedPane.getChildren().setAll(label); + } + + emptyPane = new StackPane(); + emptyPane.getStyleClass().add("notice-pane"); + { + Label label = new Label(i18n("download.failed.empty")); + label.setOnMouseClicked(e -> onBack()); + + emptyPane.getChildren().setAll(label); + } + } + this.setCenter(root); versionList = downloadProvider.getVersionListById(libraryId); if (versionList.hasType()) { @@ -205,15 +258,12 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres executor.cancel(true); } - @FXML private void onRefresh() { refresh(); } - @FXML private void onBack() { navigation.onPrev(true); } - @FXML private void onSponsor() { HMCLService.openRedirectLink("bmclapi_sponsor"); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java index 88836fb01..742a3a87f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java @@ -17,17 +17,18 @@ */ package org.jackhuang.hmcl.ui.export; +import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTreeView; -import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBoxTreeItem; import javafx.scene.control.Label; import javafx.scene.control.TreeItem; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; -import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; @@ -47,12 +48,10 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; /** * @author huangyuhui */ -public final class ModpackFileSelectionPage extends StackPane implements WizardPage { +public final class ModpackFileSelectionPage extends BorderPane implements WizardPage { private final WizardController controller; private final String version; private final ModAdviser adviser; - @FXML - private JFXTreeView treeView; private final CheckBoxTreeItem rootNode; public ModpackFileSelectionPage(WizardController controller, Profile profile, String version, ModAdviser adviser) { @@ -60,10 +59,26 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP this.version = version; this.adviser = adviser; - FXUtils.loadFXML(this, "/assets/fxml/modpack/selection.fxml"); + JFXTreeView treeView = new JFXTreeView<>(); rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft"); treeView.setRoot(rootNode); treeView.setSelectionModel(new NoneMultipleSelectionModel<>()); + this.setCenter(treeView); + + HBox nextPane = new HBox(); + nextPane.setPadding(new Insets(16, 16, 16, 0)); + nextPane.setAlignment(Pos.CENTER_RIGHT); + { + JFXButton btnNext = new JFXButton(i18n("wizard.next")); + btnNext.getStyleClass().add("jfx-button-raised"); + btnNext.setPrefSize(100, 40); + btnNext.setButtonType(JFXButton.ButtonType.RAISED); + btnNext.setOnAction(e -> onNext()); + + nextPane.getChildren().setAll(btnNext); + } + + this.setBottom(nextPane); } private CheckBoxTreeItem getTreeItem(File file, String basePath) { @@ -145,7 +160,6 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP controller.getSettings().remove(MODPACK_FILE_SELECTION); } - @FXML private void onNext() { ArrayList list = new ArrayList<>(); getFilesNeeded(rootNode, "minecraft", list); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java index 63e4f0046..836c99167 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java @@ -18,13 +18,18 @@ package org.jackhuang.hmcl.ui.export; import com.jfoenix.controls.JFXButton; -import javafx.fxml.FXML; -import javafx.scene.layout.StackPane; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.SVGPath; import org.jackhuang.hmcl.mod.ModpackExportInfo; import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask; import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask; import org.jackhuang.hmcl.mod.server.ServerModpackExportTask; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; @@ -33,31 +38,47 @@ import java.util.Map; import static org.jackhuang.hmcl.ui.export.ModpackInfoPage.MODPACK_INFO_OPTION; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class ModpackTypeSelectionPage extends StackPane implements WizardPage { +public final class ModpackTypeSelectionPage extends VBox implements WizardPage { private final WizardController controller; - @FXML - private JFXButton btnMCBBS; - @FXML - private JFXButton btnMultiMC; - @FXML - private JFXButton btnServer; public ModpackTypeSelectionPage(WizardController controller) { this.controller = controller; - FXUtils.loadFXML(this, "/assets/fxml/modpack/type.fxml"); - JFXButton[] buttons = new JFXButton[]{btnMCBBS, btnMultiMC, btnServer}; - String[] types = new String[]{MODPACK_TYPE_MCBBS, MODPACK_TYPE_MULTIMC, MODPACK_TYPE_SERVER}; - ModpackExportInfo.Options[] options = new ModpackExportInfo.Options[]{McbbsModpackExportTask.OPTION, MultiMCModpackExportTask.OPTION, ServerModpackExportTask.OPTION}; - for (int i = 0; i < types.length; ++i) { - String type = types[i]; - ModpackExportInfo.Options option = options[i]; - buttons[i].setOnMouseClicked(e -> { - controller.getSettings().put(MODPACK_TYPE, type); - controller.getSettings().put(MODPACK_INFO_OPTION, option); - controller.onNext(); - }); - } + Label title = new Label(i18n("modpack.export.as")); + title.setPadding(new Insets(8)); + + this.getStyleClass().add("jfx-list-view"); + this.setMaxSize(300, 150); + this.getChildren().setAll( + title, + createButton(MODPACK_TYPE_MCBBS, McbbsModpackExportTask.OPTION), + createButton(MODPACK_TYPE_MULTIMC, MultiMCModpackExportTask.OPTION), + createButton(MODPACK_TYPE_SERVER, ServerModpackExportTask.OPTION) + ); + } + + private JFXButton createButton(String type, ModpackExportInfo.Options option) { + JFXButton button = new JFXButton(); + button.setOnAction(e -> { + controller.getSettings().put(MODPACK_TYPE, type); + controller.getSettings().put(MODPACK_INFO_OPTION, option); + controller.onNext(); + }); + + button.prefWidthProperty().bind(this.widthProperty()); + + BorderPane graphic = new BorderPane(); + graphic.setMouseTransparent(true); + graphic.setLeft(new TwoLineListItem(i18n("modpack.type." + type), i18n("modpack.type." + type + ".export"))); + + SVGPath arrow = new SVGPath(); + arrow.setContent(SVG.ARROW_RIGHT); + BorderPane.setAlignment(arrow, Pos.CENTER); + graphic.setRight(arrow); + + button.setGraphic(graphic); + + return button; } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfilePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfilePage.java index 86dcec548..dc306adad 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfilePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfilePage.java @@ -18,19 +18,26 @@ package org.jackhuang.hmcl.ui.profile; import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXTextField; +import com.jfoenix.validation.RequiredFieldValidator; import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.FileItem; +import org.jackhuang.hmcl.ui.construct.OptionToggleButton; import org.jackhuang.hmcl.ui.construct.PageCloseEvent; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.StringUtils; @@ -40,15 +47,14 @@ import java.util.Optional; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class ProfilePage extends StackPane implements DecoratorPage { +public final class ProfilePage extends BorderPane implements DecoratorPage { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); private final StringProperty location; private final Profile profile; - @FXML private JFXTextField txtProfileName; - @FXML private FileItem gameDir; - @FXML private JFXButton btnSave; - @FXML private JFXCheckBox toggleUseRelativePath; + private final JFXTextField txtProfileName; + private final FileItem gameDir; + private final OptionToggleButton toggleUseRelativePath; /** * @param profile null if creating a new profile. @@ -61,37 +67,86 @@ public final class ProfilePage extends StackPane implements DecoratorPage { location = new SimpleStringProperty(this, "location", Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(".minecraft")); - FXUtils.loadFXML(this, "/assets/fxml/profile.fxml"); - - txtProfileName.setText(profileDisplayName); - txtProfileName.getValidators().add(new ValidatorBase() { + ScrollPane scroll = new ScrollPane(); + this.setCenter(scroll); + scroll.setFitToHeight(true); + scroll.setFitToWidth(true); + { + VBox rootPane = new VBox(); + rootPane.setStyle("-fx-padding: 20;"); { - setMessage(i18n("profile.already_exists")); + ComponentList componentList = new ComponentList(); + componentList.setDepth(1); + { + BorderPane profileNamePane = new BorderPane(); + { + Label label = new Label(i18n("profile.name")); + profileNamePane.setLeft(label); + BorderPane.setAlignment(label, Pos.CENTER_LEFT); + + txtProfileName = new JFXTextField(); + profileNamePane.setRight(txtProfileName); + RequiredFieldValidator validator = new RequiredFieldValidator(); + validator.setMessage(i18n("input.not_empty")); + txtProfileName.getValidators().add(validator); + BorderPane.setMargin(txtProfileName, new Insets(8, 0, 8, 0)); + + txtProfileName.setText(profileDisplayName); + txtProfileName.getValidators().add(new ValidatorBase() { + { + setMessage(i18n("profile.already_exists")); + } + + @Override + protected void eval() { + JFXTextField control = (JFXTextField) this.getSrcControl(); + hasErrors.set(Profiles.getProfiles().stream().anyMatch(profile -> profile.getName().equals(control.getText()))); + } + }); + } + + gameDir = new FileItem(); + gameDir.setName(i18n("profile.instance_directory")); + gameDir.setTitle(i18n("profile.instance_directory.choose")); + gameDir.pathProperty().bindBidirectional(location); + + toggleUseRelativePath = new OptionToggleButton(); + toggleUseRelativePath.setTitle(i18n("profile.use_relative_path")); + + gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty()); + if (profile != null) { + toggleUseRelativePath.setSelected(profile.isUseRelativePath()); + } + + componentList.getContent().setAll(profileNamePane, gameDir, toggleUseRelativePath); + } + + rootPane.getChildren().setAll(componentList); } - @Override - protected void eval() { - JFXTextField control = (JFXTextField) this.getSrcControl(); - if (Profiles.getProfiles().stream().anyMatch(profile -> profile.getName().equals(control.getText()))) - hasErrors.set(true); - else - hasErrors.set(false); - } - }); - FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> { - btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation())); - }); - gameDir.pathProperty().bindBidirectional(location); - FXUtils.onChangeAndOperate(location, it -> { - btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation())); - }); - gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty()); - if (profile != null) { - toggleUseRelativePath.setSelected(profile.isUseRelativePath()); + scroll.setContent(rootPane); + } + + BorderPane savePane = new BorderPane(); + this.setBottom(savePane); + savePane.setPickOnBounds(false); + savePane.setStyle("-fx-padding: 20;"); + StackPane.setAlignment(savePane, Pos.BOTTOM_RIGHT); + { + JFXButton saveButton = new JFXButton(i18n("button.save")); + savePane.setRight(saveButton); + BorderPane.setAlignment(savePane, Pos.BOTTOM_RIGHT); + StackPane.setAlignment(saveButton, Pos.BOTTOM_RIGHT); + saveButton.getStyleClass().add("jfx-button-raised"); + saveButton.setButtonType(JFXButton.ButtonType.RAISED); + saveButton.setPrefSize(100, 40); + saveButton.setOnAction(e -> onSave()); + saveButton.disableProperty().bind(Bindings.createBooleanBinding( + () -> !txtProfileName.validate() || StringUtils.isBlank(getLocation()), + txtProfileName.textProperty(), location)); } } - @FXML private void onSave() { if (profile != null) { profile.setName(txtProfileName.getText()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java index 252265bea..5ec3d2e31 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java @@ -44,7 +44,6 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler { private static final class Hole { @SuppressWarnings("unchecked") static final Pair[] SOURCE = (Pair[]) new Pair[]{ - pair("javafx.fxml.LoadException", i18n("crash.NoClassDefFound")), pair("Location is not set", i18n("crash.NoClassDefFound")), pair("UnsatisfiedLinkError", i18n("crash.user_fault")), pair("java.time.zone.ZoneRulesException: Unable to load TZDB time-zone rules", i18n("crash.user_fault")), diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 6b3f8b313..60b828c4a 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -431,12 +431,6 @@ -fx-wrap-text: true; } -.jfx-layout-body > .label { - -fx-font-size: 14.0px; - -fx-pref-width: 400.0px; - -fx-wrap-text: true; -} - .jfx-layout-actions { -fx-pref-width: 400; -fx-padding: 10.0px 0.0 0.0 0.0; diff --git a/HMCL/src/main/resources/assets/fxml/account.fxml b/HMCL/src/main/resources/assets/fxml/account.fxml deleted file mode 100644 index 392473a58..000000000 --- a/HMCL/src/main/resources/assets/fxml/account.fxml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/authlib-injector-server-add.fxml b/HMCL/src/main/resources/assets/fxml/authlib-injector-server-add.fxml deleted file mode 100644 index 7e9092cca..000000000 --- a/HMCL/src/main/resources/assets/fxml/authlib-injector-server-add.fxml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml b/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml deleted file mode 100644 index 4b17cdc41..000000000 --- a/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
-
diff --git a/HMCL/src/main/resources/assets/fxml/download/modpack-source.fxml b/HMCL/src/main/resources/assets/fxml/download/modpack-source.fxml deleted file mode 100644 index 8b9fe41cd..000000000 --- a/HMCL/src/main/resources/assets/fxml/download/modpack-source.fxml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml deleted file mode 100644 index 1f14a18fa..000000000 --- a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - -
-
- -
-
-
- - - - -
-
-
-
diff --git a/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml b/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml deleted file mode 100644 index 75ec6a77b..000000000 --- a/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/download/versions.fxml b/HMCL/src/main/resources/assets/fxml/download/versions.fxml deleted file mode 100644 index 342478729..000000000 --- a/HMCL/src/main/resources/assets/fxml/download/versions.fxml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
diff --git a/HMCL/src/main/resources/assets/fxml/input-dialog.fxml b/HMCL/src/main/resources/assets/fxml/input-dialog.fxml deleted file mode 100644 index f8ad63db1..000000000 --- a/HMCL/src/main/resources/assets/fxml/input-dialog.fxml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/message-dialog.fxml b/HMCL/src/main/resources/assets/fxml/message-dialog.fxml deleted file mode 100644 index 5fff367e7..000000000 --- a/HMCL/src/main/resources/assets/fxml/message-dialog.fxml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/modpack/selection.fxml b/HMCL/src/main/resources/assets/fxml/modpack/selection.fxml deleted file mode 100644 index 5d3746797..000000000 --- a/HMCL/src/main/resources/assets/fxml/modpack/selection.fxml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - -
- -
- - - - - -
-
diff --git a/HMCL/src/main/resources/assets/fxml/modpack/type.fxml b/HMCL/src/main/resources/assets/fxml/modpack/type.fxml deleted file mode 100644 index e684a5e68..000000000 --- a/HMCL/src/main/resources/assets/fxml/modpack/type.fxml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/profile.fxml b/HMCL/src/main/resources/assets/fxml/profile.fxml deleted file mode 100644 index d477dc6d7..000000000 --- a/HMCL/src/main/resources/assets/fxml/profile.fxml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/task-dialog.fxml b/HMCL/src/main/resources/assets/fxml/task-dialog.fxml deleted file mode 100644 index 4f1d67158..000000000 --- a/HMCL/src/main/resources/assets/fxml/task-dialog.fxml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - -
- - -
- - - - -
-
diff --git a/javafx.gradle.kts b/javafx.gradle.kts index bfb5507f9..1da1e6352 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -28,7 +28,7 @@ data class Platform( ) } -val jfxModules = listOf("base", "graphics", "controls", "fxml", "media", "web") +val jfxModules = listOf("base", "graphics", "controls", "media", "web") val jfxMirrorRepos = listOf("https://maven.aliyun.com/repository/central") val jfxDependenciesFile = project("HMCL").buildDir.resolve("openjfx-dependencies.json") val jfxPlatforms = listOf(