mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-08 03:15:17 -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.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
|
||||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
@ -359,12 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
vs = createLocalVersionSetting(id);
|
vs = createLocalVersionSetting(id);
|
||||||
if (vs == null)
|
if (vs == null)
|
||||||
return null;
|
return null;
|
||||||
VersionIconType versionIcon = vs.getVersionIcon();
|
|
||||||
if (vs.isUsesGlobal()) {
|
if (vs.isUsesGlobal()) {
|
||||||
PropertyUtils.copyProperties(profile.getGlobal(), vs);
|
|
||||||
vs.setUsesGlobal(false);
|
vs.setUsesGlobal(false);
|
||||||
}
|
}
|
||||||
vs.setVersionIcon(versionIcon); // versionIcon is preserved
|
|
||||||
return vs;
|
return vs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import javafx.application.Platform;
|
|||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.WeakInvalidationListener;
|
import javafx.beans.WeakInvalidationListener;
|
||||||
|
import javafx.beans.WeakListener;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.value.*;
|
import javafx.beans.value.*;
|
||||||
@ -79,6 +80,7 @@ import javax.xml.parsers.DocumentBuilder;
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -636,26 +638,100 @@ public final class FXUtils {
|
|||||||
checkBox.selectedProperty().unbindBidirectional(property);
|
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.
|
* 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.
|
* 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 comboBox the combo box being bound with {@code property}.
|
||||||
* @param property the property being bound with {@code combo box}.
|
* @param property the property being bound with {@code combo box}.
|
||||||
* @see #unbindEnum(JFXComboBox)
|
* @see #unbindEnum(JFXComboBox, Property)
|
||||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||||
*/
|
*/
|
||||||
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
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();
|
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||||
@SuppressWarnings("unchecked")
|
property.removeListener(binding);
|
||||||
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
|
|
||||||
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
|
|
||||||
|
|
||||||
comboBox.getSelectionModel().select(currentValue.ordinal());
|
comboBox.getSelectionModel().select(property.getValue());
|
||||||
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
|
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
|
||||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
property.addListener(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -666,11 +742,10 @@ public final class FXUtils {
|
|||||||
* @see #bindEnum(JFXComboBox, Property)
|
* @see #bindEnum(JFXComboBox, Property)
|
||||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||||
*/
|
*/
|
||||||
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
|
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||||
@SuppressWarnings("unchecked")
|
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||||
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
|
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||||
if (listener != null)
|
property.removeListener(binding);
|
||||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
|
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(txtWrapper, versionSetting.wrapperProperty());
|
||||||
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||||
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||||
FXUtils.unbindEnum(cboRenderer);
|
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
|
||||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
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.Lang;
|
||||||
import org.jackhuang.hmcl.util.Pair;
|
import org.jackhuang.hmcl.util.Pair;
|
||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
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.javafx.SafeStringConverter;
|
||||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
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 final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||||
private static TimerTask memoryStatusUpdateTask;
|
private static TimerTask memoryStatusUpdateTask;
|
||||||
|
|
||||||
private static void initMemoryStatusUpdateTask() {
|
private static void initMemoryStatusUpdateTask() {
|
||||||
FXUtils.checkFxUserThread();
|
FXUtils.checkFxUserThread();
|
||||||
if (memoryStatusUpdateTask != null)
|
if (memoryStatusUpdateTask != null)
|
||||||
@ -186,6 +188,30 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
|||||||
componentList = new ComponentList();
|
componentList = new ComponentList();
|
||||||
componentList.setDepth(1);
|
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<>();
|
javaItem = new MultiFileItem<>();
|
||||||
javaSublist = new ComponentSublist();
|
javaSublist = new ComponentSublist();
|
||||||
javaSublist.getContent().add(javaItem);
|
javaSublist.getContent().add(javaItem);
|
||||||
@ -442,7 +468,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
|||||||
showAdvancedSettingPane.setRight(button);
|
showAdvancedSettingPane.setRight(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentList.getContent().setAll(
|
componentList.getContent().addAll(
|
||||||
javaSublist,
|
javaSublist,
|
||||||
gameDirSublist,
|
gameDirSublist,
|
||||||
maxMemoryPane,
|
maxMemoryPane,
|
||||||
@ -527,8 +553,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
|||||||
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
|
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
|
||||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||||
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
||||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
|
||||||
FXUtils.unbindEnum(cboProcessPriority);
|
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
|
||||||
|
|
||||||
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
||||||
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
||||||
|
@ -1206,6 +1206,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
|
|||||||
settings.custom=Custom
|
settings.custom=Custom
|
||||||
|
|
||||||
settings.game=Settings
|
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.current=Game
|
||||||
settings.game.dimension=Resolution
|
settings.game.dimension=Resolution
|
||||||
settings.game.exploration=Explore
|
settings.game.exploration=Explore
|
||||||
|
@ -1003,6 +1003,9 @@ settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指
|
|||||||
settings.custom=自訂
|
settings.custom=自訂
|
||||||
|
|
||||||
settings.game=遊戲設定
|
settings.game=遊戲設定
|
||||||
|
settings.game.copy_global=複製全域遊戲設定
|
||||||
|
settings.game.copy_global.copy_all=複製全部
|
||||||
|
settings.game.copy_global.copy_all.confirm=你確定要覆寫目前實例特定遊戲設定嗎?該操作無法復原!
|
||||||
settings.game.current=遊戲
|
settings.game.current=遊戲
|
||||||
settings.game.dimension=遊戲介面解析度大小
|
settings.game.dimension=遊戲介面解析度大小
|
||||||
settings.game.exploration=瀏覽
|
settings.game.exploration=瀏覽
|
||||||
|
@ -1014,6 +1014,9 @@ settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命
|
|||||||
settings.custom=自定义
|
settings.custom=自定义
|
||||||
|
|
||||||
settings.game=游戏设置
|
settings.game=游戏设置
|
||||||
|
settings.game.copy_global=复制全局游戏设置
|
||||||
|
settings.game.copy_global.copy_all=复制全部
|
||||||
|
settings.game.copy_global.copy_all.confirm=你确定要覆盖当前版本特定游戏设置吗?此操作无法撤销!
|
||||||
settings.game.current=游戏
|
settings.game.current=游戏
|
||||||
settings.game.dimension=游戏窗口分辨率
|
settings.game.dimension=游戏窗口分辨率
|
||||||
settings.game.exploration=浏览
|
settings.game.exploration=浏览
|
||||||
|
@ -23,6 +23,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
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) {
|
public static void attachListener(Object instance, InvalidationListener listener) {
|
||||||
getPropertyHandleFactories(instance.getClass())
|
getPropertyHandleFactories(instance.getClass())
|
||||||
.forEach((name, factory) -> factory.apply(instance).observable.addListener(listener));
|
.forEach((name, factory) -> factory.apply(instance).observable.addListener(listener));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user