diff --git a/.gitignore b/.gitignore index 7cd7067a5..2e9fec682 100644 --- a/.gitignore +++ b/.gitignore @@ -25,8 +25,12 @@ NVIDIA /HMCLCore/out/ # eclipse +/bin/ +/HMCL/bin/ +/HMCLCore/bin/ .classpath .project +.settings # netbeans .nb-gradle \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java index 5b24da11e..5f868f7b1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java @@ -17,14 +17,16 @@ */ package org.jackhuang.hmcl.setting; +import com.jfoenix.concurrency.JFXUtilities; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyListProperty; -import javafx.beans.property.ReadOnlyListWrapper; -import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.*; +import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.event.EventBus; +import org.jackhuang.hmcl.event.RefreshedVersionsEvent; import java.io.File; import java.util.HashSet; @@ -41,6 +43,8 @@ public final class Profiles { public static final String DEFAULT_PROFILE = "Default"; public static final String HOME_PROFILE = "Home"; + private static InvalidationListener listener = o -> loadVersion(); + private Profiles() { } @@ -61,11 +65,17 @@ public final class Profiles { private static ObjectProperty selectedProfile = new SimpleObjectProperty() { { profiles.addListener(onInvalidating(this::invalidated)); + + this.addListener(this::change); } @Override protected void invalidated() { Profile profile = get(); + + if (get() != null) + get().removeListener(listener); + if (profiles.isEmpty()) { if (profile != null) { set(null); @@ -80,7 +90,16 @@ public final class Profiles { if (!initialized) return; + config().setSelectedProfile(profile == null ? "" : profile.getName()); + loadVersion(); + } + + private void change(ObservableValue observableValue, Profile oldProfile, Profile newProfile) { + if (oldProfile != null) + oldProfile.selectedVersionProperty().removeListener(listener); + if (newProfile != null) + newProfile.selectedVersionProperty().addListener(listener); } }; @@ -143,6 +162,13 @@ public final class Profiles { initialized = true; }); + + EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> { + JFXUtilities.runInFX(() -> { + if (selectedProfile.get() != null && selectedProfile.get().getRepository() == event.getSource()) + loadVersion(); + }); + }); } public static ObservableList getProfiles() { @@ -164,4 +190,21 @@ public final class Profiles { public static ObjectProperty selectedProfileProperty() { return selectedProfile; } + + private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper(); + + public static ReadOnlyStringProperty selectedVersionProperty() { + return selectedVersion.getReadOnlyProperty(); + } + + public static String getSelectedVersion() { + return selectedVersion.get(); + } + + private static void loadVersion() { + Profile profile = selectedProfile.get(); + if (profile == null || !profile.getRepository().isLoaded()) return; + JFXUtilities.runInFX(() -> + selectedVersion.set(profile.getSelectedVersion())); + } } 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 ddbdc4fc1..8e1000818 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java @@ -17,25 +17,52 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXPopup; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Rectangle; +import org.jackhuang.hmcl.event.EventBus; +import org.jackhuang.hmcl.event.RefreshedVersionsEvent; +import org.jackhuang.hmcl.event.RefreshingVersionsEvent; +import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.construct.IconedMenuItem; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.versions.Versions; +import org.jackhuang.hmcl.util.VersionNumber; + +import java.util.List; +import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class MainPage extends StackPane implements DecoratorPage { - private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page")); + private final VBox menu = new VBox(); + private final JFXPopup popup = new JFXPopup(menu); + @FXML private StackPane main; + @FXML + private JFXButton btnLaunch; + @FXML + private JFXButton btnMenu; + @FXML + private Label lblCurrentGame; + private Profile profile; { FXUtils.loadFXML(this, "/assets/fxml/main.fxml"); @@ -45,6 +72,53 @@ public final class MainPage extends StackPane implements DecoratorPage { else getChildren().setAll(main); }); + + btnLaunch.setClip(new Rectangle(-100, -100, 280, 200)); + btnMenu.setClip(new Rectangle(180, -100, 100, 200)); + menu.setMinWidth(200); + + StackPane graphic = new StackPane(); + Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10); + StackPane.setAlignment(svg, Pos.CENTER_RIGHT); + graphic.getChildren().setAll(svg); + graphic.setTranslateX(11); + btnMenu.setGraphic(graphic); + + Profiles.selectedVersionProperty().addListener((o, a, version) -> { + if (version != null) { + lblCurrentGame.setText(version); + } else { + lblCurrentGame.setText(i18n("version.empty")); + } + }); + + EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> { + if (event.getSource() == profile.getRepository()) + loadVersions((HMCLGameRepository) event.getSource()); + }); + Profiles.selectedProfileProperty().addListener((a, b, newValue) -> profile = newValue); + + profile = Profiles.getSelectedProfile(); + if (profile.getRepository().isLoaded()) + loadVersions(profile.getRepository()); + else + profile.getRepository().refreshVersionsAsync().start(); + } + + 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 -> new IconedMenuItem(null, version.getId(), () -> { + repository.getProfile().setSelectedVersion(version.getId()); + Versions.launch(repository.getProfile(), version.getId()); + popup.hide(); + })) + .collect(Collectors.toList()); + JFXUtilities.runInFX(() -> { + if (profile == repository.getProfile()) + menu.getChildren().setAll(children); + }); } @FXML @@ -53,6 +127,11 @@ public final class MainPage extends StackPane implements DecoratorPage { Versions.launch(profile, profile.getSelectedVersion()); } + @FXML + private void onMenu() { + popup.show(btnMenu, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -btnMenu.getHeight()); + } + public String getTitle() { return title.get(); } 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 44f072bd8..c6530c7f5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -150,4 +150,8 @@ public final class SVG { public static Node openInNew(ObjectBinding fill, double width, double height) { return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height); } + + public static Node triangle(ObjectBinding fill, double width, double height) { + return createSVGPath("M1,21H23L12,2", fill, width, height); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java index b915aed44..8b7fdb47e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java @@ -39,12 +39,17 @@ public class IconedItem extends RipplerContainer { private static HBox createHBox(Node icon) { HBox hBox = new HBox(); + + if (icon != null) { + icon.setMouseTransparent(true); + hBox.getChildren().add(icon); + } + hBox.getStyleClass().setAll("iconed-item-container"); - icon.setMouseTransparent(true); Label textLabel = new Label(); textLabel.setId("label"); textLabel.setMouseTransparent(true); - hBox.getChildren().addAll(icon, textLabel); + hBox.getChildren().addAll(textLabel); return hBox; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java index f3b859c37..61793b23f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java @@ -32,40 +32,10 @@ import java.io.File; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameAdvancedListItem extends AdvancedListItem { - private final WeakListenerHolder listenerHolder = new WeakListenerHolder(); - - private Profile profile; - private InvalidationListener listener = o -> loadVersion(); public GameAdvancedListItem() { - Profiles.selectedProfileProperty().addListener(listenerHolder.weak((a, b, newValue) -> { - JFXUtilities.runInFX(() -> loadProfile(newValue)); - })); - listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> { - JFXUtilities.runInFX(() -> { - if (profile != null && profile.getRepository() == event.getSource()) - loadVersion(); - }); - })); - loadProfile(Profiles.getSelectedProfile()); - } - - private void loadProfile(Profile newProfile) { - if (profile != null) - profile.selectedVersionProperty().removeListener(listener); - profile = newProfile; - if (profile != null) - profile.selectedVersionProperty().addListener(listener); - loadVersion(); - } - - private void loadVersion() { - Profile profile = this.profile; - if (profile == null || !profile.getRepository().isLoaded()) return; - String version = profile.getSelectedVersion(); - File iconFile = profile.getRepository().getVersionIcon(version); - - JFXUtilities.runInFX(() -> { + Profiles.selectedVersionProperty().addListener((o, a, version) -> { + File iconFile = Profiles.getSelectedProfile().getRepository().getVersionIcon(version); if (iconFile.exists()) imageProperty().set(new Image("file:" + iconFile.getAbsolutePath())); else diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 72f7e5fcc..83fad8c6d 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -510,6 +510,15 @@ -fx-font-size:14px; } +.jfx-button-raised .jfx-rippler { + -jfx-rippler-fill: white; +} + +.jfx-button-raised .label { + -fx-text-fill: white; + -fx-font-size: 14px; +} + .jfx-button-raised-round { -fx-background-color: -fx-base-color; -fx-background-radius: 50px; diff --git a/HMCL/src/main/resources/assets/fxml/main.fxml b/HMCL/src/main/resources/assets/fxml/main.fxml index d5b4908a9..aae97d066 100644 --- a/HMCL/src/main/resources/assets/fxml/main.fxml +++ b/HMCL/src/main/resources/assets/fxml/main.fxml @@ -2,11 +2,23 @@ + + - + + + + + + +