feat: AddAccountPane tabularization

This commit is contained in:
huanghongxun 2021-08-22 17:05:10 +08:00
parent 2fea07fc6c
commit 4bdacb66a6
6 changed files with 105 additions and 39 deletions

View File

@ -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<AccountFactory<?>> cboType;
@FXML private JFXComboBox<AuthlibInjectorServer> 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<AuthlibInjectorServer> 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<Hyperlink> links = new SimpleListProperty<>();
private final Map<AccountFactory<?>, TabControl.Tab<Node>> tabMap = new HashMap<>();
public AddAccountPane() {
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
List<TabControl.Tab<Node>> tabs = new ArrayList<>();
for (AccountFactory<?> factory : Accounts.FACTORIES) {
TabControl.Tab<Node> 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<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty();
ReadOnlyObjectProperty<TabHeader.Tab<?>> 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<Hyperlink> 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))

View File

@ -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<T> node = new SimpleObjectProperty<>(this, "node");
private final ObjectProperty<Object> userData = new SimpleObjectProperty<>(this, "userData");
private Supplier<? extends T> 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) {

View File

@ -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);

View File

@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.main;
import javafx.beans.property.ReadOnlyObjectProperty;

View File

@ -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 *

View File

@ -23,10 +23,7 @@
<ColumnConstraints minWidth="140"/>
</columnConstraints>
<Label text="%account.methods" GridPane.halignment="LEFT" GridPane.columnIndex="0"
GridPane.rowIndex="0"/>
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.columnSpan="2"/>
<StackPane fx:id="tabHeaderPane" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="0" />
<Label fx:id="lblInjectorServer" text="%account.injector.server" GridPane.halignment="LEFT"
GridPane.columnIndex="0" GridPane.rowIndex="1"/>