Skip to content
12 changes: 12 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.jackhuang.hmcl.ui.versions.GameListPage;
import org.jackhuang.hmcl.ui.versions.VersionPage;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
Expand Down Expand Up @@ -331,6 +332,12 @@ public static void initialize(Stage stage) {
stage.setOnCloseRequest(e -> Launcher.stopApplication());

decorator = new DecoratorController(stage, getRootPage());
getRootPage().getMainPage().showUpdateProperty().bind(UpdateChecker.checkingUpdateProperty().not().and(UpdateChecker.outdatedProperty()));
getRootPage().getMainPage().showUpdateDialogProperty().bind(
decorator.backableProperty().not()
.and(getRootPage().getMainPage().showUpdateProperty())
.and(config().disableAutoShowUpdateDialogProperty().not())
);

if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&
!FileUtils.canCreateDirectory(config().getCommonDirectory())) {
Expand Down Expand Up @@ -563,6 +570,11 @@ public static void confirmWithCountdown(String text, String title, int seconds,
timeline.play();
}

public static void dialogLater(Region content) {
if (decorator != null)
decorator.showDialogLater(content);
}

public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult) {
return prompt(title, onResult, "");
}
Expand Down
38 changes: 38 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import org.jackhuang.hmcl.ui.decorator.Decorator;
import org.jetbrains.annotations.Nullable;

import java.util.LinkedList;
import java.util.Optional;
import java.util.Queue;
import java.util.function.Consumer;

