Skip to content

Commit fb942d1

Browse files
authored
优化独立窗口弹窗 (#5060)
resolves #4942
1 parent 3842b6d commit fb942d1

4 files changed

Lines changed: 197 additions & 114 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Hello Minecraft! Launcher
3+
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package org.jackhuang.hmcl.ui;
19+
20+
import com.jfoenix.controls.JFXDialog;
21+
import javafx.application.Platform;
22+
import javafx.beans.value.ChangeListener;
23+
import javafx.beans.value.ObservableValue;
24+
import javafx.event.EventHandler;
25+
import javafx.scene.Node;
26+
import javafx.scene.layout.StackPane;
27+
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
28+
import org.jackhuang.hmcl.ui.construct.DialogAware;
29+
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
30+
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
31+
import org.jackhuang.hmcl.ui.decorator.Decorator;
32+
import org.jetbrains.annotations.Nullable;
33+
34+
import java.util.Optional;
35+
import java.util.function.Consumer;
36+
37+
public final class DialogUtils {
38+
private DialogUtils() {
39+
}
40+
41+
public static final String PROPERTY_DIALOG_INSTANCE = DialogUtils.class.getName() + ".dialog.instance";
42+
public static final String PROPERTY_DIALOG_PANE_INSTANCE = DialogUtils.class.getName() + ".dialog.pane.instance";
43+
public static final String PROPERTY_DIALOG_CLOSE_HANDLER = DialogUtils.class.getName() + ".dialog.closeListener";
44+
45+
public static final String PROPERTY_PARENT_PANE_REF = DialogUtils.class.getName() + ".dialog.parentPaneRef";
46+
public static final String PROPERTY_PARENT_DIALOG_REF = DialogUtils.class.getName() + ".dialog.parentDialogRef";
47+
48+
public static void show(Decorator decorator, Node content) {
49+
if (decorator.getDrawerWrapper() == null) {
50+
Platform.runLater(() -> show(decorator, content));
51+
return;
52+
}
53+
54+
show(decorator.getDrawerWrapper(), content, (dialog) -> {
55+
JFXDialogPane pane = (JFXDialogPane) dialog.getContent();
56+
decorator.capableDraggingWindow(dialog);
57+
decorator.forbidDraggingWindow(pane);
58+
dialog.setDialogContainer(decorator.getDrawerWrapper());
59+
});
60+
}
61+
62+
public static void show(StackPane container, Node content) {
63+
show(container, content, null);
64+
}
65+
66+
public static void show(StackPane container, Node content, @Nullable Consumer<JFXDialog> onDialogCreated) {
67+
FXUtils.checkFxUserThread();
68+
69+
JFXDialog dialog = (JFXDialog) container.getProperties().get(PROPERTY_DIALOG_INSTANCE);
70+
JFXDialogPane dialogPane = (JFXDialogPane) container.getProperties().get(PROPERTY_DIALOG_PANE_INSTANCE);
71+
72+
if (dialog == null) {
73+
dialog = new JFXDialog(AnimationUtils.isAnimationEnabled()
74+
? JFXDialog.DialogTransition.CENTER
75+
: JFXDialog.DialogTransition.NONE);
76+
dialogPane = new JFXDialogPane();
77+
78+
dialog.setContent(dialogPane);
79+
dialog.setDialogContainer(container);
80+
dialog.setOverlayClose(false);
81+
82+
container.getProperties().put(PROPERTY_DIALOG_INSTANCE, dialog);
83+
container.getProperties().put(PROPERTY_DIALOG_PANE_INSTANCE, dialogPane);
84+
85+
if (onDialogCreated != null) {
86+
onDialogCreated.accept(dialog);
87+
}
88+
89+
dialog.show();
90+
}
91+
92+
content.getProperties().put(PROPERTY_PARENT_PANE_REF, dialogPane);
93+
content.getProperties().put(PROPERTY_PARENT_DIALOG_REF, dialog);
94+
95+
dialogPane.push(content);
96+
97+
EventHandler<DialogCloseEvent> handler = event -> close(content);
98+
content.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
99+
content.addEventHandler(DialogCloseEvent.CLOSE, handler);
100+
101+
handleDialogShown(dialog, content);
102+
}
103+
104+
private static void handleDialogShown(JFXDialog dialog, Node node) {
105+
if (dialog.isVisible()) {
106+
dialog.requestFocus();
107+
if (node instanceof DialogAware dialogAware)
108+
dialogAware.onDialogShown();
109+
} else {
110+
dialog.visibleProperty().addListener(new ChangeListener<>() {
111+
@Override
112+
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
113+
if (newValue) {
114+
dialog.requestFocus();
115+
if (node instanceof DialogAware dialogAware)
116+
dialogAware.onDialogShown();
117+
observable.removeListener(this);
118+
}
119+
}
120+
});
121+
}
122+
}
123+
124+
@SuppressWarnings("unchecked")
125+
public static void close(Node content) {
126+
FXUtils.checkFxUserThread();
127+
128+
Optional.ofNullable(content.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
129+
.ifPresent(handler -> content.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
130+
131+
JFXDialogPane pane = (JFXDialogPane) content.getProperties().get(PROPERTY_PARENT_PANE_REF);
132+
JFXDialog dialog = (JFXDialog) content.getProperties().get(PROPERTY_PARENT_DIALOG_REF);
133+
134+
if (dialog != null && pane != null) {
135+
if (pane.size() == 1 && pane.peek().orElse(null) == content) {
136+
dialog.setOnDialogClosed(e -> pane.pop(content));
137+
dialog.close();
138+
139+
StackPane container = dialog.getDialogContainer();
140+
if (container != null) {
141+
container.getProperties().remove(PROPERTY_DIALOG_INSTANCE);
142+
container.getProperties().remove(PROPERTY_DIALOG_PANE_INSTANCE);
143+
container.getProperties().remove(PROPERTY_PARENT_DIALOG_REF);
144+
container.getProperties().remove(PROPERTY_PARENT_PANE_REF);
145+
}
146+
} else {
147+
pane.pop(content);
148+
}
149+
150+
if (content instanceof DialogAware dialogAware) {
151+
dialogAware.onDialogClosed();
152+
}
153+
}
154+
}
155+
}

HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424
import javafx.geometry.Pos;
2525
import javafx.scene.Node;
2626
import javafx.scene.Scene;
27-
import javafx.scene.control.Alert;
2827
import javafx.scene.control.Label;
2928
import javafx.scene.control.ScrollPane;
3029
import javafx.scene.layout.HBox;
3130
import javafx.scene.layout.Priority;
31+
import javafx.scene.layout.StackPane;
3232
import javafx.scene.layout.VBox;
3333
import javafx.scene.text.Text;
3434
import javafx.scene.text.TextFlow;
@@ -40,6 +40,7 @@
4040
import org.jackhuang.hmcl.setting.StyleSheets;
4141
import org.jackhuang.hmcl.task.Schedulers;
4242
import org.jackhuang.hmcl.task.Task;
43+
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
4344
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
4445
import org.jackhuang.hmcl.util.Lang;
4546
import org.jackhuang.hmcl.util.Log4jLevel;
@@ -84,6 +85,7 @@ public class GameCrashWindow extends Stage {
8485
private final ProcessListener.ExitType exitType;
8586
private final LaunchOptions launchOptions;
8687
private final View view;
88+
private final StackPane stackPane;
8789

8890
private final List<Log> logs;
8991

@@ -106,9 +108,10 @@ public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType e
106108

107109
this.view = new View();
108110

111+
this.stackPane = new StackPane(view);
109112
this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
110113

111-
setScene(new Scene(view, 800, 480));
114+
setScene(new Scene(stackPane, 800, 480));
112115
StyleSheets.init(getScene());
113116
setTitle(i18n("game.crash.title"));
114117
FXUtils.setIcon(this);
@@ -297,19 +300,16 @@ private void exportGameCrashInfo() {
297300
});
298301
})
299302
.handleAsync((result, exception) -> {
300-
Alert alert;
301-
302303
if (exception == null) {
303304
FXUtils.showFileInExplorer(logFile);
304-
alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
305+
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.success", logFile), i18n("message.success"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();
306+
DialogUtils.show(stackPane, dialog);
305307
} else {
306308
LOG.warning("Failed to export game crash info", exception);
307-
alert = new Alert(Alert.AlertType.WARNING, i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception));
309+
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception), i18n("message.error"), MessageDialogPane.MessageType.ERROR).ok(null).build();
310+
DialogUtils.show(stackPane, dialog);
308311
}
309312

