mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-15 14:56:05 -04:00
feat: AddAccountPane tabularization
This commit is contained in:
parent
2fea07fc6c
commit
4bdacb66a6
@ -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))
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 *
|
||||
|
@ -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"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user