win32: refresh title bar on Window::set_theme

This commit is contained in:
Tony 2025-08-23 20:55:54 +08:00 committed by GitHub
parent 317d62fb93
commit a4af50ec13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 37 additions and 12 deletions

View file

@ -3,13 +3,17 @@ use std::sync::LazyLock;
/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
use std::{ffi::c_void, ptr};
use windows_sys::core::PCSTR;
use windows_sys::Win32::Foundation::{BOOL, HWND, NTSTATUS, S_OK};
use windows_sys::core::{PCSTR, PCWSTR};
use windows_sys::w;
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, NTSTATUS, S_OK, WPARAM};
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA};
use windows_sys::Win32::UI::Controls::SetWindowTheme;
use windows_sys::Win32::UI::WindowsAndMessaging::{SystemParametersInfoA, SPI_GETHIGHCONTRAST};
use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
use windows_sys::Win32::UI::WindowsAndMessaging::{
DefWindowProcW, SystemParametersInfoA, SPI_GETHIGHCONTRAST, WM_NCACTIVATE,
};
use winit_core::window::Theme;
use super::util;
@ -51,13 +55,17 @@ static DARK_MODE_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
}
});
static DARK_THEME_NAME: LazyLock<Vec<u16>> =
LazyLock::new(|| util::encode_wide("DarkMode_Explorer"));
static LIGHT_THEME_NAME: LazyLock<Vec<u16>> = LazyLock::new(|| util::encode_wide(""));
const DARK_THEME_NAME: PCWSTR = w!("DarkMode_Explorer");
const LIGHT_THEME_NAME: PCWSTR = w!("");
/// Attempt to set a theme on a window, if necessary.
/// Returns the theme that was picked
pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
///
/// `refresh_title_bar` is only needed when the system doesn't do it by itself,
/// for cases like on window creation or system settings changes,
/// the system will refresh the title bar automatically,
/// if we always refresh the title bar, it will blink it on system settings changes
pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>, refresh_title_bar: bool) -> Theme {
if *DARK_MODE_SUPPORTED {
let is_dark_mode = match preferred_theme {
Some(theme) => theme == Theme::Dark,
@ -66,13 +74,16 @@ pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
let theme = if is_dark_mode { Theme::Dark } else { Theme::Light };
let theme_name = match theme {
Theme::Dark => DARK_THEME_NAME.as_ptr(),
Theme::Light => LIGHT_THEME_NAME.as_ptr(),
Theme::Dark => DARK_THEME_NAME,
Theme::Light => LIGHT_THEME_NAME,
};
let status = unsafe { SetWindowTheme(hwnd, theme_name, ptr::null()) };
if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
if refresh_title_bar {
unsafe { refresh_titlebar_theme_color(hwnd) };
}
return theme;
}
}
@ -123,6 +134,18 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
}
}
unsafe fn refresh_titlebar_theme_color(hwnd: HWND) {
unsafe {
if GetActiveWindow() == hwnd {
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default());
DefWindowProcW(hwnd, WM_NCACTIVATE, true as _, LPARAM::default());
} else {
DefWindowProcW(hwnd, WM_NCACTIVATE, true as _, LPARAM::default());
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default());
}
}
}
pub fn should_use_dark_mode() -> bool {
should_apps_use_dark_mode() && !is_high_contrast()
}

View file

@ -2467,7 +2467,7 @@ unsafe fn public_window_callback_inner(
let preferred_theme = userdata.window_state_lock().preferred_theme;
if preferred_theme.is_none() {
let new_theme = try_theme(window, preferred_theme);
let new_theme = try_theme(window, preferred_theme, false);
let mut window_state = userdata.window_state_lock();
if window_state.current_theme != new_theme {

View file

@ -1099,7 +1099,7 @@ impl CoreWindow for Window {
}
fn set_theme(&self, theme: Option<Theme>) {
try_theme(self.window.hwnd(), theme);
self.window_state_lock().current_theme = try_theme(self.window.hwnd(), theme, true);
}
fn theme(&self) -> Option<Theme> {
@ -1196,7 +1196,7 @@ impl InitData<'_> {
// If the system theme is dark, we need to set the window theme now
// before we update the window flags (and possibly show the
// window for the first time).
let current_theme = try_theme(window, self.attributes.preferred_theme);
let current_theme = try_theme(window, self.attributes.preferred_theme, false);
let window_state = {
let window_state = WindowState::new(

View file

@ -257,3 +257,5 @@ changelog entry.
- On macOS, don't panic on monitors with unknown bit-depths.
- On macOS, fixed crash when closing the window on macOS 26+.
- On Windows, account for mouse wheel lines per scroll setting for `WindowEvent::MouseWheel`.
- On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`.
- On Windows, `Window::set_theme` will change the title bar color immediately now.