310-
alert.setTitle(i18n("settings.launcher.launcher_log.export"));
311-
alert.showAndWait();
312-
313313
return null;
314314
}, Schedulers.javafx());
315315
}

HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import javafx.geometry.Insets;
3434
import javafx.geometry.Pos;
3535
import javafx.scene.Scene;
36-
import javafx.scene.control.Label;
3736
import javafx.scene.control.*;
3837
import javafx.scene.input.KeyCode;
3938
import javafx.scene.input.MouseButton;
@@ -44,11 +43,10 @@
4443
import org.jackhuang.hmcl.game.Log;
4544
import org.jackhuang.hmcl.setting.StyleSheets;
4645
import org.jackhuang.hmcl.theme.Themes;
46+
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
4747
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
4848
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
49-
import org.jackhuang.hmcl.util.CircularArrayList;
50-
import org.jackhuang.hmcl.util.Lang;
51-
import org.jackhuang.hmcl.util.Log4jLevel;
49+
import org.jackhuang.hmcl.util.*;
5250
import org.jackhuang.hmcl.util.platform.*;
5351
import org.jackhuang.hmcl.util.platform.windows.Dwmapi;
5452
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
@@ -65,8 +63,8 @@
6563

6664
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
6765
import static org.jackhuang.hmcl.util.Lang.thread;
68-
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
6966
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
67+
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
7068

