diff --git a/CHANGELOG.md b/CHANGELOG.md index c588aceb..9946438c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # 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 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. diff --git a/FEATURES.md b/FEATURES.md index ca28c417..1e77e9c5 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -117,7 +117,7 @@ If your PR makes notable changes to Winit's features, please update this section * Setting the taskbar icon * Setting the parent window * `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 * Window activation policy diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a040be36..05839d89 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -13,7 +13,7 @@ use crate::{ event_loop::EventLoop, monitor::MonitorHandle, 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. @@ -81,8 +81,8 @@ pub trait WindowExtWindows { /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn set_taskbar_icon(&self, taskbar_icon: Option); - /// Whether the system theme is currently Windows 10's "Dark Mode". - fn is_dark_mode(&self) -> bool; + /// Returns the current window theme. + fn theme(&self) -> Theme; } impl WindowExtWindows for Window { @@ -102,8 +102,8 @@ impl WindowExtWindows for Window { } #[inline] - fn is_dark_mode(&self) -> bool { - self.window.is_dark_mode() + fn theme(&self) -> Theme { + 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. /// 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; + + /// Forces a theme or uses the system settings if `None` was provided. + fn with_theme(self, theme: Option) -> WindowBuilder; } impl WindowBuilderExtWindows for WindowBuilder { @@ -151,6 +154,12 @@ impl WindowBuilderExtWindows for WindowBuilder { self.platform_specific.drag_and_drop = flag; self } + + #[inline] + fn with_theme(mut self, theme: Option) -> WindowBuilder { + self.platform_specific.preferred_theme = theme; + self + } } /// Additional methods on `MonitorHandle` that are specific to Windows. diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 3c7c16ee..a229e555 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -14,6 +14,8 @@ use winapi::{ um::{libloaderapi, uxtheme, winuser}, }; +use crate::window::Theme; + lazy_static! { static ref WIN10_BUILD_VERSION: Option = { // FIXME: RtlGetVersion is a documented windows API, @@ -70,11 +72,14 @@ lazy_static! { static ref LIGHT_THEME_NAME: Vec = widestring(""); } -/// Attempt to set dark mode on a window, if necessary. -/// Returns true if dark mode was set, false if not. -pub fn try_dark_mode(hwnd: HWND) -> bool { +/// 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 { 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 { 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()) }; - status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) - } else { - false + if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) { + return Theme::Dark; + } } + + Theme::Light } fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 37817cb5..a6794e4e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -36,7 +36,7 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, monitor::MonitorHandle as RootMonitorHandle, 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}, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, @@ -1874,20 +1874,20 @@ unsafe extern "system" fn public_window_callback( winuser::WM_SETTINGCHANGE => { use crate::event::WindowEvent::ThemeChanged; - let is_dark_mode = try_dark_mode(window); - let mut window_state = subclass_input.window_state.lock(); - let changed = window_state.is_dark_mode != is_dark_mode; + let preferred_theme = subclass_input.window_state.lock().preferred_theme; - if changed { - use crate::window::Theme::*; - let theme = if is_dark_mode { Dark } else { Light }; + if preferred_theme == None { + let new_theme = try_theme(window, preferred_theme); + let mut window_state = subclass_input.window_state.lock(); - window_state.is_dark_mode = is_dark_mode; - mem::drop(window_state); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ThemeChanged(theme), - }); + if window_state.current_theme != new_theme { + window_state.current_theme = new_theme; + mem::drop(window_state); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ThemeChanged(new_theme), + }); + } } commctrl::DefSubclassProc(window, msg, wparam, lparam) diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 498cdfb8..5b3f4e11 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -13,6 +13,7 @@ pub use self::icon::WinIcon as PlatformIcon; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; +use crate::window::Theme; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -20,6 +21,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, + pub preferred_theme: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -29,6 +31,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, + preferred_theme: None, } } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 592307b7..580fa958 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -34,7 +34,7 @@ use crate::{ icon::Icon, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ - dark_mode::try_dark_mode, + dark_mode::try_theme, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, @@ -43,7 +43,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, + window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -653,8 +653,8 @@ impl Window { } #[inline] - pub fn is_dark_mode(&self) -> bool { - self.window_state.lock().is_dark_mode + pub fn theme(&self) -> Theme { + self.window_state.lock().current_theme } } @@ -764,14 +764,15 @@ unsafe fn init( // 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 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 = WindowState::new( &attributes, pl_attribs.taskbar_icon, scale_factor, - dark_mode, + current_theme, + pl_attribs.preferred_theme, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9ea7c2da..1369fe72 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -3,7 +3,7 @@ use crate::{ event::ModifiersState, icon::Icon, platform_impl::platform::{event_loop, util}, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, Theme, WindowAttributes}, }; use parking_lot::MutexGuard; use std::{io, ptr}; @@ -31,7 +31,8 @@ pub struct WindowState { pub modifiers_state: ModifiersState, pub fullscreen: Option, - pub is_dark_mode: bool, + pub current_theme: Theme, + pub preferred_theme: Option, pub high_surrogate: Option, window_flags: WindowFlags, } @@ -101,7 +102,8 @@ impl WindowState { attributes: &WindowAttributes, taskbar_icon: Option, scale_factor: f64, - is_dark_mode: bool, + current_theme: Theme, + preferred_theme: Option, ) -> WindowState { WindowState { mouse: MouseProperties { @@ -122,7 +124,8 @@ impl WindowState { modifiers_state: ModifiersState::default(), fullscreen: None, - is_dark_mode, + current_theme, + preferred_theme, high_surrogate: None, window_flags: WindowFlags::empty(), } diff --git a/src/window.rs b/src/window.rs index b0951bc8..8cf6e066 100644 --- a/src/window.rs +++ b/src/window.rs @@ -886,7 +886,7 @@ pub enum Fullscreen { Borderless(Option), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Theme { Light, Dark,