From 454744871896ea57fe5a76b7d702b4a2f7d65527 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 16:32:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B8=B8=E6=88=8F=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E5=A4=A7=E5=B0=8F=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20(#4234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 147 +++++++++++++++++- .../hmcl/ui/versions/VersionSettingsPage.java | 48 +++--- 2 files changed, 158 insertions(+), 37 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index ad97e738d..3776fd71f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -25,10 +25,7 @@ import javafx.beans.Observable; import javafx.beans.WeakInvalidationListener; import javafx.beans.WeakListener; import javafx.beans.binding.Bindings; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.Property; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.*; import javafx.beans.value.*; import javafx.collections.ObservableMap; import javafx.event.Event; @@ -832,10 +829,144 @@ public final class FXUtils { property.addListener(binding); } - public static void unbindColorPicker(ColorPicker colorPicker, Property property) { - PaintBidirectionalBinding binding = new PaintBidirectionalBinding(colorPicker, property); - colorPicker.valueProperty().removeListener(binding); - property.removeListener(binding); + private static final class WindowsSizeBidirectionalBinding implements InvalidationListener, WeakListener { + private final WeakReference> comboBoxRef; + private final WeakReference widthPropertyRef; + private final WeakReference heightPropertyRef; + + private final int hashCode; + + private boolean updating = false; + + private WindowsSizeBidirectionalBinding(JFXComboBox comboBox, + IntegerProperty widthProperty, + IntegerProperty heightProperty) { + this.comboBoxRef = new WeakReference<>(comboBox); + this.widthPropertyRef = new WeakReference<>(widthProperty); + this.heightPropertyRef = new WeakReference<>(heightProperty); + this.hashCode = System.identityHashCode(comboBox) + ^ System.identityHashCode(widthProperty) + ^ System.identityHashCode(heightProperty); + } + + @Override + public void invalidated(Observable observable) { + if (!updating) { + var comboBox = this.comboBoxRef.get(); + var widthProperty = this.widthPropertyRef.get(); + var heightProperty = this.heightPropertyRef.get(); + + if (comboBox == null || widthProperty == null || heightProperty == null) { + if (comboBox != null) { + comboBox.focusedProperty().removeListener(this); + comboBox.sceneProperty().removeListener(this); + } + if (widthProperty != null) + widthProperty.removeListener(this); + if (heightProperty != null) + heightProperty.removeListener(this); + } else { + updating = true; + try { + int width = widthProperty.get(); + int height = heightProperty.get(); + + if (observable instanceof ReadOnlyProperty + && ((ReadOnlyProperty) observable).getBean() == comboBox) { + String value = comboBox.valueProperty().get(); + if (value == null) + value = ""; + int idx = value.indexOf('x'); + if (idx < 0) + idx = value.indexOf('*'); + + if (idx < 0) { + LOG.warning("Bad window size: " + value); + comboBox.setValue(width + "x" + height); + return; + } + + String widthStr = value.substring(0, idx).trim(); + String heightStr = value.substring(idx + 1).trim(); + + int newWidth; + int newHeight; + try { + newWidth = Integer.parseInt(widthStr); + newHeight = Integer.parseInt(heightStr); + } catch (NumberFormatException e) { + LOG.warning("Bad window size: " + value); + comboBox.setValue(width + "x" + height); + return; + } + + widthProperty.set(newWidth); + heightProperty.set(newHeight); + } else { + comboBox.setValue(width + "x" + height); + } + } finally { + updating = false; + } + } + } + } + + @Override + public boolean wasGarbageCollected() { + return this.comboBoxRef.get() == null + || this.widthPropertyRef.get() == null + || this.heightPropertyRef.get() == null; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof WindowsSizeBidirectionalBinding)) + return false; + + var that = (WindowsSizeBidirectionalBinding) obj; + + var comboBox = this.comboBoxRef.get(); + var widthProperty = this.widthPropertyRef.get(); + var heightProperty = this.heightPropertyRef.get(); + + var thatComboBox = that.comboBoxRef.get(); + var thatWidthProperty = that.widthPropertyRef.get(); + var thatHeightProperty = that.heightPropertyRef.get(); + + if (comboBox == null || widthProperty == null || heightProperty == null + || thatComboBox == null || thatWidthProperty == null || thatHeightProperty == null) { + return false; + } + + return comboBox == thatComboBox + && widthProperty == thatWidthProperty + && heightProperty == thatHeightProperty; + } + } + + public static void bindWindowsSize(JFXComboBox comboBox, IntegerProperty widthProperty, IntegerProperty heightProperty) { + comboBox.setValue(widthProperty.get() + "x" + heightProperty.get()); + var binding = new WindowsSizeBidirectionalBinding(comboBox, widthProperty, heightProperty); + comboBox.focusedProperty().addListener(binding); + comboBox.sceneProperty().addListener(binding); + widthProperty.addListener(binding); + heightProperty.addListener(binding); + } + + public static void unbindWindowsSize(JFXComboBox comboBox, IntegerProperty widthProperty, IntegerProperty heightProperty) { + var binding = new WindowsSizeBidirectionalBinding(comboBox, widthProperty, heightProperty); + comboBox.focusedProperty().removeListener(binding); + comboBox.sceneProperty().removeListener(binding); + widthProperty.removeListener(binding); + heightProperty.removeListener(binding); } public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) { 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 e284cd930..e3548ffef 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 @@ -93,8 +93,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private String versionId; private final VBox rootPane; - private final JFXTextField txtWidth; - private final JFXTextField txtHeight; + private final JFXComboBox cboWindowsSize; private final JFXTextField txtServerIP; private final ComponentList componentList; private final JFXComboBox cboLauncherVisibility; @@ -389,29 +388,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag BorderPane right = new BorderPane(); dimensionPane.setRight(right); { - HBox hbox = new HBox(); - right.setLeft(hbox); - hbox.setPrefWidth(210); - hbox.setSpacing(3); - hbox.setAlignment(Pos.CENTER); - BorderPane.setAlignment(hbox, Pos.CENTER); - { - txtWidth = new JFXTextField(); - txtWidth.setPromptText("800"); - txtWidth.setPrefWidth(100); - FXUtils.setValidateWhileTextChanged(txtWidth, true); - txtWidth.getValidators().setAll(new NumberValidator(i18n("input.number"), false)); - - Label x = new Label("x"); - - txtHeight = new JFXTextField(); - txtHeight.setPromptText("480"); - txtHeight.setPrefWidth(100); - FXUtils.setValidateWhileTextChanged(txtHeight, true); - txtHeight.getValidators().setAll(new NumberValidator(i18n("input.number"), false)); - - hbox.getChildren().setAll(txtWidth, x, txtHeight); - } + cboWindowsSize = new JFXComboBox<>(); + cboWindowsSize.setPrefWidth(150); + right.setLeft(cboWindowsSize); + cboWindowsSize.setEditable(true); + cboWindowsSize.setStyle("-fx-padding: 4 4 4 16"); + cboWindowsSize.setPromptText("854x480"); + cboWindowsSize.getItems().addAll( + "854x480", + "1280x720", + "1600x900", + "1920x1080", + "2560x1440", + "3840x2160" + ); chkFullscreen = new JFXCheckBox(); right.setRight(chkFullscreen); @@ -419,6 +409,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag chkFullscreen.setAlignment(Pos.CENTER); BorderPane.setAlignment(chkFullscreen, Pos.CENTER); BorderPane.setMargin(chkFullscreen, new Insets(0, 0, 0, 7)); + + cboWindowsSize.disableProperty().bind(chkFullscreen.selectedProperty()); } } @@ -559,8 +551,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag // unbind data fields if (lastVersionSetting != null) { - FXUtils.unbind(txtWidth, lastVersionSetting.widthProperty()); - FXUtils.unbind(txtHeight, lastVersionSetting.heightProperty()); + FXUtils.unbindWindowsSize(cboWindowsSize, lastVersionSetting.widthProperty(), lastVersionSetting.heightProperty()); maxMemory.unbindBidirectional(lastVersionSetting.maxMemoryProperty()); javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty()); gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty()); @@ -593,8 +584,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag javaVersionOption.valueProperty().unbind(); // bind new data fields - FXUtils.bindInt(txtWidth, versionSetting.widthProperty()); - FXUtils.bindInt(txtHeight, versionSetting.heightProperty()); + FXUtils.bindWindowsSize(cboWindowsSize, versionSetting.widthProperty(), versionSetting.heightProperty()); maxMemory.bindBidirectional(versionSetting.maxMemoryProperty()); javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());