7169
/**
7270
* @author huangyuhui
@@ -199,6 +197,7 @@ private final class LogWindowImpl extends Control {
199197
private final StringProperty[] buttonText = new StringProperty[LEVELS.length];
200198
private final BooleanProperty[] showLevel = new BooleanProperty[LEVELS.length];
201199
private final JFXComboBox<Integer> cboLines = new JFXComboBox<>();
200+
private final StackPane stackPane = new StackPane();
202201

203202
LogWindowImpl() {
204203
getStyleClass().add("log-window");
@@ -241,9 +240,8 @@ private void onExportLogs() {
241240
}
242241

243242
Platform.runLater(() -> {
244-
Alert alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
245-
alert.setTitle(i18n("settings.launcher.launcher_log.export"));
246-
alert.showAndWait();
243+
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.success", logFile), i18n("message.success"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();
244+
DialogUtils.show(stackPane, dialog);
247245
});
248246

249247
FXUtils.showFileInExplorer(logFile);
@@ -267,9 +265,8 @@ private void onExportDump(SpinnerPane pane) {
267265
LOG.warning("Failed to create minecraft jstack dump", e);
268266

269267
Platform.runLater(() -> {
270-
Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump"));
271-
alert.setTitle(i18n("message.error"));
272-
alert.showAndWait();
268+
var dialog = new MessageDialogPane.Builder(i18n("logwindow.export_dump") + "\n" + StringUtils.getStackTrace(e), i18n("message.error"), MessageDialogPane.MessageType.ERROR).ok(null).build();
269+
DialogUtils.show(stackPane, dialog);
273270
});
274271
}
275272

@@ -300,7 +297,9 @@ private static final class LogWindowSkin extends SkinBase<LogWindowImpl> {
300297

301298
VBox vbox = new VBox(3);
302299
vbox.setPadding(new Insets(3, 0, 3, 0));
303-
getChildren().setAll(vbox);
300+
getSkinnable().stackPane.getChildren().setAll(vbox);
301+
getChildren().setAll(getSkinnable().stackPane);
302+
304303

305304
{
306305
BorderPane borderPane = new BorderPane();

0 commit comments

Comments
 (0)