diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index bc21719a7..fad203939 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -38,12 +38,14 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.*; import java.util.logging.Level; +import java.util.stream.Stream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.newImage; @@ -89,6 +91,13 @@ public class HMCLGameRepository extends DefaultGameRepository { } } + public Stream getDisplayVersions() { + return getVersions().stream() + .filter(v -> !v.isHidden()) + .sorted(Comparator.comparing((Version v) -> v.getReleaseTime() == null ? new Date(0L) : v.getReleaseTime()) + .thenComparing(v -> VersionNumber.asVersion(v.getId()))); + } + @Override protected void refreshVersionsImpl() { localVersionSettings.clear(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 2aa040986..fc7cf0254 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -418,7 +418,7 @@ public final class LauncherHelper { if (java8.isPresent()) { java8required = true; setting.setJavaVersion(java8.get()); - Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFORMATION, onAccept); + Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFO, onAccept); javaChanged = true; } else { Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, null); 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 ea51893bd..9c0b682bf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -235,7 +235,7 @@ public final class Controllers { } public static void dialog(String text, String title) { - dialog(text, title, MessageType.INFORMATION); + dialog(text, title, MessageType.INFO); } public static void dialog(String text, String title, MessageType type) { 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 b4a9524a5..6d4e044b0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -216,6 +216,10 @@ public final class SVG { return createSVGPath("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z", fill, width, height); } + public static Node alertOutline(ObjectBinding fill, double width, double height) { + return createSVGPath("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16", fill, width, height); + } + public static Node plus(ObjectBinding fill, double width, double height) { return createSVGPath("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z", fill, width, height); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index adbf31796..16c57667c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -261,7 +261,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { } if (factory == Accounts.FACTORY_MICROSOFT) { VBox vbox = new VBox(8); - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); hintPane.textProperty().bind(BindingMapping.of(logging).map(logging -> logging ? i18n("account.methods.microsoft.manual") diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java index e8a704b94..b03b3f656 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java @@ -21,6 +21,7 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -36,27 +37,44 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class HintPane extends VBox { private final Text label = new Text(); private final StringProperty text = new SimpleStringProperty(this, "text"); + private final TextFlow flow = new TextFlow(); public HintPane() { - this(MessageDialogPane.MessageType.INFORMATION); + this(MessageDialogPane.MessageType.INFO); } public HintPane(MessageDialogPane.MessageType type) { setFillWidth(true); - getStyleClass().add("hint"); + getStyleClass().addAll("hint", type.name().toLowerCase()); HBox hbox = new HBox(); hbox.setAlignment(Pos.CENTER_LEFT); - hbox.getChildren().setAll( - SVG.informationOutline(Theme.blackFillBinding(), 16, 16), - new Label(i18n("message.info")) - ); - TextFlow flow = new TextFlow(); + + switch (type) { + case INFO: + hbox.getChildren().add(SVG.informationOutline(Theme.blackFillBinding(), 16, 16)); + break; + case ERROR: + hbox.getChildren().add(SVG.closeCircleOutline(Theme.blackFillBinding(), 16, 16)); + break; + case SUCCESS: + hbox.getChildren().add(SVG.checkCircleOutline(Theme.blackFillBinding(), 16, 16)); + break; + case WARNING: + hbox.getChildren().add(SVG.alertOutline(Theme.blackFillBinding(), 16, 16)); + break; + case QUESTION: + hbox.getChildren().add(SVG.helpCircleOutline(Theme.blackFillBinding(), 16, 16)); + break; + default: + throw new IllegalArgumentException("Unrecognized message box message type " + type); + } + + + hbox.getChildren().add(new Text(type.getDisplayName())); flow.getChildren().setAll(label); getChildren().setAll(hbox, flow); label.textProperty().bind(text); VBox.setMargin(flow, new Insets(2, 2, 0, 2)); - - label.fillProperty().bind(BindingMapping.of(disabledProperty()).map(disabled -> disabled ? new Color(0, 0, 0, 0.5) : Color.BLACK)); } public String getText() { @@ -70,4 +88,8 @@ public class HintPane extends VBox { public void setText(String text) { this.text.set(text); } + + public void setChildren(Node... children) { + flow.getChildren().setAll(children); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 6dbe3787e..72be8aae6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -31,6 +31,8 @@ import org.jackhuang.hmcl.ui.SVG; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Locale; + import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -38,10 +40,14 @@ public final class MessageDialogPane extends StackPane { public enum MessageType { ERROR, - INFORMATION, + INFO, WARNING, QUESTION, - FINE, + SUCCESS; + + public String getDisplayName() { + return i18n("message." + name().toLowerCase(Locale.ROOT)); + } } @FXML @@ -64,13 +70,13 @@ public final class MessageDialogPane extends StackPane { this.title.setText(title); switch (type) { - case INFORMATION: + case INFO: graphic.setGraphic(SVG.infoCircle(Theme.blackFillBinding(), 40, 40)); break; case ERROR: graphic.setGraphic(SVG.closeCircle(Theme.blackFillBinding(), 40, 40)); break; - case FINE: + case SUCCESS: graphic.setGraphic(SVG.checkCircle(Theme.blackFillBinding(), 40, 40)); break; case WARNING: diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java index 503ffdba9..db2172875 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java @@ -120,7 +120,7 @@ public class ModpackInstallWizardProvider implements WizardProvider { if (exception.getCause() instanceof FileNotFoundException) { Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next); } else { - Controllers.dialog(i18n("install.success"), i18n("install.success"), MessageType.INFORMATION, next); + Controllers.dialog(i18n("install.success"), i18n("install.success"), MessageType.SUCCESS, next); } } else { UpdateInstallerWizardProvider.alertFailureMessage(exception, next); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java index 56b9bbdc2..07f2847ea 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java @@ -187,7 +187,7 @@ public final class ModpackInfoPage extends Control implements WizardPage { }); borderPane.setTop(hyperlink); } else { - HintPane pane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO); pane.setText(i18n("modpack.wizard.step.initialization.warning")); borderPane.setTop(pane); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 1922252ff..1ec72753c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -154,7 +154,7 @@ public class DownloadSettingsPage extends StackPane { } { - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); VBox.setMargin(hintPane, new Insets(0, 0, 0, 30)); hintPane.disableProperty().bind(config().autoDownloadThreadsProperty()); hintPane.setText(i18n("settings.launcher.download.threads.hint")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java index 88e62dd56..9dd8ef914 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java @@ -326,7 +326,7 @@ public class FeedbackPage extends VBox { searchHintPane.setText(i18n("feedback.add.hint.search_before_add")); body.addRow(0, searchHintPane); - HintPane titleHintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane titleHintPane = new HintPane(MessageDialogPane.MessageType.INFO); GridPane.setColumnSpan(titleHintPane, 2); titleHintPane.setText(i18n("feedback.add.hint.title")); body.addRow(1, titleHintPane); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index 9080754fd..dad01f65e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -48,7 +48,7 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage { private final TransitionPane transitionPane = new TransitionPane(); public LauncherSettingsPage() { - gameTab.setNodeSupplier(VersionSettingsPage::new); + gameTab.setNodeSupplier(() -> new VersionSettingsPage(true)); settingsTab.setNodeSupplier(SettingsPage::new); personalizationTab.setNodeSupplier(PersonalizationPage::new); downloadTab.setNodeSupplier(DownloadSettingsPage::new); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java index a60f78cde..c5a85e39c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java @@ -120,7 +120,7 @@ public class MultiplayerPageSkin extends SkinBase { scrollPane.setFitToHeight(true); root.setCenter(scrollPane); - HintPane hint = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane hint = new HintPane(MessageDialogPane.MessageType.INFO); hint.setText(i18n("multiplayer.hint")); ComponentList roomPane = new ComponentList(); @@ -130,7 +130,7 @@ public class MultiplayerPageSkin extends SkinBase { VBox disconnectedPane = new VBox(8); { - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); hintPane.setText(i18n("multiplayer.state.disconnected.hint")); Label label = new Label(i18n("multiplayer.state.disconnected")); @@ -190,7 +190,7 @@ public class MultiplayerPageSkin extends SkinBase { pane.setHgap(16); pane.setVgap(8); - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); hintPane.setText(i18n("multiplayer.nat.hint")); GridPane.setColumnSpan(hintPane, 2); pane.addRow(0, hintPane); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 9ca6e9a44..72ee5d1c8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -124,10 +124,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP } if (versionSelection) { - versions.setAll(profile.getRepository().getVersions().stream() - .filter(v -> !v.isHidden()) - .sorted(Comparator.comparing((Version v) -> v.getReleaseTime() == null ? new Date(0L) : v.getReleaseTime()) - .thenComparing(v -> VersionNumber.asVersion(v.getId()))) + versions.setAll(profile.getRepository().getDisplayVersions() .map(Version::getId) .collect(Collectors.toList())); selectedVersion.set(profile.getSelectedVersion()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java index fc0e234ef..86a3fbc14 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java @@ -190,15 +190,12 @@ public class GameListPage extends ListPageBase implements Decorato toggleGroup = new ToggleGroup(); WeakListenerHolder listenerHolder = new WeakListenerHolder(); toggleGroup.getProperties().put("ReferenceHolder", listenerHolder); - List children = repository.getVersions().parallelStream() - .filter(version -> !version.isHidden()) - .sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime()) - .thenComparing(a -> VersionNumber.asVersion(a.getId()))) - .map(version -> new GameListItem(toggleGroup, profile, version.getId())) - .collect(Collectors.toList()); runInFX(() -> { if (profile == Profiles.getSelectedProfile()) { setLoading(false); + List children = repository.getDisplayVersions() + .map(version -> new GameListItem(toggleGroup, profile, version.getId())) + .collect(Collectors.toList()); itemsProperty().setAll(children); children.forEach(GameListItem::checkSelection); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index ce33d82df..6eac6eb5a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -57,7 +57,7 @@ public class VersionPage extends Control implements DecoratorPage { private String preferredVersionName = null; { - versionSettingsTab.setNodeSupplier(VersionSettingsPage::new); + versionSettingsTab.setNodeSupplier(() -> new VersionSettingsPage(false)); modListTab.setNodeSupplier(ModListPage::new); installerListTab.setNodeSupplier(InstallerListPage::new); worldListTab.setNodeSupplier(WorldListPage::new); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index a3da13348..a3dc896b1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -29,6 +29,7 @@ import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.image.Image; import javafx.scene.layout.*; +import javafx.scene.text.Text; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.GameDirectoryType; import org.jackhuang.hmcl.game.HMCLGameRepository; @@ -42,11 +43,13 @@ import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -69,8 +72,11 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class VersionSettingsPage extends StackPane implements DecoratorPage, VersionPage.VersionLoadable { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(new State("", null, false, false, false)); + private final boolean globalSetting; + private VersionSetting lastVersionSetting = null; private Profile profile; + private WeakListenerHolder listenerHolder; private String versionId; private boolean javaItemsLoaded; @@ -85,7 +91,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final JFXTextField txtPostExitCommand; private final JFXTextField txtServerIP; private final ComponentList componentList; - private final ComponentList iconPickerItemWrapper; private final JFXComboBox cboLauncherVisibility; private final JFXCheckBox chkAutoAllocate; private final JFXCheckBox chkFullscreen; @@ -102,19 +107,23 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final MultiFileItem.FileOption nativesDirCustomOption; private final JFXComboBox cboProcessPriority; private final OptionToggleButton showLogsPane; - private final ImagePickerItem iconPickerItem; - private final JFXCheckBox chkEnableSpecificSettings; - private final BorderPane settingsTypePane; + private ImagePickerItem iconPickerItem; private final InvalidationListener specificSettingsListener; private final InvalidationListener javaListener = any -> initJavaSubtitle(); private boolean uiVisible = false; - private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(); - private final ObjectProperty memoryStatusProperty = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID); + private final StringProperty selectedVersion = new SimpleStringProperty(); + private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false); + private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(true); + private final IntegerProperty maxMemory = new SimpleIntegerProperty(); + private final ObjectProperty memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID); + private final BooleanProperty modpack = new SimpleBooleanProperty(); + + public VersionSettingsPage(boolean globalSetting) { + this.globalSetting = globalSetting; - public VersionSettingsPage() { ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); scrollPane.setFitToWidth(true); @@ -122,34 +131,58 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag getChildren().setAll(scrollPane); rootPane = new VBox(); + rootPane.setFillWidth(true); scrollPane.setContent(rootPane); FXUtils.smoothScrolling(scrollPane); rootPane.getStyleClass().add("card-list"); - { - iconPickerItemWrapper = new ComponentList(); + if (globalSetting) { + HintPane skinHint = new HintPane(MessageDialogPane.MessageType.INFO); + skinHint.setText(i18n("settings.skin")); + rootPane.getChildren().add(skinHint); + + HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING); + Text text = new Text(); + text.textProperty().bind(BindingMapping.of(selectedVersion) + .map(selectedVersion -> i18n("settings.type.special.edit.hint", selectedVersion))); + + JFXHyperlink specificSettingsLink = new JFXHyperlink(); + specificSettingsLink.setText(i18n("settings.type.special.edit")); + specificSettingsLink.setOnMouseClicked(e -> editSpecificSettings()); + + specificSettingsHint.setChildren(text, specificSettingsLink); + + rootPane.getChildren().addAll(specificSettingsHint); + } + + if (!globalSetting) { + ComponentList iconPickerItemWrapper = new ComponentList(); + rootPane.getChildren().add(iconPickerItemWrapper); + iconPickerItem = new ImagePickerItem(); iconPickerItem.setImage(new Image("/assets/img/icon.png")); iconPickerItem.setTitle(i18n("settings.icon")); iconPickerItem.setOnSelectButtonClicked(e -> onExploreIcon()); iconPickerItem.setOnDeleteButtonClicked(e -> onDeleteIcon()); iconPickerItemWrapper.getContent().setAll(iconPickerItem); - } - { - settingsTypePane = new BorderPane(); - chkEnableSpecificSettings = new JFXCheckBox(); - settingsTypePane.setLeft(chkEnableSpecificSettings); - chkEnableSpecificSettings.setText(i18n("settings.type.special.enable")); - BorderPane.setAlignment(chkEnableSpecificSettings, Pos.CENTER_RIGHT); + BorderPane settingsTypePane = new BorderPane(); + settingsTypePane.disableProperty().bind(modpack); + rootPane.getChildren().add(settingsTypePane); + + JFXCheckBox enableSpecificCheckBox = new JFXCheckBox(); + enableSpecificCheckBox.selectedProperty().bindBidirectional(enableSpecificSettings); + settingsTypePane.setLeft(enableSpecificCheckBox); + enableSpecificCheckBox.setText(i18n("settings.type.special.enable")); + BorderPane.setAlignment(enableSpecificCheckBox, Pos.CENTER_RIGHT); JFXButton editGlobalSettingsButton = new JFXButton(); settingsTypePane.setRight(editGlobalSettingsButton); editGlobalSettingsButton.setText(i18n("settings.type.global.edit")); editGlobalSettingsButton.getStyleClass().add("jfx-button-raised"); editGlobalSettingsButton.setButtonType(JFXButton.ButtonType.RAISED); - editGlobalSettingsButton.disableProperty().bind(chkEnableSpecificSettings.selectedProperty()); + editGlobalSettingsButton.disableProperty().bind(enableSpecificCheckBox.selectedProperty()); BorderPane.setAlignment(editGlobalSettingsButton, Pos.CENTER_RIGHT); editGlobalSettingsButton.setOnMouseClicked(e -> editGlobalSettings()); } @@ -167,6 +200,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag gameDirItem = new MultiFileItem<>(); gameDirItem.setTitle(i18n("settings.game.working_directory")); gameDirItem.setHasSubtitle(true); + gameDirItem.disableProperty().bind(modpack); gameDirCustomOption = new MultiFileItem.FileOption<>(i18n("settings.custom"), GameDirectoryType.CUSTOM) .setChooserTitle(i18n("settings.game.working_directory.choose")) .setDirectory(true); @@ -203,20 +237,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag HBox.setHgrow(slider, Priority.ALWAYS); slider.setValueFactory(self -> Bindings.createStringBinding(() -> (int) (self.getValue() * 100) + "%", self.valueProperty())); AtomicBoolean changedByTextField = new AtomicBoolean(false); - FXUtils.onChangeAndOperate(maxMemoryProperty, maxMemory -> { + FXUtils.onChangeAndOperate(maxMemory, maxMemory -> { changedByTextField.set(true); slider.setValue(maxMemory.intValue() * 1.0 / OperatingSystem.TOTAL_MEMORY); changedByTextField.set(false); }); slider.valueProperty().addListener((value, oldVal, newVal) -> { if (changedByTextField.get()) return; - maxMemoryProperty.set((int) (value.getValue().doubleValue() * OperatingSystem.TOTAL_MEMORY)); + maxMemory.set((int) (value.getValue().doubleValue() * OperatingSystem.TOTAL_MEMORY)); }); JFXTextField txtMaxMemory = new JFXTextField(); FXUtils.setLimitWidth(txtMaxMemory, 60); FXUtils.setValidateWhileTextChanged(txtMaxMemory, true); - txtMaxMemory.textProperty().bindBidirectional(maxMemoryProperty, SafeStringConverter.fromInteger()); + txtMaxMemory.textProperty().bindBidirectional(maxMemory, SafeStringConverter.fromInteger()); txtMaxMemory.setValidators(new NumberValidator(i18n("input.number"), false)); lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label("MB")); @@ -233,16 +267,16 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag usedMemory.getStyleClass().add("memory-used"); usedMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> progressBarPane.getWidth() * - (memoryStatusProperty.get().getUsed() * 1.0 / memoryStatusProperty.get().getTotal()), progressBarPane.widthProperty(), - memoryStatusProperty)); + (memoryStatus.get().getUsed() * 1.0 / memoryStatus.get().getTotal()), progressBarPane.widthProperty(), + memoryStatus)); StackPane allocateMemory = new StackPane(); allocateMemory.getStyleClass().add("memory-allocate"); allocateMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> progressBarPane.getWidth() * Math.min(1.0, - (double) (HMCLGameRepository.getAllocatedMemory(maxMemoryProperty.get() * 1024L * 1024L, memoryStatusProperty.get().getAvailable(), chkAutoAllocate.isSelected()) - + memoryStatusProperty.get().getUsed()) / memoryStatusProperty.get().getTotal()), progressBarPane.widthProperty(), - maxMemoryProperty, memoryStatusProperty, chkAutoAllocate.selectedProperty())); + (double) (HMCLGameRepository.getAllocatedMemory(maxMemory.get() * 1024L * 1024L, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected()) + + memoryStatus.get().getUsed()) / memoryStatus.get().getTotal()), progressBarPane.widthProperty(), + maxMemory, memoryStatus, chkAutoAllocate.selectedProperty())); progressBarPane.getChildren().setAll(allocateMemory, usedMemory); } @@ -254,19 +288,19 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag lblPhysicalMemory.getStyleClass().add("memory-label"); digitalPane.setLeft(lblPhysicalMemory); lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() -> { - return i18n("settings.memory.used_per_total", memoryStatusProperty.get().getUsedGB(), memoryStatusProperty.get().getTotalGB()); - }, memoryStatusProperty)); + return i18n("settings.memory.used_per_total", memoryStatus.get().getUsedGB(), memoryStatus.get().getTotalGB()); + }, memoryStatus)); Label lblAllocateMemory = new Label(); lblAllocateMemory.textProperty().bind(Bindings.createStringBinding(() -> { - long maxMemory = Lang.parseInt(maxMemoryProperty.get(), 0) * 1024L * 1024L; - return i18n(memoryStatusProperty.get().hasAvailable() && maxMemory > memoryStatusProperty.get().getAvailable() + long maxMemory = Lang.parseInt(this.maxMemory.get(), 0) * 1024L * 1024L; + return i18n(memoryStatus.get().hasAvailable() && maxMemory > memoryStatus.get().getAvailable() ? (chkAutoAllocate.isSelected() ? "settings.memory.allocate.auto.exceeded" : "settings.memory.allocate.manual.exceeded") : (chkAutoAllocate.isSelected() ? "settings.memory.allocate.auto" : "settings.memory.allocate.manual"), OperatingSystem.PhysicalMemoryStatus.toGigaBytes(maxMemory), - OperatingSystem.PhysicalMemoryStatus.toGigaBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatusProperty.get().getAvailable(), chkAutoAllocate.isSelected())), - OperatingSystem.PhysicalMemoryStatus.toGigaBytes(memoryStatusProperty.get().getAvailable())); - }, memoryStatusProperty, maxMemoryProperty, chkAutoAllocate.selectedProperty())); + OperatingSystem.PhysicalMemoryStatus.toGigaBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())), + OperatingSystem.PhysicalMemoryStatus.toGigaBytes(memoryStatus.get().getAvailable())); + }, memoryStatus, maxMemory, chkAutoAllocate.selectedProperty())); lblAllocateMemory.getStyleClass().add("memory-label"); digitalPane.setRight(lblAllocateMemory); } @@ -374,7 +408,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag } ComponentList customCommandsPane = new ComponentList(); - customCommandsPane.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not()); + customCommandsPane.disableProperty().bind(enableSpecificSettings.not()); { GridPane pane = new GridPane(); pane.setHgap(16); @@ -410,7 +444,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag } ComponentList jvmPane = new ComponentList(); - jvmPane.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not()); + jvmPane.disableProperty().bind(enableSpecificSettings.not()); { GridPane pane = new GridPane(); ColumnConstraints title = new ColumnConstraints(); @@ -437,7 +471,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag } ComponentList workaroundPane = new ComponentList(); - workaroundPane.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not()); + workaroundPane.disableProperty().bind(enableSpecificSettings.not()); { nativesDirItem = new MultiFileItem<>(); nativesDirItem.setTitle(i18n("settings.advanced.natives_directory")); @@ -468,7 +502,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag workaroundPane.getContent().setAll(nativesDirItem, noJVMArgsPane, noGameCheckPane, noJVMCheckPane, useNativeGLFWPane, useNativeOpenALPane); } - rootPane.getChildren().setAll(iconPickerItemWrapper, settingsTypePane, componentList, + rootPane.getChildren().addAll(componentList, advancedHintPane, ComponentList.createComponentListTitle(i18n("settings.advanced.custom_commands")), customCommandsPane, ComponentList.createComponentListTitle(i18n("settings.advanced.jvm")), jvmPane, @@ -477,7 +511,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag initialize(); specificSettingsListener = any -> { - chkEnableSpecificSettings.setSelected(!lastVersionSetting.isUsesGlobal()); + enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal()); }; addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); @@ -490,7 +524,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag } private void initialize() { - memoryStatusProperty.set(OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID)); + memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID)); Task.supplyAsync(JavaVersion::getJavas).thenAcceptAsync(Schedulers.javafx(), list -> { List> options = list.stream() @@ -509,7 +543,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe")); - chkEnableSpecificSettings.selectedProperty().addListener((a, b, newValue) -> { + enableSpecificSettings.addListener((a, b, newValue) -> { if (versionId == null) return; // do not call versionSettings.setUsesGlobal(true/false) @@ -523,31 +557,36 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag Platform.runLater(() -> loadVersion(profile, versionId)); }); - componentList.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not()); + componentList.disableProperty().bind(enableSpecificSettings.not()); } @Override public void loadVersion(Profile profile, String versionId) { this.profile = profile; this.versionId = versionId; + this.listenerHolder = new WeakListenerHolder(); if (versionId == null) { - rootPane.getChildren().remove(iconPickerItemWrapper); - rootPane.getChildren().remove(settingsTypePane); - chkEnableSpecificSettings.setSelected(true); + enableSpecificSettings.set(true); state.set(State.fromTitle(Profiles.getProfileDisplayName(profile) + " - " + i18n("settings.type.global.manage"))); + + listenerHolder.add(FXUtils.onWeakChangeAndOperate(profile.selectedVersionProperty(), selectedVersion -> { + this.selectedVersion.setValue(selectedVersion); + navigateToSpecificSettings.set(!profile.getRepository().getVersionSetting(selectedVersion).isUsesGlobal()); + })); + } else { + navigateToSpecificSettings.set(false); } VersionSetting versionSetting = profile.getVersionSetting(versionId); - gameDirItem.setDisable(versionId != null && profile.getRepository().isModpack(versionId)); - settingsTypePane.setDisable(versionId != null && profile.getRepository().isModpack(versionId)); + modpack.set(versionId != null && profile.getRepository().isModpack(versionId)); // unbind data fields if (lastVersionSetting != null) { FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty()); FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty()); - maxMemoryProperty.unbindBidirectional(lastVersionSetting.maxMemoryProperty()); + maxMemory.unbindBidirectional(lastVersionSetting.maxMemoryProperty()); javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty()); gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty()); nativesDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.nativesDirProperty()); @@ -586,7 +625,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag // bind new data fields FXUtils.bindInt(txtWidth, versionSetting.widthProperty()); FXUtils.bindInt(txtHeight, versionSetting.heightProperty()); - maxMemoryProperty.bindBidirectional(versionSetting.maxMemoryProperty()); + maxMemory.bindBidirectional(versionSetting.maxMemoryProperty()); javaCustomOption.bindBidirectional(versionSetting.javaDirProperty()); gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty()); @@ -610,7 +649,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag versionSetting.usesGlobalProperty().addListener(specificSettingsListener); if (versionId != null) - chkEnableSpecificSettings.setSelected(!versionSetting.isUsesGlobal()); + enableSpecificSettings.set(!versionSetting.isUsesGlobal()); javaItem.setToggleSelectedListener(newValue -> { if (newValue.getUserData() == null) { @@ -669,6 +708,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag .start(); } + private void editSpecificSettings() { + Versions.modifyGameSettings(profile, profile.getSelectedVersion()); + } + private void editGlobalSettings() { Versions.modifyGlobalSettings(profile); } @@ -703,7 +746,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private void loadIcon() { if (versionId == null) { - iconPickerItem.setImage(newImage("/assets/img/grass.png")); return; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java index fdb709b23..2719513e0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java @@ -62,9 +62,9 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi runInFX(() -> { if (success) { if (settings.containsKey("success_message") && settings.get("success_message") instanceof String) - Controllers.dialog((String) settings.get("success_message"), null, MessageType.FINE, () -> onEnd()); + Controllers.dialog((String) settings.get("success_message"), null, MessageType.SUCCESS, () -> onEnd()); else if (!settings.containsKey("forbid_success_message")) - Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd()); + Controllers.dialog(i18n("message.success"), null, MessageType.SUCCESS, () -> onEnd()); } else { if (executor.getException() == null) return; diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c72825e65..da666f05c 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -43,14 +43,54 @@ } .hint { - -fx-border-color: #b8daff; - -fx-background-color: #cce5ff; -fx-background-radius: 5; -fx-border-width: 1; -fx-border-radius: 5; -fx-padding: 6; } +/* + Colors are picked from bootstrap + + https://getbootstrap.com/docs/4.1/components/alerts/ + */ + +.hint.info { + -fx-background-color: #cce5ff; + -fx-border-color: #b8daff; +} + +.hint.info Text, .hint.info .svg { + -fx-fill: #004085; +} + +.hint.success { + -fx-background-color: #d4edda; + -fx-border-color: #c3e6cb; +} + +.hint.success Text, .hint.success .svg { + -fx-fill: #155724; +} + +.hint.error { + -fx-background-color: #f8d7da; + -fx-border-color: #f5c6cb; +} + +.hint.error Text, .hint.error .svg { + -fx-fill: #721c24; +} + +.hint.warning { + -fx-background-color: #fff3cd; + -fx-border-color: #ffeeba; +} + +.hint.warning Text, .hint.warning .svg { + -fx-fill: #856404; +} + .memory-label { } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 50324ad1a..e4ff04fcd 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -797,12 +797,15 @@ settings.memory.lower_bound=Minimum Memory settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total settings.physical_memory=Physical Memory Size settings.show_log=Show Logs +settings.skin=Now changing skin of offline account is supported! Go to account page and change your skin now! settings.tabs.installers=Installers settings.type=Version setting type settings.type.global=Global game settings (all settings shared among games) settings.type.global.manage=Global Game Settings settings.type.global.edit=Configure global game settings settings.type.special.enable=Enable specialized settings for this game +settings.type.special.edit=Configure current game settings +settings.type.special.edit.hint=Current game version %s has enabled specialized settings, resulting in options in current page not applied to that game version. Click link to configure current game settings. sponsor=Donations sponsor.bmclapi=Download services are provided by BMCLAPI and MCBBS. Click here for more information. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 05098185e..5ad453008 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -796,12 +796,15 @@ settings.memory.lower_bound=最低分配 settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB settings.physical_memory=實體記憶體大小 settings.show_log=查看記錄 +settings.skin=現已支持離線帳戶更換皮膚,你可以到帳戶頁面更改離線帳戶的皮膚和披風(多人遊戲下其他玩家無法看到你的皮膚) settings.tabs.installers=自動安裝 settings.type=版本設定類型 settings.type.global=全域版本設定(使用該設定的版本共用一套設定) settings.type.global.manage=全域遊戲設定 settings.type.global.edit=編輯全域遊戲設定 settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本) +settings.type.special.edit=編輯遊戲特定設置 +settings.type.special.edit.hint=當前遊戲版本 %s 啟動了遊戲特定設置,本頁面選項不對當前遊戲生效。點擊連結以修改當前遊戲設置。 sponsor=贊助 sponsor.bmclapi=大中華區下載源由 BMCLAPI 和我的世界中文論壇 (MCBBS) 提供高速下載服務 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 462cd6461..8b009ca88 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -796,12 +796,15 @@ settings.memory.lower_bound=最低分配 settings.memory.used_per_total=已使用 %1$.1f GB / 总内存 %2$.1f GB settings.physical_memory=物理内存大小 settings.show_log=查看日志 +settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风(多人游戏下其他玩家无法看到你的皮肤) settings.tabs.installers=自动安装 settings.type=版本设置类型 settings.type.global=全局版本设置(使用该设置的版本共用一套设定) settings.type.global.manage=全局游戏设置 settings.type.global.edit=编辑全局版本设置 settings.type.special.enable=启用游戏特定设置(不影响其他游戏版本) +settings.type.special.edit=编辑游戏特定设置 +settings.type.special.edit.hint=当前游戏版本 %s 启动了游戏特定设置,本页面选项不对当前游戏生效。点击链接以修改当前游戏设置。 sponsor=赞助 sponsor.bmclapi=国内下载源由 BMCLAPI 和我的世界中文论坛 (MCBBS) 提供高速下载服务。BMCLAPI 是公益服务,请赞助 BMCLAPI 以获得稳定高速的下载服务,点击此处查阅详细信息。