3636import javafx .scene .control .Label ;
3737import javafx .scene .layout .Region ;
3838import javafx .scene .paint .Color ;
39- import javafx .stage .Screen ;
40- import javafx .stage .Stage ;
41- import javafx .stage .StageStyle ;
39+ import javafx .stage .*;
4240import javafx .util .Duration ;
4341import org .jackhuang .hmcl .Launcher ;
4442import org .jackhuang .hmcl .Metadata ;
7371import org .jetbrains .annotations .Nullable ;
7472
7573import java .io .IOException ;
74+ import java .nio .file .Path ;
7675import java .time .LocalDate ;
7776import java .util .List ;
7877import java .util .concurrent .CompletableFuture ;
@@ -97,12 +96,12 @@ public final class Controllers {
9796 public static final int MIN_CONTENT_HEIGHT = 450 + 2 + 40 ; // bg height + border width*2 + toolbar height
9897 public static final int MIN_WIDTH = MIN_CONTENT_WIDTH + CUSTOM_DECORATION_SHADOW_EXTENT ;
9998 public static final int MIN_HEIGHT = MIN_CONTENT_HEIGHT + CUSTOM_DECORATION_SHADOW_EXTENT ;
100- public static final Screen SCREEN = Screen .getPrimary ();
99+ public static final Rectangle2D PRIMARY_SCREEN_BOUNDS = Screen .getPrimary (). getBounds ();
101100 private static InvalidationListener stageSizeChangeListener ;
102- private static DoubleProperty stageX = new SimpleDoubleProperty ();
103- private static DoubleProperty stageY = new SimpleDoubleProperty ();
104- private static DoubleProperty stageWidth = new SimpleDoubleProperty ();
105- private static DoubleProperty stageHeight = new SimpleDoubleProperty ();
101+ private static final DoubleProperty contentX = new SimpleDoubleProperty ();
102+ private static final DoubleProperty contentY = new SimpleDoubleProperty ();
103+ private static final DoubleProperty contentWidth = new SimpleDoubleProperty ();
104+ private static final DoubleProperty contentHeight = new SimpleDoubleProperty ();
106105
107106 private static Scene scene ;
108107 private static Stage stage ;
@@ -133,12 +132,16 @@ public interface ThrowingRunnable {
133132 void run () throws Exception ;
134133 }
135134
136- public static Scene getScene () {
137- return scene ;
135+ public static @ Nullable Stage getStage () {
136+ return stage ;
138137 }
139138
140- public static Stage getStage () {
141- return stage ;
139+ public static ReadOnlyDoubleProperty windowWidthProperty () {
140+ return contentWidth ;
141+ }
142+
143+ public static ReadOnlyDoubleProperty windowHeightProperty () {
144+ return contentHeight ;
142145 }
143146
144147 @ FXThread
@@ -214,64 +217,8 @@ public static DecoratorController getDecorator() {
214217 return decorator ;
215218 }
216219
217- public static void saveWindowStates () {
218- saveWindowBounds ();
219- }
220-
221- private static void saveWindowBounds () {
222- if (stageX != null ) {
223- state ().setX (toContentX (stageX .get ()) / SCREEN .getBounds ().getWidth ());
224- }
225- if (stageY != null ) {
226- state ().setY (toContentY (stageY .get ()) / SCREEN .getBounds ().getHeight ());
227- }
228- if (stageHeight != null ) {
229- state ().setHeight (toContentHeight (stageHeight .get ()));
230- }
231- if (stageWidth != null ) {
232- state ().setWidth (toContentWidth (stageWidth .get ()));
233- }
234- }
235-
236220 public static void onApplicationStop () {
237221 stageSizeChangeListener = null ;
238- saveWindowBounds ();
239- stageX = null ;
240- stageY = null ;
241- stageHeight = null ;
242- stageWidth = null ;
243- }
244-
245- private static double toContentX (double stageX ) {
246- return stageX + CUSTOM_DECORATION_SHADOW_SIZE ;
247- }
248-
249- private static double toContentY (double stageY ) {
250- return stageY + CUSTOM_DECORATION_SHADOW_SIZE ;
251- }
252-
253- private static double toStageX (double contentX ) {
254- return contentX - CUSTOM_DECORATION_SHADOW_SIZE ;
255- }
256-
257- private static double toStageY (double contentY ) {
258- return contentY - CUSTOM_DECORATION_SHADOW_SIZE ;
259- }
260-
261- private static double toContentWidth (double stageWidth ) {
262- return Math .max (0.0 , stageWidth - CUSTOM_DECORATION_SHADOW_EXTENT );
263- }
264-
265- private static double toContentHeight (double stageHeight ) {
266- return Math .max (0.0 , stageHeight - CUSTOM_DECORATION_SHADOW_EXTENT );
267- }
268-
269- private static double toStageWidth (double contentWidth ) {
270- return contentWidth + CUSTOM_DECORATION_SHADOW_EXTENT ;
271- }
272-
273- private static double toStageHeight (double contentHeight ) {
274- return contentHeight + CUSTOM_DECORATION_SHADOW_EXTENT ;
275222 }
276223
277224 public static void initialize (Stage stage ) {
@@ -285,60 +232,22 @@ public static void initialize(Stage stage) {
285232 LOG .info ("Enable sub-pixel antialiasing" );
286233 System .getProperties ().put ("prism.lcdtext" , "true" );
287234 } else if ("gray" .equalsIgnoreCase (fontAntiAliasing )
288- || OperatingSystem .CURRENT_OS == OperatingSystem .WINDOWS && SCREEN .getOutputScaleX () > 1 ) {
235+ || OperatingSystem .CURRENT_OS == OperatingSystem .WINDOWS && Screen . getPrimary () .getOutputScaleX () > 1 ) {
289236 LOG .info ("Disable sub-pixel antialiasing" );
290237 System .getProperties ().put ("prism.lcdtext" , "false" );
291238 }
292239 }
293240
294241 Controllers .stage = stage ;
295242
296- stageSizeChangeListener = o -> {
297- ReadOnlyDoubleProperty sourceProperty = (ReadOnlyDoubleProperty ) o ;
298- DoubleProperty targetProperty ;
299- switch (sourceProperty .getName ()) {
300- case "x" : {
301- targetProperty = stageX ;
302- break ;
303- }
304- case "y" : {
305- targetProperty = stageY ;
306- break ;
307- }
308- case "width" : {
309- targetProperty = stageWidth ;
310- break ;
311- }
312- case "height" : {
313- targetProperty = stageHeight ;
314- break ;
315- }
316- default : {
317- targetProperty = null ;
318- }
319- }
320-
321- if (targetProperty != null
322- && Controllers .stage != null
323- && !Controllers .stage .isIconified ()
324- // https://github.com/HMCL-dev/HMCL/issues/4290
325- && (OperatingSystem .CURRENT_OS == OperatingSystem .MACOS ||
326- !Controllers .stage .isFullScreen () && !Controllers .stage .isMaximized ())
327- ) {
328- targetProperty .set (sourceProperty .get ());
329- }
330- };
331-
332- WeakInvalidationListener weakListener = new WeakInvalidationListener (stageSizeChangeListener );
333-
334243 double initContentWidth = Math .max (MIN_CONTENT_WIDTH , state ().getWidth ());
335244 double initContentHeight = Math .max (MIN_CONTENT_HEIGHT , state ().getHeight ());
336- double initWidth = toStageWidth ( initContentWidth ) ;
337- double initHeight = toStageHeight ( initContentHeight ) ;
245+ double initWidth = initContentWidth + CUSTOM_DECORATION_SHADOW_EXTENT ;
246+ double initHeight = initContentHeight + CUSTOM_DECORATION_SHADOW_EXTENT ;
338247
339248 {
340- double initContentX = state ().getX () * SCREEN . getBounds () .getWidth ();
341- double initContentY = state ().getY () * SCREEN . getBounds () .getHeight ();
249+ double initContentX = state ().getX () * PRIMARY_SCREEN_BOUNDS .getWidth ();
250+ double initContentY = state ().getY () * PRIMARY_SCREEN_BOUNDS .getHeight ();
342251
343252 boolean invalid = true ;
344253 double border = 20D ;
@@ -355,24 +264,65 @@ public static void initialize(Stage stage) {
355264 }
356265
357266 if (invalid ) {
358- initContentX = (0.5D - initContentWidth / SCREEN . getBounds () .getWidth () / 2 )
359- * SCREEN . getBounds () .getWidth ();
360- initContentY = (0.5D - initContentHeight / SCREEN . getBounds () .getHeight () / 2 )
361- * SCREEN . getBounds () .getHeight ();
267+ initContentX = (0.5D - initContentWidth / PRIMARY_SCREEN_BOUNDS .getWidth () / 2 )
268+ * PRIMARY_SCREEN_BOUNDS .getWidth ();
269+ initContentY = (0.5D - initContentHeight / PRIMARY_SCREEN_BOUNDS .getHeight () / 2 )
270+ * PRIMARY_SCREEN_BOUNDS .getHeight ();
362271 }
363272
364- double initX = toStageX ( initContentX ) ;
365- double initY = toStageY ( initContentY ) ;
273+ double initX = initContentX - CUSTOM_DECORATION_SHADOW_SIZE ;
274+ double initY = initContentY - CUSTOM_DECORATION_SHADOW_SIZE ;
366275 stage .setX (initX );
367276 stage .setY (initY );
368- stageX .set (initX );
369- stageY .set (initY );
277+ contentX .set (initContentX );
278+ contentY .set (initContentY );
370279 }
371280
372281 stage .setHeight (initHeight );
373282 stage .setWidth (initWidth );
374- stageHeight .set (initHeight );
375- stageWidth .set (initWidth );
283+ contentHeight .set (initContentHeight );
284+ contentWidth .set (initContentWidth );
285+
286+ stageSizeChangeListener = o -> {
287+ ReadOnlyDoubleProperty property = (ReadOnlyDoubleProperty ) o ;
288+ Stage currentStage = property .getBean () instanceof Stage s ? s : null ;
289+ if (currentStage == null )
290+ return ;
291+
292+ boolean saveState = !currentStage .isIconified ()
293+ // https://github.com/HMCL-dev/HMCL/issues/4290
294+ && (OperatingSystem .CURRENT_OS == OperatingSystem .MACOS
295+ || !currentStage .isFullScreen () && !currentStage .isMaximized ());
296+
297+ switch (property .getName ()) {
298+ case "x" -> {
299+ double value = property .get () + CUSTOM_DECORATION_SHADOW_SIZE ;
300+ contentX .set (value );
301+ if (saveState )
302+ state ().setX (value / PRIMARY_SCREEN_BOUNDS .getWidth ());
303+ }
304+ case "y" -> {
305+ double value = property .get () + CUSTOM_DECORATION_SHADOW_SIZE ;
306+ contentY .set (value );
307+ if (saveState )
308+ state ().setY (value / PRIMARY_SCREEN_BOUNDS .getHeight ());
309+ }
310+ case "width" -> {
311+ double value = Math .max (MIN_CONTENT_WIDTH , property .get () - CUSTOM_DECORATION_SHADOW_EXTENT );
312+ contentWidth .set (value );
313+ if (saveState )
314+ state ().setWidth (value );
315+ }
316+ case "height" -> {
317+ double value = Math .max (MIN_CONTENT_HEIGHT , property .get () - CUSTOM_DECORATION_SHADOW_EXTENT );
318+ contentHeight .set (value );
319+ if (saveState )
320+ state ().setHeight (value );
321+ }
322+ }
323+ };
324+
325+ WeakInvalidationListener weakListener = new WeakInvalidationListener (stageSizeChangeListener );
376326 stage .xProperty ().addListener (weakListener );
377327 stage .yProperty ().addListener (weakListener );
378328 stage .heightProperty ().addListener (weakListener );
@@ -585,7 +535,7 @@ public static void confirm(String text, String title, MessageType type, Runnable
585535
586536 /// Shows a warning that confirms backing up a read-only settings file before overwriting it.
587537 ///
588- /// @param text the file-specific read-only warning
538+ /// @param text the file-specific read-only warning
589539 /// @param overwrite the action that backs up and overwrites the file
590540 public static void confirmBackupAndOverwrite (String text , ThrowingRunnable overwrite ) {
591541 dialog (new MessageDialogPane .Builder (
@@ -694,6 +644,22 @@ public static void showToast(String content) {
694644 decorator .showToast (content );
695645 }
696646
647+ public static @ Nullable Path showDialog (DirectoryChooser directoryChooser ) {
648+ return FileUtils .toPath (directoryChooser .showDialog (stage ));
649+ }
650+
651+ public static @ Nullable Path showOpenDialog (FileChooser fileChooser ) {
652+ return FileUtils .toPath (fileChooser .showOpenDialog (stage ));
653+ }
654+
655+ public static @ Nullable Path showSaveDialog (FileChooser fileChooser ) {
656+ return FileUtils .toPath (fileChooser .showSaveDialog (stage ));
657+ }
658+
659+ public static @ Nullable List <Path > showOpenMultipleDialog (FileChooser fileChooser ) {
660+ return FileUtils .toPaths (fileChooser .showOpenMultipleDialog (stage ));
661+ }
662+
697663 public static void onHyperlinkAction (String href ) {
698664 if (href .startsWith ("hmcl://" )) {
699665 switch (href ) {
0 commit comments