Ability to force a theme on Windows (#1666)

This commit is contained in:
Viktor Zoutman 2020-11-30 19:04:26 +01:00 committed by GitHub
parent 5700359a61
commit 6ddee9a8ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 63 additions and 37 deletions

View file

@ -1,5 +1,8 @@
# Unreleased # Unreleased
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
- On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag.
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.

View file

@ -117,7 +117,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the taskbar icon * Setting the taskbar icon
* Setting the parent window * Setting the parent window
* `WS_EX_NOREDIRECTIONBITMAP` support * `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
### macOS ### macOS
* Window activation policy * Window activation policy

View file

@ -13,7 +13,7 @@ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::MonitorHandle, monitor::MonitorHandle,
platform_impl::{EventLoop as WindowsEventLoop, WinIcon}, platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
window::{BadIcon, Icon, Window, WindowBuilder}, window::{BadIcon, Icon, Theme, Window, WindowBuilder},
}; };
/// Additional methods on `EventLoop` that are specific to Windows. /// Additional methods on `EventLoop` that are specific to Windows.
@ -81,8 +81,8 @@ pub trait WindowExtWindows {
/// This sets `ICON_BIG`. A good ceiling here is 256x256. /// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>); fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Whether the system theme is currently Windows 10's "Dark Mode". /// Returns the current window theme.
fn is_dark_mode(&self) -> bool; fn theme(&self) -> Theme;
} }
impl WindowExtWindows for Window { impl WindowExtWindows for Window {
@ -102,8 +102,8 @@ impl WindowExtWindows for Window {
} }
#[inline] #[inline]
fn is_dark_mode(&self) -> bool { fn theme(&self) -> Theme {
self.window.is_dark_mode() self.window.theme()
} }
} }
@ -125,6 +125,9 @@ pub trait WindowBuilderExtWindows {
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. /// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information. /// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
/// Forces a theme or uses the system settings if `None` was provided.
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
} }
impl WindowBuilderExtWindows for WindowBuilder { impl WindowBuilderExtWindows for WindowBuilder {
@ -151,6 +154,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
self.platform_specific.drag_and_drop = flag; self.platform_specific.drag_and_drop = flag;
self self
} }
#[inline]
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}
} }
/// Additional methods on `MonitorHandle` that are specific to Windows. /// Additional methods on `MonitorHandle` that are specific to Windows.

View file

@ -14,6 +14,8 @@ use winapi::{
um::{libloaderapi, uxtheme, winuser}, um::{libloaderapi, uxtheme, winuser},
}; };
use crate::window::Theme;
lazy_static! { lazy_static! {
static ref WIN10_BUILD_VERSION: Option<DWORD> = { static ref WIN10_BUILD_VERSION: Option<DWORD> = {
// FIXME: RtlGetVersion is a documented windows API, // FIXME: RtlGetVersion is a documented windows API,
@ -70,11 +72,14 @@ lazy_static! {
static ref LIGHT_THEME_NAME: Vec<u16> = widestring(""); static ref LIGHT_THEME_NAME: Vec<u16> = widestring("");
} }
/// Attempt to set dark mode on a window, if necessary. /// Attempt to set a theme on a window, if necessary.
/// Returns true if dark mode was set, false if not. /// Returns the theme that was picked
pub fn try_dark_mode(hwnd: HWND) -> bool { pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
if *DARK_MODE_SUPPORTED { if *DARK_MODE_SUPPORTED {
let is_dark_mode = should_use_dark_mode(); let is_dark_mode = match preferred_theme {
Some(theme) => theme == Theme::Dark,
None => should_use_dark_mode(),
};
let theme_name = if is_dark_mode { let theme_name = if is_dark_mode {
DARK_THEME_NAME.as_ptr() DARK_THEME_NAME.as_ptr()
@ -84,10 +89,12 @@ pub fn try_dark_mode(hwnd: HWND) -> bool {
let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) }; let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) };
status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
} else { return Theme::Dark;
false }
} }
Theme::Light
} }
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {

View file

@ -36,7 +36,7 @@ use crate::{
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{ platform_impl::platform::{
dark_mode::try_dark_mode, dark_mode::try_theme,
dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling},
drop_handler::FileDropHandler, drop_handler::FileDropHandler,
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
@ -1874,20 +1874,20 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
winuser::WM_SETTINGCHANGE => { winuser::WM_SETTINGCHANGE => {
use crate::event::WindowEvent::ThemeChanged; use crate::event::WindowEvent::ThemeChanged;
let is_dark_mode = try_dark_mode(window); let preferred_theme = subclass_input.window_state.lock().preferred_theme;
let mut window_state = subclass_input.window_state.lock();
let changed = window_state.is_dark_mode != is_dark_mode;
if changed { if preferred_theme == None {
use crate::window::Theme::*; let new_theme = try_theme(window, preferred_theme);
let theme = if is_dark_mode { Dark } else { Light }; let mut window_state = subclass_input.window_state.lock();
window_state.is_dark_mode = is_dark_mode; if window_state.current_theme != new_theme {
mem::drop(window_state); window_state.current_theme = new_theme;
subclass_input.send_event(Event::WindowEvent { mem::drop(window_state);
window_id: RootWindowId(WindowId(window)), subclass_input.send_event(Event::WindowEvent {
event: ThemeChanged(theme), window_id: RootWindowId(WindowId(window)),
}); event: ThemeChanged(new_theme),
});
}
} }
commctrl::DefSubclassProc(window, msg, wparam, lparam) commctrl::DefSubclassProc(window, msg, wparam, lparam)

View file

@ -13,6 +13,7 @@ pub use self::icon::WinIcon as PlatformIcon;
use crate::event::DeviceId as RootDeviceId; use crate::event::DeviceId as RootDeviceId;
use crate::icon::Icon; use crate::icon::Icon;
use crate::window::Theme;
#[derive(Clone)] #[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
@ -20,6 +21,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub taskbar_icon: Option<Icon>, pub taskbar_icon: Option<Icon>,
pub no_redirection_bitmap: bool, pub no_redirection_bitmap: bool,
pub drag_and_drop: bool, pub drag_and_drop: bool,
pub preferred_theme: Option<Theme>,
} }
impl Default for PlatformSpecificWindowBuilderAttributes { impl Default for PlatformSpecificWindowBuilderAttributes {
@ -29,6 +31,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
taskbar_icon: None, taskbar_icon: None,
no_redirection_bitmap: false, no_redirection_bitmap: false,
drag_and_drop: true, drag_and_drop: true,
preferred_theme: None,
} }
} }
} }

View file

@ -34,7 +34,7 @@ use crate::{
icon::Icon, icon::Icon,
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{ platform_impl::platform::{
dark_mode::try_dark_mode, dark_mode::try_theme,
dpi::{dpi_to_scale_factor, hwnd_dpi}, dpi::{dpi_to_scale_factor, hwnd_dpi},
drop_handler::FileDropHandler, drop_handler::FileDropHandler,
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
@ -43,7 +43,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId, PlatformSpecificWindowBuilderAttributes, WindowId,
}, },
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes},
}; };
/// The Win32 implementation of the main `Window` object. /// The Win32 implementation of the main `Window` object.
@ -653,8 +653,8 @@ impl Window {
} }
#[inline] #[inline]
pub fn is_dark_mode(&self) -> bool { pub fn theme(&self) -> Theme {
self.window_state.lock().is_dark_mode self.window_state.lock().current_theme
} }
} }
@ -764,14 +764,15 @@ unsafe fn init<T: 'static>(
// If the system theme is dark, we need to set the window theme now // If the system theme is dark, we need to set the window theme now
// before we update the window flags (and possibly show the // before we update the window flags (and possibly show the
// window for the first time). // window for the first time).
let dark_mode = try_dark_mode(real_window.0); let current_theme = try_theme(real_window.0, pl_attribs.preferred_theme);
let window_state = { let window_state = {
let window_state = WindowState::new( let window_state = WindowState::new(
&attributes, &attributes,
pl_attribs.taskbar_icon, pl_attribs.taskbar_icon,
scale_factor, scale_factor,
dark_mode, current_theme,
pl_attribs.preferred_theme,
); );
let window_state = Arc::new(Mutex::new(window_state)); let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);

View file

@ -3,7 +3,7 @@ use crate::{
event::ModifiersState, event::ModifiersState,
icon::Icon, icon::Icon,
platform_impl::platform::{event_loop, util}, platform_impl::platform::{event_loop, util},
window::{CursorIcon, Fullscreen, WindowAttributes}, window::{CursorIcon, Fullscreen, Theme, WindowAttributes},
}; };
use parking_lot::MutexGuard; use parking_lot::MutexGuard;
use std::{io, ptr}; use std::{io, ptr};
@ -31,7 +31,8 @@ pub struct WindowState {
pub modifiers_state: ModifiersState, pub modifiers_state: ModifiersState,
pub fullscreen: Option<Fullscreen>, pub fullscreen: Option<Fullscreen>,
pub is_dark_mode: bool, pub current_theme: Theme,
pub preferred_theme: Option<Theme>,
pub high_surrogate: Option<u16>, pub high_surrogate: Option<u16>,
window_flags: WindowFlags, window_flags: WindowFlags,
} }
@ -101,7 +102,8 @@ impl WindowState {
attributes: &WindowAttributes, attributes: &WindowAttributes,
taskbar_icon: Option<Icon>, taskbar_icon: Option<Icon>,
scale_factor: f64, scale_factor: f64,
is_dark_mode: bool, current_theme: Theme,
preferred_theme: Option<Theme>,
) -> WindowState { ) -> WindowState {
WindowState { WindowState {
mouse: MouseProperties { mouse: MouseProperties {
@ -122,7 +124,8 @@ impl WindowState {
modifiers_state: ModifiersState::default(), modifiers_state: ModifiersState::default(),
fullscreen: None, fullscreen: None,
is_dark_mode, current_theme,
preferred_theme,
high_surrogate: None, high_surrogate: None,
window_flags: WindowFlags::empty(), window_flags: WindowFlags::empty(),
} }

View file

@ -886,7 +886,7 @@ pub enum Fullscreen {
Borderless(Option<MonitorHandle>), Borderless(Option<MonitorHandle>),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Theme { pub enum Theme {
Light, Light,
Dark, Dark,