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.collections.FXCollections;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Hyperlink; import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException; 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 org.jackhuang.hmcl.util.skin.InvalidSkinException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -65,57 +68,85 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AddAccountPane extends StackPane { public class AddAccountPane extends StackPane {
@FXML private JFXTextField txtUsername; @FXML
@FXML private JFXPasswordField txtPassword; private JFXTextField txtUsername;
@FXML private Label lblCreationWarning; @FXML
@FXML private Label lblPassword; private JFXPasswordField txtPassword;
@FXML private Label lblUsername; @FXML
@FXML private JFXComboBox<AccountFactory<?>> cboType; private Label lblCreationWarning;
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers; @FXML
@FXML private Label lblInjectorServer; private Label lblPassword;
@FXML private JFXButton btnAccept; @FXML
@FXML private JFXButton btnAddServer; private Label lblUsername;
@FXML private JFXButton btnManageServer; @FXML
@FXML private SpinnerPane acceptPane; private StackPane tabHeaderPane;
@FXML private HBox linksContainer; @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 ListProperty<Hyperlink> links = new SimpleListProperty<>();
private final Map<AccountFactory<?>, TabControl.Tab<Node>> tabMap = new HashMap<>();
public AddAccountPane() { public AddAccountPane() {
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml"); 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.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName)); cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers()); Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());
cboServers.getItems().addListener(onInvalidating(this::resetServerSelection)); cboServers.getItems().addListener(onInvalidating(this::resetServerSelection));
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()); btnAddServer.visibleProperty().bind(cboServers.visibleProperty());
btnManageServer.visibleProperty().bind(cboServers.visibleProperty()); btnManageServer.visibleProperty().bind(cboServers.visibleProperty());
cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer)); cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer));
checkIfNoServer(); checkIfNoServer();
ReadOnlyObjectProperty<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty(); ReadOnlyObjectProperty<TabHeader.Tab<?>> loginType = tabHeader.getSelectionModel().selectedItemProperty();
// remember the last used login type // 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()); 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()); 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()); lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
txtUsername.getValidators().add(new Validator(i18n("input.email"), this::validateUsername)); txtUsername.getValidators().add(new Validator(i18n("input.email"), this::validateUsername));
@ -142,7 +173,7 @@ public class AddAccountPane extends StackPane {
} }
private boolean validateUsername(String username) { private boolean validateUsername(String username) {
AccountFactory<?> loginType = cboType.getSelectionModel().getSelectedItem(); AccountFactory<?> loginType = ((AccountFactory<?>) tabHeader.getSelectionModel().getSelectedItem().getUserData());
if (loginType == Accounts.FACTORY_OFFLINE) { if (loginType == Accounts.FACTORY_OFFLINE) {
return true; return true;
} else if (loginType == Accounts.FACTORY_AUTHLIB_INJECTOR) { } else if (loginType == Accounts.FACTORY_AUTHLIB_INJECTOR) {
@ -155,7 +186,7 @@ public class AddAccountPane extends StackPane {
return username.contains("@"); return username.contains("@");
} }
private static final String[] ALLOWED_LINKS = { "register" }; private static final String[] ALLOWED_LINKS = {"register"};
public static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) { public static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) {
if (server == null) { 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)}. * Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}.
*/ */
private Object getAuthAdditionalData() { private Object getAuthAdditionalData() {
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem(); AccountFactory<?> factory = ((AccountFactory<?>) tabHeader.getSelectionModel().getSelectedItem().getUserData());
if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) { if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) {
return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null"); return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null");
} }
@ -215,7 +246,7 @@ public class AddAccountPane extends StackPane {
String username = txtUsername.getText(); String username = txtUsername.getText();
String password = txtPassword.getText(); String password = txtPassword.getText();
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem(); AccountFactory<?> factory = ((AccountFactory<?>) tabHeader.getSelectionModel().getSelectedItem().getUserData());
Object additionalData = getAuthAdditionalData(); Object additionalData = getAuthAdditionalData();
Task.supplyAsync(() -> factory.create(new Selector(), username, password, additionalData)) 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 StringProperty text = new SimpleStringProperty(this, "text");
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected"); private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected");
private final ObjectProperty<T> node = new SimpleObjectProperty<>(this, "node"); private final ObjectProperty<T> node = new SimpleObjectProperty<>(this, "node");
private final ObjectProperty<Object> userData = new SimpleObjectProperty<>(this, "userData");
private Supplier<? extends T> nodeSupplier; private Supplier<? extends T> nodeSupplier;
public Tab(String id) { public Tab(String id) {
@ -246,6 +247,18 @@ public interface TabControl {
this.node.set(node); 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() { public boolean initializeIfNeeded() {
if (getNode() == null) { if (getNode() == null) {
if (getNodeSupplier() == null) { if (getNodeSupplier() == null) {

View File

@ -161,7 +161,6 @@ public class TabHeader extends Control implements TabControl {
getChildren().setAll(headersRegion, selectedTabLine); getChildren().setAll(headersRegion, selectedTabLine);
headersRegion.setPickOnBounds(false); headersRegion.setPickOnBounds(false);
headersRegion.prefHeightProperty().bind(heightProperty()); headersRegion.prefHeightProperty().bind(heightProperty());
prefWidthProperty().bind(headersRegion.widthProperty());
rotate.pivotXProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 0.0 : 1, getSkinnable().sideProperty())); 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())); 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.setCenter(tabText);
inner.getStyleClass().add("tab-container"); inner.getStyleClass().add("tab-container");
rippler = new JFXRippler(inner, JFXRippler.RipplerPos.FRONT); rippler = new JFXRippler(inner, JFXRippler.RipplerPos.FRONT);
rippler.getStyleClass().add("tab-rippler");
rippler.setRipplerFill(ripplerColor); rippler.setRipplerFill(ripplerColor);
getChildren().setAll(rippler); 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; package org.jackhuang.hmcl.ui.main;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;

View File

@ -260,6 +260,10 @@
-fx-background-color: derive(-fx-base-color, -30%); -fx-background-color: derive(-fx-base-color, -30%);
} }
.tab-rippler {
-jfx-rippler-fill: -fx-base-check-color;
}
.jfx-tab-pane .jfx-rippler { .jfx-tab-pane .jfx-rippler {
-jfx-rippler-fill: white; -jfx-rippler-fill: white;
} }
@ -272,6 +276,10 @@
-fx-padding: 10 17 10 17; -fx-padding: 10 17 10 17;
} }
.add-account-tab-header .tab-label {
-fx-font-size: 12;
}
/******************************************************************************* /*******************************************************************************
* * * *
* JFX Dialog Layout * * JFX Dialog Layout *

View File

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