在 Windows 平台上 GameCrashWindow 标题栏应当跟随深色模式设置 (#5319)

This commit is contained in:
Glavo
2026-01-25 03:27:39 +08:00
committed by GitHub
parent f7987a91ce
commit 179ccb7805
3 changed files with 49 additions and 40 deletions

View File

@@ -17,6 +17,7 @@
*/ */
package org.jackhuang.hmcl.theme; package org.jackhuang.hmcl.theme;
import com.sun.jna.Pointer;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
@@ -24,7 +25,10 @@ import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.ObjectExpression; import javafx.beans.binding.ObjectExpression;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.glavo.monetfx.Brightness; import org.glavo.monetfx.Brightness;
import org.glavo.monetfx.ColorScheme; import org.glavo.monetfx.ColorScheme;
import org.glavo.monetfx.Contrast; import org.glavo.monetfx.Contrast;
@@ -32,14 +36,17 @@ import org.glavo.monetfx.beans.property.ColorSchemeProperty;
import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty; import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty;
import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty; import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.WindowsNativeUtils;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jackhuang.hmcl.util.platform.OSVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jackhuang.hmcl.util.platform.SystemUtils;
import org.jackhuang.hmcl.util.platform.windows.Dwmapi;
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
import org.jackhuang.hmcl.util.platform.windows.WinReg; import org.jackhuang.hmcl.util.platform.windows.WinReg;
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.setting.ConfigHolder.config;
@@ -160,6 +167,39 @@ public final class Themes {
return darkMode; return darkMode;
} }
public static void applyNativeDarkMode(Stage stage) {
if (OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_11) && NativeUtils.USE_JNA && Dwmapi.INSTANCE != null) {
ChangeListener<Boolean> listener = FXUtils.onWeakChange(Themes.darkModeProperty(), darkMode -> {
if (stage.isShowing()) {
WindowsNativeUtils.getWindowHandle(stage).ifPresent(handle -> {
if (handle == WinTypes.HANDLE.INVALID_VALUE)
return;
Dwmapi.INSTANCE.DwmSetWindowAttribute(
new WinTypes.HANDLE(Pointer.createConstant(handle)),
WinConstants.DWMWA_USE_IMMERSIVE_DARK_MODE,
new WinTypes.BOOLByReference(new WinTypes.BOOL(darkMode)),
WinTypes.BOOL.SIZE
);
});
}
});
stage.getProperties().put("Themes.applyNativeDarkMode.listener", listener);
if (stage.isShowing()) {
listener.changed(null, false, Themes.darkModeProperty().get());
} else {
stage.addEventFilter(WindowEvent.WINDOW_SHOWN, new EventHandler<>() {
@Override
public void handle(WindowEvent event) {
stage.removeEventFilter(WindowEvent.WINDOW_SHOWN, this);
listener.changed(null, false, Themes.darkModeProperty().get());
}
});
}
}
}
private Themes() { private Themes() {
} }
} }

View File

@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.launch.ProcessListener;
import org.jackhuang.hmcl.setting.StyleSheets; import org.jackhuang.hmcl.setting.StyleSheets;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
@@ -90,6 +91,8 @@ public class GameCrashWindow extends Stage {
private final List<Log> logs; private final List<Log> logs;
public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, List<Log> logs) { public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, List<Log> logs) {
Themes.applyNativeDarkMode(this);
this.managedProcess = managedProcess; this.managedProcess = managedProcess;
this.exitType = exitType; this.exitType = exitType;
this.repository = repository; this.repository = repository;

View File

@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXListView;
import com.sun.jna.Pointer;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener; import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
@@ -29,7 +28,6 @@ import javafx.beans.property.*;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.css.PseudoClass; import javafx.css.PseudoClass;
import javafx.event.EventHandler;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Scene; import javafx.scene.Scene;
@@ -38,7 +36,6 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.jackhuang.hmcl.game.GameDumpGenerator; import org.jackhuang.hmcl.game.GameDumpGenerator;
import org.jackhuang.hmcl.game.Log; import org.jackhuang.hmcl.game.Log;
import org.jackhuang.hmcl.setting.StyleSheets; import org.jackhuang.hmcl.setting.StyleSheets;
@@ -48,9 +45,6 @@ import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.platform.*; import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.platform.windows.Dwmapi;
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -87,41 +81,13 @@ public final class LogWindow extends Stage {
private final LogWindowImpl impl; private final LogWindowImpl impl;
private final ManagedProcess gameProcess; private final ManagedProcess gameProcess;
@SuppressWarnings("unused")
private Object windowsDarkModeListenerHolder;
{
if (OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_11) && NativeUtils.USE_JNA && Dwmapi.INSTANCE != null) {
this.addEventFilter(WindowEvent.WINDOW_SHOWN, new EventHandler<>() {
@Override
public void handle(WindowEvent event) {
LogWindow.this.removeEventFilter(WindowEvent.WINDOW_SHOWN, this);
windowsDarkModeListenerHolder = FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {
if (LogWindow.this.isShowing()) {
WindowsNativeUtils.getWindowHandle(LogWindow.this).ifPresent(handle -> {
if (handle == WinTypes.HANDLE.INVALID_VALUE)
return;
Dwmapi.INSTANCE.DwmSetWindowAttribute(
new WinTypes.HANDLE(Pointer.createConstant(handle)),
WinConstants.DWMWA_USE_IMMERSIVE_DARK_MODE,
new WinTypes.BOOLByReference(new WinTypes.BOOL(darkMode)),
WinTypes.BOOL.SIZE
);
});
}
});
}
});
}
}
public LogWindow(ManagedProcess gameProcess) { public LogWindow(ManagedProcess gameProcess) {
this(gameProcess, new CircularArrayList<>()); this(gameProcess, new CircularArrayList<>());
} }
public LogWindow(ManagedProcess gameProcess, CircularArrayList<Log> logs) { public LogWindow(ManagedProcess gameProcess, CircularArrayList<Log> logs) {
Themes.applyNativeDarkMode(this);
this.logs = logs; this.logs = logs;
this.impl = new LogWindowImpl(); this.impl = new LogWindowImpl();
setScene(new Scene(impl, 800, 480)); setScene(new Scene(impl, 800, 480));