From f61181438fb5b2555b79a8af859a7796185c71e6 Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Mon, 3 Sep 2018 00:10:21 +0800 Subject: [PATCH] allow displaying game list in main page --- .../org/jackhuang/hmcl/setting/Config.java | 15 ++ .../hmcl/ui/GameVersionListPage.java | 241 ++++++++++++++++++ .../java/org/jackhuang/hmcl/ui/MainPage.java | 12 + .../org/jackhuang/hmcl/ui/SettingsPage.java | 2 + .../org/jackhuang/hmcl/ui/SettingsView.java | 16 ++ HMCL/src/main/resources/assets/fxml/main.fxml | 12 +- .../assets/fxml/version/version-settings.fxml | 2 +- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 10 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/GameVersionListPage.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 355cd3b8f..69d12c3f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -153,6 +153,9 @@ public final class Config implements Cloneable, Observable { @SerializedName("updateChannel") private ObjectProperty updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE); + @SerializedName("enableMainPageGameList") + private BooleanProperty enableMainPageGameList = new SimpleBooleanProperty(false); + @SerializedName("_version") private IntegerProperty configVersion = new SimpleIntegerProperty(0); @@ -450,4 +453,16 @@ public final class Config implements Cloneable, Observable { public void setUpdateChannel(UpdateChannel updateChannel) { this.updateChannel.set(updateChannel); } + + public boolean isEnableMainPageGameList() { + return enableMainPageGameList.get(); + } + + public BooleanProperty enableMainPageGameListProperty() { + return enableMainPageGameList; + } + + public void setEnableMainPageGameList(boolean enableMainPageGameList) { + this.enableMainPageGameList.set(enableMainPageGameList); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameVersionListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameVersionListPage.java new file mode 100644 index 000000000..0b2df4cc1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameVersionListPage.java @@ -0,0 +1,241 @@ +/* + * 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; + +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.*; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.image.Image; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.event.EventBus; +import org.jackhuang.hmcl.event.RefreshedVersionsEvent; +import org.jackhuang.hmcl.event.RefreshingVersionsEvent; +import org.jackhuang.hmcl.game.GameVersion; +import org.jackhuang.hmcl.game.HMCLGameRepository; +import org.jackhuang.hmcl.game.ModpackHelper; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.mod.MismatchedModpackTypeException; +import org.jackhuang.hmcl.mod.UnsupportedModpackException; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.setting.Profiles; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.MessageBox; +import org.jackhuang.hmcl.ui.versions.Versions; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.VersionNumber; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.util.StringUtils.removePrefix; +import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class GameVersionListPage extends StackPane { + private final JFXSpinner spinner; + private final StackPane contentPane; + private final JFXMasonryPane masonryPane; + + private Profile profile; + + public GameVersionListPage() { + spinner = new JFXSpinner(); + spinner.getStyleClass().setAll("first-spinner"); + + contentPane = new StackPane(); + + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setFitToWidth(true); + + masonryPane = new JFXMasonryPane(); + masonryPane.setHSpacing(3); + masonryPane.setVSpacing(3); + masonryPane.setCellWidth(182); + masonryPane.setCellHeight(153); + + scrollPane.setContent(masonryPane); + + VBox vBox = new VBox(); + vBox.setPadding(new Insets(15)); + vBox.setPickOnBounds(false); + vBox.setAlignment(Pos.BOTTOM_RIGHT); + vBox.setSpacing(15); + + JFXButton btnRefresh = new JFXButton(); + btnRefresh.setPrefWidth(40); + btnRefresh.setPrefHeight(40); + btnRefresh.setButtonType(JFXButton.ButtonType.RAISED); + btnRefresh.getStyleClass().setAll("jfx-button-raised-round"); + btnRefresh.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1)); + btnRefresh.setOnMouseClicked(e -> profile.getRepository().refreshVersionsAsync().start()); + FXUtils.installTooltip(btnRefresh, i18n("button.refresh")); + + vBox.getChildren().setAll(btnRefresh); + + contentPane.getChildren().setAll(scrollPane, vBox); + + getChildren().setAll(spinner); + + Profiles.selectedProfileProperty().addListener((o, a, b) -> this.profile = b); + profile = Profiles.getSelectedProfile(); + + EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> { + if (event.getSource() == profile.getRepository()) + loadVersions((HMCLGameRepository) event.getSource()); + }); + EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> { + if (event.getSource() == profile.getRepository()) + // This will occupy 0.5s. Too slow! + JFXUtilities.runInFX(this::loadingVersions); + }); + if (profile.getRepository().isLoaded()) + loadVersions(profile.getRepository()); + else + profile.getRepository().refreshVersionsAsync().start(); + } + + private String modifyVersion(String gameVersion, String version) { + return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"); + } + + private Node buildNode(HMCLGameRepository repository, Version version, Callable gameCallable) { + Profile profile = repository.getProfile(); + String id = version.getId(); + VersionItem item = new VersionItem(); + item.setUpdate(repository.isModpack(id)); + Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> { + String game = vars.get("game"); + item.setGameVersion(game); + + StringBuilder libraries = new StringBuilder(); + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); + analyzer.getForge().ifPresent(library -> libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n")); + analyzer.getLiteLoader().ifPresent(library -> libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n")); + analyzer.getOptiFine().ifPresent(library -> libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n")); + + item.setLibraries(libraries.toString()); + }); + item.setVersionName(id); + item.setOnLaunchButtonClicked(e -> Versions.launch(profile, id)); + item.setOnScriptButtonClicked(e -> Versions.generateLaunchScript(profile, id)); + item.setOnSettingsButtonClicked(e -> { + Controllers.getVersionPage().load(id, profile); + Controllers.navigate(Controllers.getVersionPage()); + }); + item.setOnUpdateButtonClicked(event -> { + FileChooser chooser = new FileChooser(); + chooser.setTitle(i18n("modpack.choose")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip")); + File selectedFile = chooser.showOpenDialog(Controllers.getStage()); + if (selectedFile != null) { + AtomicReference region = new AtomicReference<>(); + try { + TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id))) + .then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor(); + region.set(Controllers.taskDialog(executor, i18n("modpack.update"), "")); + executor.start(); + } catch (UnsupportedModpackException e) { + region.get().fireEvent(new DialogCloseEvent()); + Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE); + } catch (MismatchedModpackTypeException e) { + region.get().fireEvent(new DialogCloseEvent()); + Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE); + } catch (IOException e) { + region.get().fireEvent(new DialogCloseEvent()); + Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE); + } + } + }); + item.setOnMouseClicked(event -> { + if (event.getButton() == MouseButton.SECONDARY) { + JFXListView versionList = new JFXListView<>(); + JFXPopup versionPopup = new JFXPopup(versionList); + versionList.getStyleClass().add("option-list-view"); + FXUtils.setLimitWidth(versionList, 150); + versionList.getItems().setAll(Lang.immutableListOf( + i18n("version.manage.rename"), + i18n("version.manage.remove"), + i18n("modpack.export"), + i18n("folder.game") + )); + versionList.setOnMouseClicked(e -> { + versionPopup.hide(); + switch (versionList.getSelectionModel().getSelectedIndex()) { + case 0: + Versions.renameVersion(profile, id); + break; + case 1: + Versions.deleteVersion(profile, id); + break; + case 2: + Versions.exportVersion(profile, id); + break; + case 3: + FXUtils.openFolder(repository.getRunDirectory(id)); + break; + default: + break; + } + }); + versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY()); + } else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) { + Versions.launch(profile, id); + } + }); + File iconFile = repository.getVersionIcon(id); + if (iconFile.exists()) + item.setImage(new Image("file:" + iconFile.getAbsolutePath())); + return item; + } + + private void loadingVersions() { + getChildren().setAll(spinner); + masonryPane.getChildren().clear(); + } + + private void loadVersions(HMCLGameRepository repository) { + List children = repository.getVersions().parallelStream() + .filter(version -> !version.isHidden()) + .sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId()))) + .map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown"))) + .collect(Collectors.toList()); + JFXUtilities.runInFX(() -> { + if (profile == repository.getProfile()) { + masonryPane.getChildren().setAll(children); + getChildren().setAll(contentPane); + } + }); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java index af12d5e23..263ca1af5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java @@ -17,10 +17,12 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.controls.JFXButton; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.layout.StackPane; +import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; @@ -32,8 +34,18 @@ public final class MainPage extends StackPane implements DecoratorPage { private final StringProperty title = new SimpleStringProperty(this, "title", i18n("main_page")); + @FXML + private StackPane main; + { FXUtils.loadFXML(this, "/assets/fxml/main.fxml"); + + FXUtils.onChangeAndOperate(ConfigHolder.config().enableMainPageGameListProperty(), newValue -> { + if (newValue) + getChildren().setAll(new GameVersionListPage()); + else + getChildren().setAll(main); + }); } @FXML diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index b3588e87c..803984150 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -63,6 +63,8 @@ public final class SettingsPage extends SettingsView implements DecoratorPage { public SettingsPage() { FXUtils.smoothScrolling(scroll); + chkEnableGameList.selectedProperty().bindBidirectional(config().enableMainPageGameListProperty()); + cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.instance().getDownloadProvider())); cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.instance().setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue()))); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java index 8f9cdc677..aa4d913ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java @@ -58,6 +58,7 @@ public abstract class SettingsView extends StackPane { protected final JFXCheckBox chkProxyAuthentication; protected final GridPane authPane; protected final Pane proxyPane; + protected final JFXToggleButton chkEnableGameList; public SettingsView() { scroll = new ScrollPane(); @@ -134,6 +135,21 @@ public abstract class SettingsView extends StackPane { settingsPane.addChildren(backgroundItem); } + { + BorderPane borderPane = new BorderPane(); + + Label left = new Label(I18n.i18n("settings.launcher.enable_game_list")); + BorderPane.setAlignment(left, Pos.CENTER_LEFT); + borderPane.setLeft(left); + + chkEnableGameList = new JFXToggleButton(); + chkEnableGameList.setSize(8); + FXUtils.setLimitHeight(chkEnableGameList, 20); + borderPane.setRight(chkEnableGameList); + + settingsPane.addChildren(borderPane); + } + { BorderPane downloadSourcePane = new BorderPane(); { diff --git a/HMCL/src/main/resources/assets/fxml/main.fxml b/HMCL/src/main/resources/assets/fxml/main.fxml index 11cf40672..d5b4908a9 100644 --- a/HMCL/src/main/resources/assets/fxml/main.fxml +++ b/HMCL/src/main/resources/assets/fxml/main.fxml @@ -2,9 +2,11 @@ - - + + + + diff --git a/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml b/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml index d5524de68..6591599fd 100644 --- a/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml +++ b/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml @@ -109,7 +109,7 @@