mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-08-03 19:36:53 -04:00
在游戏设置中添加“复制全局游戏设置”选项 (#3628)
* update * update * Fix checkstyle * update * fix checkstyle * Update HMCL/src/main/resources/assets/lang/I18N.properties Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com> * Update HMCL/src/main/resources/assets/lang/I18N_zh.properties Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com> * Fix FXUtils.bindEnum --------- Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
This commit is contained in:
parent
fce08a6923
commit
a24f8e2ec0
@ -38,7 +38,6 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
@ -359,12 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs = createLocalVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
VersionIconType versionIcon = vs.getVersionIcon();
|
||||
if (vs.isUsesGlobal()) {
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), vs);
|
||||
vs.setUsesGlobal(false);
|
||||
}
|
||||
vs.setVersionIcon(versionIcon); // versionIcon is preserved
|
||||
return vs;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.*;
|
||||
@ -79,6 +80,7 @@ import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -636,26 +638,100 @@ public final class FXUtils {
|
||||
checkBox.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {
|
||||
private final WeakReference<JFXComboBox<E>> comboBoxRef;
|
||||
private final WeakReference<Property<E>> propertyRef;
|
||||
private final int hashCode;
|
||||
|
||||
private boolean updating = false;
|
||||
|
||||
private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {
|
||||
this.comboBoxRef = new WeakReference<>(comboBox);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
this.hashCode = System.identityHashCode(comboBox) ^ System.identityHashCode(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable sourceProperty) {
|
||||
if (!updating) {
|
||||
final JFXComboBox<E> comboBox = comboBoxRef.get();
|
||||
final Property<E> property = propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null) {
|
||||
if (comboBox != null) {
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(this);
|
||||
}
|
||||
|
||||
if (property != null) {
|
||||
property.removeListener(this);
|
||||
}
|
||||
} else {
|
||||
updating = true;
|
||||
try {
|
||||
if (property == sourceProperty) {
|
||||
E newValue = property.getValue();
|
||||
comboBox.getSelectionModel().select(newValue);
|
||||
} else {
|
||||
E newValue = comboBox.getSelectionModel().getSelectedItem();
|
||||
property.setValue(newValue);
|
||||
}
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasGarbageCollected() {
|
||||
return comboBoxRef.get() == null || propertyRef.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof EnumBidirectionalBinding))
|
||||
return false;
|
||||
|
||||
EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;
|
||||
|
||||
final JFXComboBox<E> comboBox = this.comboBoxRef.get();
|
||||
final Property<E> property = this.propertyRef.get();
|
||||
|
||||
final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();
|
||||
final Property<?> thatProperty = that.propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)
|
||||
return false;
|
||||
|
||||
return comboBox == thatComboBox && property == thatProperty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind combo box selection with given enum property bidirectionally.
|
||||
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
|
||||
*
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @see #unbindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
unbindEnum(comboBox);
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
|
||||
T currentValue = property.getValue();
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
|
||||
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
|
||||
comboBox.getSelectionModel().select(currentValue.ordinal());
|
||||
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
||||
comboBox.getSelectionModel().select(property.getValue());
|
||||
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
|
||||
property.addListener(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -666,11 +742,10 @@ public final class FXUtils {
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
|
||||
if (listener != null)
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
}
|
||||
|
||||
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
|
||||
|
@ -240,7 +240,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());
|
||||
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||
FXUtils.unbindEnum(cboRenderer);
|
||||
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
|
@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
@ -65,6 +66,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
private static final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private static TimerTask memoryStatusUpdateTask;
|
||||
|
||||
private static void initMemoryStatusUpdateTask() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (memoryStatusUpdateTask != null)
|
||||
@ -186,6 +188,30 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
componentList = new ComponentList();
|
||||
componentList.setDepth(1);
|
||||
|
||||
if (!globalSetting) {
|
||||
BorderPane copyGlobalPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("settings.game.copy_global"));
|
||||
copyGlobalPane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
JFXButton button = new JFXButton(i18n("settings.game.copy_global.copy_all"));
|
||||
copyGlobalPane.setRight(button);
|
||||
button.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
|
||||
Set<String> ignored = new HashSet<>(Arrays.asList(
|
||||
"usesGlobal",
|
||||
"versionIcon"
|
||||
));
|
||||
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
|
||||
}, null));
|
||||
button.getStyleClass().add("jfx-button-border");
|
||||
BorderPane.setAlignment(button, Pos.CENTER_RIGHT);
|
||||
}
|
||||
|
||||
componentList.getContent().add(copyGlobalPane);
|
||||
}
|
||||
|
||||
javaItem = new MultiFileItem<>();
|
||||
javaSublist = new ComponentSublist();
|
||||
javaSublist.getContent().add(javaItem);
|
||||
@ -442,7 +468,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
showAdvancedSettingPane.setRight(button);
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(
|
||||
componentList.getContent().addAll(
|
||||
javaSublist,
|
||||
gameDirSublist,
|
||||
maxMemoryPane,
|
||||
@ -527,8 +553,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
|
||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
FXUtils.unbindEnum(cboProcessPriority);
|
||||
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
|
||||
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
||||
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
||||
|
@ -1206,6 +1206,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
|
||||
settings.custom=Custom
|
||||
|
||||
settings.game=Settings
|
||||
settings.game.copy_global=Copy from Global Settings
|
||||
settings.game.copy_global.copy_all=Copy All
|
||||
settings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!
|
||||
settings.game.current=Game
|
||||
settings.game.dimension=Resolution
|
||||
settings.game.exploration=Explore
|
||||
|
@ -1003,6 +1003,9 @@ settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指
|
||||
settings.custom=自訂
|
||||
|
||||
settings.game=遊戲設定
|
||||
settings.game.copy_global=複製全域遊戲設定
|
||||
settings.game.copy_global.copy_all=複製全部
|
||||
settings.game.copy_global.copy_all.confirm=你確定要覆寫目前實例特定遊戲設定嗎?該操作無法復原!
|
||||
settings.game.current=遊戲
|
||||
settings.game.dimension=遊戲介面解析度大小
|
||||
settings.game.exploration=瀏覽
|
||||
|
@ -1014,6 +1014,9 @@ settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命
|
||||
settings.custom=自定义
|
||||
|
||||
settings.game=游戏设置
|
||||
settings.game.copy_global=复制全局游戏设置
|
||||
settings.game.copy_global.copy_all=复制全部
|
||||
settings.game.copy_global.copy_all.confirm=你确定要覆盖当前版本特定游戏设置吗?此操作无法撤销!
|
||||
settings.game.current=游戏
|
||||
settings.game.dimension=游戏窗口分辨率
|
||||
settings.game.exploration=浏览
|
||||
|
@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@ -158,6 +159,21 @@ public final class PropertyUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyProperties(Object from, Object to, Predicate<String> predicate) {
|
||||
Class<?> type = from.getClass();
|
||||
while (!type.isInstance(to))
|
||||
type = type.getSuperclass();
|
||||
|
||||
getPropertyHandleFactories(type)
|
||||
.forEach((name, factory) -> {
|
||||
if (predicate.test(name)) {
|
||||
PropertyHandle src = factory.apply(from);
|
||||
PropertyHandle target = factory.apply(to);
|
||||
target.accessor.setValue(src.accessor.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void attachListener(Object instance, InvalidationListener listener) {
|
||||
getPropertyHandleFactories(instance.getClass())
|
||||
.forEach((name, factory) -> factory.apply(instance).observable.addListener(listener));
|
||||
|
Loading…
x
Reference in New Issue
Block a user