diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItem2.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItem2.java index 93506b15d..bd091f404 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItem2.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItem2.java @@ -29,6 +29,8 @@ import javafx.scene.layout.VBox; import javafx.scene.text.TextAlignment; import org.jackhuang.hmcl.setting.Theme; +import java.util.Optional; + public class AdvancedListItem2 extends StackPane { public AdvancedListItem2(AdvancedListItemViewModel viewModel) { @@ -47,6 +49,8 @@ public class AdvancedListItem2 extends StackPane { FXUtils.limitSize(imageView, 32, 32); imageView.setPreserveRatio(true); imageView.imageProperty().bind(viewModel.imageProperty()); + Optional.ofNullable(viewModel.viewportProperty()) + .ifPresent(imageView.viewportProperty()::bind); imageViewContainer.getChildren().setAll(imageView); VBox vbox = new VBox(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItemViewModel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItemViewModel.java index d902c6ae9..ffb1fa339 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItemViewModel.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AdvancedListItemViewModel.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui; import javafx.beans.property.ObjectProperty; import javafx.beans.property.StringProperty; +import javafx.geometry.Rectangle2D; import javafx.scene.image.Image; public abstract class AdvancedListItemViewModel { @@ -27,6 +28,10 @@ public abstract class AdvancedListItemViewModel { public abstract ObjectProperty imageProperty(); + public ObjectProperty viewportProperty() { + return null; + } + public abstract StringProperty titleProperty(); public abstract StringProperty subtitleProperty(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index c27d2ee4a..a70dc1b0f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -27,6 +27,8 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.ui.account.AccountListView; +import org.jackhuang.hmcl.ui.account.AccountListViewModel; import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage; import org.jackhuang.hmcl.ui.construct.InputDialogPane; import org.jackhuang.hmcl.ui.construct.MessageBox; @@ -49,6 +51,7 @@ public final class Controllers { private static SettingsPage settingsPage = null; private static VersionPage versionPage = null; private static GameListView gameListView = null; + private static AccountListView accountListView = null; private static AuthlibInjectorServersPage serversPage = null; private static LeftPaneController leftPaneController; private static Decorator decorator; @@ -75,6 +78,13 @@ public final class Controllers { return gameListView; } + // FXThread + public static AccountListView getAccountListView() { + if (accountListView == null) + accountListView = new AccountListView(new AccountListViewModel()); + return accountListView; + } + // FXThread public static VersionPage getVersionPage() { if (versionPage == null) @@ -192,5 +202,7 @@ public final class Controllers { decorator = null; stage = null; scene = null; + gameListView = null; + accountListView = null; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java index cec6a6dcb..562d41b37 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java @@ -19,28 +19,15 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXPopup; import javafx.application.Platform; -import javafx.beans.binding.Bindings; import javafx.beans.binding.When; -import javafx.beans.property.ListProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleListProperty; -import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Node; -import javafx.scene.control.Tooltip; -import javafx.scene.image.Image; -import javafx.scene.input.MouseButton; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; -import org.jackhuang.hmcl.auth.offline.OfflineAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.event.*; -import org.jackhuang.hmcl.game.AccountHelper; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.Modpack; @@ -49,12 +36,12 @@ import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; -import org.jackhuang.hmcl.ui.account.AccountPage; +import org.jackhuang.hmcl.ui.account.AccountAdvancedListItemViewModel; import org.jackhuang.hmcl.ui.account.AddAccountPane; import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.versions.GameAdvancedListItemViewModel; import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.MappedObservableList; import java.io.File; import java.util.LinkedList; @@ -62,37 +49,20 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import static javafx.collections.FXCollections.singletonObservableList; -import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class LeftPaneController { private final AdvancedListBox leftPane; private final VBox profilePane = new VBox(); private final VBox accountPane = new VBox(); - private final IconedItem launcherSettingsItem; - private final AdvancedListItem2 gameListItem; - - private ListProperty accountItems = new SimpleListProperty<>(); - private ObjectProperty selectedAccount = new SimpleObjectProperty() { - { - accountItems.addListener(onInvalidating(this::invalidated)); - } - - @Override - protected void invalidated() { - Account selected = get(); - accountItems.forEach(item -> item.setSelected( - getAccountFromItem(item) - .map(it -> it == selected) - .orElse(false))); - } - }; public LeftPaneController(AdvancedListBox leftPane) { this.leftPane = leftPane; - launcherSettingsItem = new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20)); + AdvancedListItem2 accountListItem = new AdvancedListItem2(new AccountAdvancedListItemViewModel()); + AdvancedListItem2 gameListItem = new AdvancedListItem2(new GameAdvancedListItemViewModel()); + + IconedItem launcherSettingsItem = new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20)); launcherSettingsItem.getLabel().textProperty().bind( new When(UpdateChecker.outdatedProperty()) @@ -107,17 +77,12 @@ public final class LeftPaneController { launcherSettingsItem.maxWidthProperty().bind(leftPane.widthProperty()); launcherSettingsItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage())); - gameListItem = new AdvancedListItem2(new GameAdvancedListItemViewModel()); - leftPane - .add(new ClassTitle(i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> { - button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10)); - button.getStyleClass().add("toggle-icon-tiny"); - button.setOnMouseClicked(e -> addNewAccount()); - }))) - .add(accountPane) - .startCategory(i18n("launcher").toUpperCase()) + .startCategory(i18n("account").toUpperCase()) + .add(accountListItem) + .startCategory(i18n("version").toUpperCase()) .add(gameListItem) + .startCategory(i18n("launcher").toUpperCase()) .add(launcherSettingsItem) .add(new ClassTitle(i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> { button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10)); @@ -127,22 +92,6 @@ public final class LeftPaneController { }))) .add(profilePane); - // ==== Accounts ==== - // Missing account item - AdvancedListItem missingAccountItem = new AdvancedListItem(i18n("account.missing"), i18n("message.unknown")); - RipplerContainer missingAccountRippler = new RipplerContainer(missingAccountItem); - missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount()); - missingAccountRippler.setOnMouseClicked(e -> addNewAccount()); - - accountItems.bind( - new When(Accounts.accountsProperty().emptyProperty()) - .then(singletonObservableList(missingAccountRippler)) - .otherwise(MappedObservableList.create(Accounts.getAccounts(), this::createAccountItem))); - Bindings.bindContent(accountPane.getChildren(), accountItems); - - selectedAccount.bindBidirectional(Accounts.selectedAccountProperty()); - // ==== - EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading); EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged); EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(this::onRefreshedVersions); @@ -154,55 +103,6 @@ public final class LeftPaneController { .map(Account.class::cast); } - private static String accountSubtitle(Account account) { - if (account instanceof OfflineAccount) - return i18n("account.methods.offline"); - else if (account instanceof YggdrasilAccount) - return account.getUsername(); - else - return ""; - } - - private RipplerContainer createAccountItem(Account account) { - AdvancedListItem item = new AdvancedListItem(account.getCharacter(), accountSubtitle(account)); - RipplerContainer rippler = new RipplerContainer(item); - item.setOnSettingsButtonClicked(e -> { - AccountPage accountPage = new AccountPage(account, item); - JFXPopup popup = new JFXPopup(accountPage); - accountPage.setOnDelete(popup::hide); - popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY()); - }); - rippler.setOnMouseClicked(e -> { - if (e.getButton() == MouseButton.PRIMARY) { - selectedAccount.set(account); - } - }); - rippler.getProperties().put("account", account); - rippler.maxWidthProperty().bind(leftPane.widthProperty()); - - if (account instanceof YggdrasilAccount) { - Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); - item.setImage(image, AccountHelper.getViewport(4)); - } else { - item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4)); - } - - if (account instanceof AuthlibInjectorAccount) { - FXUtils.installTooltip(rippler, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName())); - } - - // update skin - if (account instanceof YggdrasilAccount) { - AccountHelper.refreshSkinAsync((YggdrasilAccount) account) - .subscribe(Schedulers.javafx(), () -> { - Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); - item.setImage(image, AccountHelper.getViewport(4)); - }); - } - - return rippler; - } - public void checkAccount() { if (Accounts.getAccounts().isEmpty()) addNewAccount(); 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 96928efb6..8925dd194 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -18,8 +18,10 @@ package org.jackhuang.hmcl.ui; import javafx.beans.binding.ObjectBinding; +import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Node; +import javafx.scene.layout.StackPane; import javafx.scene.paint.Paint; import javafx.scene.shape.SVGPath; @@ -33,8 +35,11 @@ public final class SVG { path.setContent(d); path.fillProperty().bind(fill); - if (width < 0 || height < 0) - return path; + if (width < 0 || height < 0) { + StackPane pane = new StackPane(path); + pane.setAlignment(Pos.CENTER); + return pane; + } Group svg = new Group(path); double scale = Math.min(width / svg.getBoundsInParent().getWidth(), height / svg.getBoundsInParent().getHeight()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItemViewModel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItemViewModel.java new file mode 100644 index 000000000..8110af52b --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItemViewModel.java @@ -0,0 +1,100 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui.account; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Rectangle2D; +import javafx.scene.image.Image; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.offline.OfflineAccount; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; +import org.jackhuang.hmcl.game.AccountHelper; +import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.ui.AdvancedListItemViewModel; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class AccountAdvancedListItemViewModel extends AdvancedListItemViewModel { + private final ObjectProperty image = new SimpleObjectProperty<>(); + private final ObjectProperty viewport = new SimpleObjectProperty<>(AccountHelper.getViewport(4)); + private final StringProperty title = new SimpleStringProperty(); + private final StringProperty subtitle = new SimpleStringProperty(); + + public AccountAdvancedListItemViewModel() { + + FXUtils.onChangeAndOperate(Accounts.selectedAccountProperty(), account -> { + if (account == null) { + title.set(i18n("account.missing")); + subtitle.set(i18n("account.missing.add")); + image.set(new Image("/assets/img/craft_table.png")); + } else { + title.set(account.getCharacter()); + subtitle.set(accountSubtitle(account)); + + this.image.set(AccountHelper.getDefaultSkin(account.getUUID(), 4)); + + if (account instanceof YggdrasilAccount) { + AccountHelper.loadSkinAsync((YggdrasilAccount) account).subscribe(Schedulers.javafx(), () -> { + Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); + this.image.set(image); + }); + } + } + }); + } + + @Override + public void action() { + Controllers.navigate(Controllers.getAccountListView()); + } + + @Override + public ObjectProperty imageProperty() { + return image; + } + + @Override + public ObjectProperty viewportProperty() { + return viewport; + } + + @Override + public StringProperty titleProperty() { + return title; + } + + @Override + public StringProperty subtitleProperty() { + return subtitle; + } + + private static String accountSubtitle(Account account) { + if (account instanceof OfflineAccount) + return i18n("account.methods.offline"); + else if (account instanceof YggdrasilAccount) + return account.getUsername(); + else + return ""; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemView.java new file mode 100644 index 000000000..8728c20df --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemView.java @@ -0,0 +1,88 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui.account; + +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXRadioButton; +import com.jfoenix.effects.JFXDepthManager; +import javafx.geometry.Pos; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.TwoLineListItem; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class AccountListItemView extends BorderPane { + + public AccountListItemView(AccountListItemViewModel viewModel) { + JFXRadioButton chkSelected = new JFXRadioButton(); + BorderPane.setAlignment(chkSelected, Pos.CENTER); + chkSelected.setUserData(viewModel); + chkSelected.selectedProperty().bindBidirectional(viewModel.selectedProperty()); + chkSelected.setToggleGroup(viewModel.getToggleGroup()); + setLeft(chkSelected); + + HBox center = new HBox(); + center.setSpacing(8); + center.setAlignment(Pos.CENTER_LEFT); + + StackPane imageViewContainer = new StackPane(); + FXUtils.setLimitWidth(imageViewContainer, 32); + FXUtils.setLimitHeight(imageViewContainer, 32); + + ImageView imageView = new ImageView(); + FXUtils.limitSize(imageView, 32, 32); + imageView.imageProperty().bind(viewModel.imageProperty()); + imageView.viewportProperty().bind(viewModel.viewportProperty()); + imageViewContainer.getChildren().setAll(imageView); + + TwoLineListItem item = new TwoLineListItem(); + BorderPane.setAlignment(item, Pos.CENTER); + center.getChildren().setAll(imageView, item); + setCenter(center); + + HBox right = new HBox(); + right.setAlignment(Pos.CENTER_RIGHT); + JFXButton btnRefresh = new JFXButton(); + btnRefresh.setOnMouseClicked(e -> viewModel.refresh()); + btnRefresh.getStyleClass().add("toggle-icon4"); + btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1)); + JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRefresh, i18n("button.refresh"))); + right.getChildren().add(btnRefresh); + + JFXButton btnRemove = new JFXButton(); + btnRemove.setOnMouseClicked(e -> viewModel.remove()); + btnRemove.getStyleClass().add("toggle-icon4"); + BorderPane.setAlignment(btnRemove, Pos.CENTER); + btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1)); + JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRefresh, i18n("button.delete"))); + right.getChildren().add(btnRemove); + setRight(right); + + setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;"); + JFXDepthManager.setDepth(this, 1); + item.titleProperty().bind(viewModel.titleProperty()); + item.subtitleProperty().bind(viewModel.subtitleProperty()); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemViewModel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemViewModel.java new file mode 100644 index 000000000..c28e107df --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemViewModel.java @@ -0,0 +1,116 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui.account; + +import javafx.beans.property.*; +import javafx.geometry.Rectangle2D; +import javafx.scene.control.ToggleGroup; +import javafx.scene.image.Image; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; +import org.jackhuang.hmcl.auth.offline.OfflineAccount; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; +import org.jackhuang.hmcl.game.AccountHelper; +import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.task.Schedulers; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class AccountListItemViewModel { + private final Account account; + private final ToggleGroup toggleGroup; + private final StringProperty title = new SimpleStringProperty(); + private final StringProperty subtitle = new SimpleStringProperty(); + private final BooleanProperty selected = new SimpleBooleanProperty(); + private final ObjectProperty image = new SimpleObjectProperty<>(); + private final ObjectProperty viewport = new SimpleObjectProperty<>(); + + public AccountListItemViewModel(ToggleGroup toggleGroup, Account account) { + this.account = account; + this.toggleGroup = toggleGroup; + + StringBuilder subtitleString = new StringBuilder(Accounts.getAccountTypeName(account)); + if (account instanceof AuthlibInjectorAccount) { + AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer(); + subtitleString.append(", ").append(i18n("account.injector.server")).append(": ").append(server.getName()); + } + + if (account instanceof OfflineAccount) + title.set(account.getCharacter()); + else + title.set(account.getUsername() + " - " + account.getCharacter()); + subtitle.set(subtitleString.toString()); + selected.set(Accounts.selectedAccountProperty().get() == account); + + viewport.set(AccountHelper.getViewport(4)); + if (account instanceof YggdrasilAccount) { + Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); + this.image.set(image); + } else { + this.image.set(AccountHelper.getDefaultSkin(account.getUUID(), 4)); + } + } + + public ToggleGroup getToggleGroup() { + return toggleGroup; + } + + public Account getAccount() { + return account; + } + + public StringProperty titleProperty() { + return title; + } + + public StringProperty subtitleProperty() { + return subtitle; + } + + public BooleanProperty selectedProperty() { + return selected; + } + + public ObjectProperty imageProperty() { + return image; + } + + public ObjectProperty viewportProperty() { + return viewport; + } + + public void refresh() { + if (account instanceof YggdrasilAccount) { + // progressBar.setVisible(true); + AccountHelper.refreshSkinAsync((YggdrasilAccount) account) + .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { + // progressBar.setVisible(false); + + if (isDependentsSucceeded) { + Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); + this.image.set(image); + } + }).start(); + } + } + + public void remove() { + Accounts.getAccounts().remove(account); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListView.java new file mode 100644 index 000000000..8e0cc566d --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListView.java @@ -0,0 +1,81 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui.account; + +import com.jfoenix.controls.JFXButton; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.wizard.DecoratorPage; +import org.jackhuang.hmcl.util.MappedObservableList; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class AccountListView extends StackPane implements DecoratorPage { + private final StringProperty title = new SimpleStringProperty(i18n("account.manage")); + + public AccountListView(AccountListViewModel viewModel) { + ScrollPane scrollPane = new ScrollPane(); + { + scrollPane.setFitToWidth(true); + + VBox accountList = new VBox(); + accountList.maxWidthProperty().bind(scrollPane.widthProperty()); + accountList.setSpacing(10); + accountList.setStyle("-fx-padding: 10 10 10 10;"); + + Bindings.bindContent(accountList.getChildren(), + MappedObservableList.create(viewModel.itemsProperty(), AccountListItemView::new)); + + scrollPane.setContent(accountList); + } + + VBox vBox = new VBox(); + { + vBox.setAlignment(Pos.BOTTOM_RIGHT); + vBox.setPickOnBounds(false); + vBox.setPadding(new Insets(15)); + vBox.setSpacing(15); + + JFXButton btnAdd = new JFXButton(); + FXUtils.setLimitWidth(btnAdd, 40); + FXUtils.setLimitHeight(btnAdd, 40); + btnAdd.getStyleClass().setAll("jfx-button-raised-round"); + btnAdd.setButtonType(JFXButton.ButtonType.RAISED); + btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1)); + btnAdd.setOnMouseClicked(e -> viewModel.addNewAccount()); + + vBox.getChildren().setAll(btnAdd); + } + + getChildren().setAll(scrollPane, vBox); + } + + @Override + public StringProperty titleProperty() { + return title; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListViewModel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListViewModel.java new file mode 100644 index 000000000..d952e2772 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListViewModel.java @@ -0,0 +1,73 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui.account; + +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.scene.control.ToggleGroup; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.MappedObservableList; + +import java.util.Comparator; + +import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; + +public class AccountListViewModel { + private final ListProperty items = new SimpleListProperty<>(FXCollections.observableArrayList()); + private ObjectProperty selectedAccount = new SimpleObjectProperty() { + { + items.addListener(onInvalidating(this::invalidated)); + } + + @Override + protected void invalidated() { + Account selected = get(); + items.forEach(item -> item.selectedProperty().set(item.getAccount() == selected)); + } + }; + + private ToggleGroup toggleGroup; + + public AccountListViewModel() { + toggleGroup = new ToggleGroup(); + + items.bindContent(MappedObservableList.create( + Accounts.accountsProperty(), + account -> new AccountListItemViewModel(toggleGroup, account))); + + selectedAccount.bindBidirectional(Accounts.selectedAccountProperty()); + toggleGroup.selectedToggleProperty().addListener((o, a, toggle) -> { + if (toggle == null || toggle.getUserData() == null) return; + selectedAccount.set(((AccountListItemViewModel) toggle.getUserData()).getAccount()); + }); + } + + public void addNewAccount() { + Controllers.dialog(new AddAccountPane()); + } + + public ListProperty itemsProperty() { + return items; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountPage.java deleted file mode 100644 index f0eff72d8..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountPage.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * 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 {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.ui.account; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXProgressBar; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.image.Image; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.StackPane; - -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -import org.jackhuang.hmcl.auth.offline.OfflineAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.game.AccountHelper; -import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.AdvancedListItem; -import org.jackhuang.hmcl.ui.construct.ComponentList; -import org.jackhuang.hmcl.ui.wizard.DecoratorPage; - -import static org.jackhuang.hmcl.ui.FXUtils.installTooltip; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -import java.util.Optional; - -public class AccountPage extends StackPane implements DecoratorPage { - private final StringProperty title; - private final ObjectProperty onDelete = new SimpleObjectProperty<>(this, "onDelete"); - private final AdvancedListItem item; - private final Account account; - - @FXML - private Label lblType; - @FXML - private Label lblServer; - @FXML - private Label lblCharacter; - @FXML - private Label lblEmail; - @FXML - private BorderPane paneServer; - @FXML - private BorderPane paneEmail; - @FXML - private ComponentList componentList; - @FXML - private JFXButton btnDelete; - @FXML - private JFXButton btnRefresh; - @FXML - private JFXProgressBar progressBar; - - public AccountPage(Account account, AdvancedListItem item) { - this.account = account; - this.item = item; - - title = new SimpleStringProperty(this, "title", i18n("account") + " - " + account.getCharacter()); - - FXUtils.loadFXML(this, "/assets/fxml/account.fxml"); - - if (account instanceof AuthlibInjectorAccount) { - AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer(); - lblServer.setText(server.getName()); - installTooltip(lblServer, server.getUrl()); - } else { - componentList.removeChildren(paneServer); - - if (account instanceof OfflineAccount) { - componentList.removeChildren(paneEmail); - } - } - - btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15)); - btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15)); - - lblCharacter.setText(account.getCharacter()); - lblType.setText(Accounts.getAccountTypeName(account)); - lblEmail.setText(account.getUsername()); - - btnRefresh.setVisible(account instanceof YggdrasilAccount); - } - - @FXML - private void onDelete() { - Accounts.getAccounts().remove(account); - Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run); - } - - @FXML - private void onRefresh() { - if (account instanceof YggdrasilAccount) { - progressBar.setVisible(true); - AccountHelper.refreshSkinAsync((YggdrasilAccount) account) - .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { - progressBar.setVisible(false); - - if (isDependentsSucceeded) { - Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); - item.setImage(image, AccountHelper.getViewport(4)); - } - }).start(); - } - } - - public String getTitle() { - return title.get(); - } - - @Override - public StringProperty titleProperty() { - return title; - } - - public void setTitle(String title) { - this.title.set(title); - } - - public Runnable getOnDelete() { - return onDelete.get(); - } - - public ObjectProperty onDeleteProperty() { - return onDelete; - } - - public void setOnDelete(Runnable onDelete) { - this.onDelete.set(onDelete); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameAdvancedListItemViewModel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItemViewModel.java similarity index 94% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/GameAdvancedListItemViewModel.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItemViewModel.java index a484888fe..f4581c0f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameAdvancedListItemViewModel.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItemViewModel.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.ui; +package org.jackhuang.hmcl.ui.versions; import com.jfoenix.concurrency.JFXUtilities; import javafx.beans.InvalidationListener; @@ -23,14 +23,15 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.beans.value.ChangeListener; import javafx.scene.image.Image; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.ProfileChangedEvent; import org.jackhuang.hmcl.event.RefreshedVersionsEvent; -import org.jackhuang.hmcl.event.RefreshingVersionsEvent; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Settings; +import org.jackhuang.hmcl.ui.AdvancedListItemViewModel; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.WeakListenerHelper; import java.io.File; diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 04ad657a9..94fe55876 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -52,7 +52,7 @@ } .advanced-list-box-item { - -fx-padding: 10 16 10 16; + -fx-padding: 0 16 0 16; } .advanced-list-box-content { diff --git a/HMCL/src/main/resources/assets/img/craft_table.png b/HMCL/src/main/resources/assets/img/craft_table.png new file mode 100644 index 000000000..45740510a Binary files /dev/null and b/HMCL/src/main/resources/assets/img/craft_table.png differ diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 2302fd043..48b52fa7e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -48,11 +48,13 @@ account.injector.http=Warning: This server is using HTTP, which will cause your account.injector.server=Auth Server account.injector.server_url=Server URL account.injector.server_name=Server Name +account.manage=Account List account.methods=Login Type account.methods.authlib_injector=authlib-injector account.methods.offline=Offline account.methods.yggdrasil=Mojang -account.missing=None +account.missing=No Account +account.missing.add=Click the button on the right to add account.password=Password account.username=Name @@ -325,6 +327,7 @@ update.latest=This is latest Version. update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update. update.tooltip=Update +version=Games version.cannot_read=Unable to gather the game version. Cannot continue auto-installing. version.forbidden_name=Forbidden name, do not use this. version.game.old=Old diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index baffe02dc..632f4d877 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -48,11 +48,13 @@ account.injector.http=警告:此伺服器使用不安全的 HTTP 協議,您 account.injector.server=認證伺服器 account.injector.server_url=伺服器位址 account.injector.server_name=伺服器名稱 +account.manage=帳戶列表 account.methods=登入方式 account.methods.authlib_injector=authlib-injector 登入 account.methods.offline=離線模式 account.methods.yggdrasil=正版登入 -account.missing=沒有帳戶 +account.missing=沒有遊戲帳戶 +account.missing.add=點擊右邊按鈕添加 account.password=密碼 account.username=使用者名稱 @@ -325,6 +327,7 @@ update.latest=目前版本為最新版本 update.no_browser=無法打開瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址打開頁面 update.tooltip=更新 +version=遊戲 version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝 version.forbidden_name=此版本名稱不受支援,請換一個名字 version.game.old=老舊版本 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 8eee5b0d6..036e06861 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -48,11 +48,13 @@ account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您 account.injector.server=认证服务器 account.injector.server_url=服务器地址 account.injector.server_name=服务器名称 +account.manage=账户列表 account.methods=登录方式 account.methods.authlib_injector=authlib-injector 登录 account.methods.offline=离线模式 account.methods.yggdrasil=正版登录 -account.missing=没有账户 +account.missing=没有游戏账户 +account.missing.add=点击右边按钮添加 account.password=密码 account.username=用户名 @@ -325,6 +327,7 @@ update.latest=当前版本为最新版本 update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面 update.tooltip=更新 +version=游戏 version.cannot_read=读取游戏版本失败,无法进行自动安装 version.forbidden_name=此版本名称不受支持,请换一个名字 version.game.old=远古版