public final class DialogUtils {
Expand All @@ -45,6 +47,8 @@ private DialogUtils() {
public static final String PROPERTY_PARENT_PANE_REF = DialogUtils.class.getName() + ".dialog.parentPaneRef";
public static final String PROPERTY_PARENT_DIALOG_REF = DialogUtils.class.getName() + ".dialog.parentDialogRef";

public static final String PROPERTY_DIALOG_SHOW_LATER = DialogUtils.class.getName() + ".dialog.showLater";

public static void show(Decorator decorator, Node content) {
if (decorator.getDrawerWrapper() == null) {
Platform.runLater(() -> show(decorator, content));
Expand Down Expand Up @@ -121,6 +125,30 @@ public void changed(ObservableValue<? extends Boolean> observable, Boolean oldVa
}
}

public static void showLater(Decorator decorator, Node content) {
if (decorator.getDrawerWrapper() == null) {
Platform.runLater(() -> showLater(decorator, content));
return;
}
showLater(decorator.getDrawerWrapper(), () -> show(decorator, content));
}

@SuppressWarnings("unchecked")
public static void showLater(StackPane container, Runnable showDialogAction) {
FXUtils.checkFxUserThread();

if (container.getProperties().get(PROPERTY_DIALOG_INSTANCE) == null) {
showDialogAction.run();
return;
}
Queue<Runnable> queue = (Queue<Runnable>) container.getProperties().get(PROPERTY_DIALOG_SHOW_LATER);
if (queue == null) {
queue = new LinkedList<>();
container.getProperties().put(PROPERTY_DIALOG_SHOW_LATER, queue);
}
queue.add(showDialogAction);
}
Comment on lines +137 to +150
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It is recommended to add a null check for showDialogAction before adding it to the queue to prevent potential NullPointerException when the action is eventually executed.

Suggested change
public static void showLater(StackPane container, Runnable showDialogAction) {
FXUtils.checkFxUserThread();
if (container.getProperties().get(PROPERTY_DIALOG_INSTANCE) == null) {
showDialogAction.run();
return;
}
Queue<Runnable> queue = (Queue<Runnable>) container.getProperties().get(PROPERTY_DIALOG_SHOW_LATER);
if (queue == null) {
queue = new LinkedList<>();
container.getProperties().put(PROPERTY_DIALOG_SHOW_LATER, queue);
}
queue.add(showDialogAction);
}
@SuppressWarnings("unchecked")
public static void showLater(StackPane container, Runnable showDialogAction) {
FXUtils.checkFxUserThread();
if (showDialogAction == null) return;
if (container.getProperties().get(PROPERTY_DIALOG_INSTANCE) == null) {
showDialogAction.run();
return;
}
Queue<Runnable> queue = (Queue<Runnable>) container.getProperties().get(PROPERTY_DIALOG_SHOW_LATER);
if (queue == null) {
queue = new LinkedList<>();
container.getProperties().put(PROPERTY_DIALOG_SHOW_LATER, queue);
}
queue.add(showDialogAction);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没有这个回调那这方法啥用没有啊


@SuppressWarnings("unchecked")
public static void close(Node content) {
FXUtils.checkFxUserThread();
Expand All @@ -131,6 +159,8 @@ public static void close(Node content) {
JFXDialogPane pane = (JFXDialogPane) content.getProperties().get(PROPERTY_PARENT_PANE_REF);
JFXDialog dialog = (JFXDialog) content.getProperties().get(PROPERTY_PARENT_DIALOG_REF);

Runnable showNextDialogAction = null;

if (dialog != null && pane != null) {
if (pane.size() == 1 && pane.peek().orElse(null) == content) {
dialog.setOnDialogClosed(e -> pane.pop(content));
Expand All @@ -142,6 +172,12 @@ public static void close(Node content) {
container.getProperties().remove(PROPERTY_DIALOG_PANE_INSTANCE);
container.getProperties().remove(PROPERTY_PARENT_DIALOG_REF);
container.getProperties().remove(PROPERTY_PARENT_PANE_REF);

Queue<Runnable> queue = (Queue<Runnable>) container.getProperties().get(PROPERTY_DIALOG_SHOW_LATER);
if (queue != null && !queue.isEmpty()) {
Runnable next = queue.remove();
if (next != null) showNextDialogAction = next;
}
}
} else {
pane.pop(content);
Expand All @@ -151,5 +187,7 @@ public static void close(Node content) {
dialogAware.onDialogClosed();
}
}

if (showNextDialogAction != null) showNextDialogAction.run();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Defer queued dialog until close animation completes

This runs the next queued dialog action immediately after dialog.close(), but JFXDialog.close() is asynchronous when animations are enabled (it schedules the actual close work in an animation.setOnFinished callback), so the queued dialog can be shown before the previous one has fully closed. In that case users can see overlapping/stacked dialogs and the "show after close" guarantee from this change is violated; trigger the queued action from the dialog-closed callback instead.

Useful? React with 👍 / 👎.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import javafx.animation.Interpolator;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.image.Image;
Expand Down Expand Up @@ -343,6 +344,10 @@ public void navigate(Node node, AnimationProducer animationProducer, Duration du
navigator.navigate(node, animationProducer, duration, interpolator);
}

public BooleanProperty backableProperty() {
return navigator.backableProperty();
}

private void close() {
if (navigator.getCurrentPage() instanceof DecoratorPage) {
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
Expand Down Expand Up @@ -427,6 +432,10 @@ private void closeDialog(Node node) {
DialogUtils.close(node);
}

public void showDialogLater(Node node) {
DialogUtils.showLater(decorator, node);
}

// ==== Toast ====

public void showToast(String content) {
Expand Down
31 changes: 23 additions & 8 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public final class MainPage extends StackPane implements DecoratorPage {

private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
private final BooleanProperty showUpdateDialog = new SimpleBooleanProperty(this, "showUpdateDialog");
private final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(this, "latestVersion");
private final ObservableList<Version> versions = FXCollections.observableArrayList();
private Profile profile;
Expand All @@ -98,6 +99,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
private final StackPane updatePane;
private final JFXButton menuButton;

private RemoteVersion lastShownVersion;

{
HBox titleNode = new HBox(8);
titleNode.setPadding(new Insets(0, 0, 0, 2));
Expand Down Expand Up @@ -170,7 +173,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
FXUtils.setLimitHeight(updatePane, 55);
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
FXUtils.onClicked(updatePane, this::onUpgrade);
FXUtils.onChange(showUpdateProperty(), this::showUpdate);
FXUtils.onChange(showUpdateProperty(), this::doAnimation);
FXUtils.onChange(showUpdateDialogProperty(), this::showUpdateDialog);

{
HBox hBox = new HBox();
Expand Down Expand Up @@ -274,13 +278,12 @@ public void accept(String currentGame) {

}

private void showUpdate(boolean show) {
doAnimation(show);

if (show && !config().isDisableAutoShowUpdateDialog()
&& getLatestVersion() != null
&& !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) {
Controllers.dialog(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO)
private void showUpdateDialog(boolean show) {
if (show && getLatestVersion() != null && !Objects.equals(getLatestVersion(), lastShownVersion)
&& !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())
) {
lastShownVersion = getLatestVersion();
Controllers.dialogLater(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO)
.addAction(i18n("button.view"), () -> {
config().setPromptedVersion(getLatestVersion().getVersion());
onUpgrade();
Expand Down Expand Up @@ -403,6 +406,18 @@ public void setShowUpdate(boolean showUpdate) {
this.showUpdate.set(showUpdate);
}

public boolean isShowUpdateDialog() {
return showUpdateDialog.get();
}

public BooleanProperty showUpdateDialogProperty() {
return showUpdateDialog;
}

public void setShowUpdateDialog(boolean showUpdateDialog) {
this.showUpdateDialog.set(showUpdateDialog);
}

public RemoteVersion getLatestVersion() {
return latestVersion.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ public MainPage getMainPage() {
});

FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), mainPage::setCurrentGame);
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
mainPage.latestVersionProperty().bind(UpdateChecker.latestVersionProperty());

Profiles.registerVersionsListener(profile -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ public static void requestCheckUpdate(UpdateChannel channel, boolean preview) {

RemoteVersion finalResult = result;
Platform.runLater(() -> {
checkingUpdate.set(false);
if (finalResult != null) {
latestVersion.set(finalResult);
}
checkingUpdate.set(false);
});
}, "Update Checker", true);
});
Expand Down