From 4bdacb66a666ca906dfbb8c17d14266d3eb815be Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sun, 22 Aug 2021 17:05:10 +0800 Subject: [PATCH] feat: AddAccountPane tabularization --- .../hmcl/ui/account/AddAccountPane.java | 99 ++++++++++++------- .../hmcl/ui/construct/TabControl.java | 13 +++ .../hmcl/ui/construct/TabHeader.java | 2 +- .../hmcl/ui/main/LauncherSettingsPage.java | 17 ++++ HMCL/src/main/resources/assets/css/root.css | 8 ++ .../resources/assets/fxml/account-add.fxml | 5 +- 6 files changed, 105 insertions(+), 39 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java index bb6c555e8..f7360ae01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java @@ -29,11 +29,13 @@ import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException; @@ -52,6 +54,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.skin.InvalidSkinException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -65,57 +68,85 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class AddAccountPane extends StackPane { - @FXML private JFXTextField txtUsername; - @FXML private JFXPasswordField txtPassword; - @FXML private Label lblCreationWarning; - @FXML private Label lblPassword; - @FXML private Label lblUsername; - @FXML private JFXComboBox> cboType; - @FXML private JFXComboBox cboServers; - @FXML private Label lblInjectorServer; - @FXML private JFXButton btnAccept; - @FXML private JFXButton btnAddServer; - @FXML private JFXButton btnManageServer; - @FXML private SpinnerPane acceptPane; - @FXML private HBox linksContainer; + @FXML + private JFXTextField txtUsername; + @FXML + private JFXPasswordField txtPassword; + @FXML + private Label lblCreationWarning; + @FXML + private Label lblPassword; + @FXML + private Label lblUsername; + @FXML + private StackPane tabHeaderPane; + @FXML + private JFXComboBox cboServers; + @FXML + private Label lblInjectorServer; + @FXML + private JFXButton btnAccept; + @FXML + private JFXButton btnAddServer; + @FXML + private JFXButton btnManageServer; + @FXML + private SpinnerPane acceptPane; + @FXML + private HBox linksContainer; + + private final TabHeader tabHeader; private ListProperty links = new SimpleListProperty<>(); + private final Map, TabControl.Tab> tabMap = new HashMap<>(); + public AddAccountPane() { FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml"); + List> tabs = new ArrayList<>(); + for (AccountFactory factory : Accounts.FACTORIES) { + TabControl.Tab tab = new TabControl.Tab<>(factory.getLoginType().name(), Accounts.getLocalizedLoginTypeName(factory)); + tab.setUserData(factory); + tabMap.put(factory, tab); + tabs.add(tab); + } + + tabHeader = new TabHeader(tabs.toArray(new TabControl.Tab[0])); + tabHeader.getStyleClass().add("add-account-tab-header"); + // try selecting the preferred login type + tabHeader.getSelectionModel().select( + tabMap.get(tabMap.keySet().stream().filter(factory -> Accounts.getLoginType(factory).equals(config().getPreferredLoginType())) + .findFirst().orElse(Accounts.FACTORY_OFFLINE)) + ); + + tabHeaderPane.getChildren().setAll(tabHeader); + tabHeader.setMinWidth(Region.USE_PREF_SIZE); + + cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl()))); cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName)); Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers()); cboServers.getItems().addListener(onInvalidating(this::resetServerSelection)); resetServerSelection(); - cboType.getItems().setAll(Accounts.FACTORIES); - cboType.setConverter(stringConverter(Accounts::getLocalizedLoginTypeName)); - // try selecting the preferred login type - cboType.getSelectionModel().select( - cboType.getItems().stream() - .filter(type -> Accounts.getLoginType(type).equals(config().getPreferredLoginType())) - .findFirst() - .orElse(Accounts.FACTORY_OFFLINE)); - btnAddServer.visibleProperty().bind(cboServers.visibleProperty()); btnManageServer.visibleProperty().bind(cboServers.visibleProperty()); cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer)); checkIfNoServer(); - ReadOnlyObjectProperty> loginType = cboType.getSelectionModel().selectedItemProperty(); + ReadOnlyObjectProperty> loginType = tabHeader.getSelectionModel().selectedItemProperty(); // remember the last used login type - loginType.addListener((observable, oldValue, newValue) -> config().setPreferredLoginType(Accounts.getLoginType(newValue))); + loginType.addListener((observable, oldValue, newValue) -> config().setPreferredLoginType(Accounts.getLoginType((AccountFactory) newValue.getUserData()))); - txtUsername.visibleProperty().bind(Bindings.createBooleanBinding(() -> loginType.get().getLoginType().requiresUsername, loginType)); + txtUsername.visibleProperty().bind(Bindings.createBooleanBinding(() -> ((AccountFactory) loginType.get().getUserData()).getLoginType().requiresUsername, loginType)); lblUsername.visibleProperty().bind(txtUsername.visibleProperty()); - txtPassword.visibleProperty().bind(Bindings.createBooleanBinding(() -> loginType.get().getLoginType().requiresPassword, loginType)); + txtPassword.visibleProperty().bind(Bindings.createBooleanBinding(() -> ((AccountFactory) loginType.get().getUserData()).getLoginType().requiresPassword, loginType)); lblPassword.visibleProperty().bind(txtPassword.visibleProperty()); - cboServers.visibleProperty().bind(loginType.isEqualTo(Accounts.FACTORY_AUTHLIB_INJECTOR)); + cboServers.visibleProperty().bind(loginType.isEqualTo(tabMap.get(Accounts.FACTORY_AUTHLIB_INJECTOR))); lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty()); txtUsername.getValidators().add(new Validator(i18n("input.email"), this::validateUsername)); @@ -123,9 +154,9 @@ public class AddAccountPane extends StackPane { btnAccept.disableProperty().bind(Bindings.createBooleanBinding( () -> !( // consider the opposite situation: input is valid (!txtUsername.isVisible() || txtUsername.validate()) && - // invisible means the field is not needed, neither should it be validated - (!txtPassword.isVisible() || txtPassword.validate()) && - (!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null) + // invisible means the field is not needed, neither should it be validated + (!txtPassword.isVisible() || txtPassword.validate()) && + (!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null) ), txtUsername.textProperty(), txtPassword.textProperty(), loginType, cboServers.getSelectionModel().selectedItemProperty(), @@ -142,7 +173,7 @@ public class AddAccountPane extends StackPane { } private boolean validateUsername(String username) { - AccountFactory loginType = cboType.getSelectionModel().getSelectedItem(); + AccountFactory loginType = ((AccountFactory) tabHeader.getSelectionModel().getSelectedItem().getUserData()); if (loginType == Accounts.FACTORY_OFFLINE) { return true; } else if (loginType == Accounts.FACTORY_AUTHLIB_INJECTOR) { @@ -155,7 +186,7 @@ public class AddAccountPane extends StackPane { return username.contains("@"); } - private static final String[] ALLOWED_LINKS = { "register" }; + private static final String[] ALLOWED_LINKS = {"register"}; public static List createHyperlinks(AuthlibInjectorServer server) { if (server == null) { @@ -197,7 +228,7 @@ public class AddAccountPane extends StackPane { * Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}. */ private Object getAuthAdditionalData() { - AccountFactory factory = cboType.getSelectionModel().getSelectedItem(); + AccountFactory factory = ((AccountFactory) tabHeader.getSelectionModel().getSelectedItem().getUserData()); if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) { return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null"); } @@ -215,7 +246,7 @@ public class AddAccountPane extends StackPane { String username = txtUsername.getText(); String password = txtPassword.getText(); - AccountFactory factory = cboType.getSelectionModel().getSelectedItem(); + AccountFactory factory = ((AccountFactory) tabHeader.getSelectionModel().getSelectedItem().getUserData()); Object additionalData = getAuthAdditionalData(); Task.supplyAsync(() -> factory.create(new Selector(), username, password, additionalData)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java index 89a6e6cb7..aff0819a9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java @@ -179,6 +179,7 @@ public interface TabControl { private final StringProperty text = new SimpleStringProperty(this, "text"); private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected"); private final ObjectProperty node = new SimpleObjectProperty<>(this, "node"); + private final ObjectProperty userData = new SimpleObjectProperty<>(this, "userData"); private Supplier nodeSupplier; public Tab(String id) { @@ -246,6 +247,18 @@ public interface TabControl { this.node.set(node); } + public Object getUserData() { + return userData.get(); + } + + public ObjectProperty userDataProperty() { + return userData; + } + + public void setUserData(Object userData) { + this.userData.set(userData); + } + public boolean initializeIfNeeded() { if (getNode() == null) { if (getNodeSupplier() == null) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java index 0bacedcb4..fcc1c763f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java @@ -161,7 +161,6 @@ public class TabHeader extends Control implements TabControl { getChildren().setAll(headersRegion, selectedTabLine); headersRegion.setPickOnBounds(false); headersRegion.prefHeightProperty().bind(heightProperty()); - prefWidthProperty().bind(headersRegion.widthProperty()); rotate.pivotXProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 0.0 : 1, getSkinnable().sideProperty())); rotate.pivotYProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 1.0 : 0, getSkinnable().sideProperty())); @@ -552,6 +551,7 @@ public class TabHeader extends Control implements TabControl { inner.setCenter(tabText); inner.getStyleClass().add("tab-container"); rippler = new JFXRippler(inner, JFXRippler.RipplerPos.FRONT); + rippler.getStyleClass().add("tab-rippler"); rippler.setRipplerFill(ripplerColor); getChildren().setAll(rippler); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index d31f45621..bfb7710cf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.ui.main; import javafx.beans.property.ReadOnlyObjectProperty; diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c725e271e..c5879078d 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -260,6 +260,10 @@ -fx-background-color: derive(-fx-base-color, -30%); } +.tab-rippler { + -jfx-rippler-fill: -fx-base-check-color; +} + .jfx-tab-pane .jfx-rippler { -jfx-rippler-fill: white; } @@ -272,6 +276,10 @@ -fx-padding: 10 17 10 17; } +.add-account-tab-header .tab-label { + -fx-font-size: 12; +} + /******************************************************************************* * * * JFX Dialog Layout * diff --git a/HMCL/src/main/resources/assets/fxml/account-add.fxml b/HMCL/src/main/resources/assets/fxml/account-add.fxml index c47e55a63..b5ad75f90 100644 --- a/HMCL/src/main/resources/assets/fxml/account-add.fxml +++ b/HMCL/src/main/resources/assets/fxml/account-add.fxml @@ -23,10 +23,7 @@ -