diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fabe69c5d..ac12952c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Unreleased` header. - Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases. - **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent. - on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`. +- on Windows: add `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference` # 0.29.10 diff --git a/FEATURES.md b/FEATURES.md index 5855979c1f..f8f53fd98c 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -126,6 +126,10 @@ If your PR makes notable changes to Winit's features, please update this section * Setting a menu bar * `WS_EX_NOREDIRECTIONBITMAP` support * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme +* Setting the window border color +* Setting the title bar background color +* Setting the title color +* Setting the corner rounding preference ### macOS * Window activation policy diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 3a0428b5ba..3fdf244cf6 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -15,6 +15,60 @@ pub type HMENU = isize; /// Monitor Handle type used by Win32 API pub type HMONITOR = isize; +/// Describes a color used by Windows +#[repr(transparent)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Color(u32); + +impl Color { + /// Use the system's default color + pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF); + + //Special constant only valid for the window border and therefore modeled using Option for user facing code + const NONE: Color = Color(0xFFFFFFFE); + + /// Create a new color from the given RGB values + pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16)) + } +} + +impl Default for Color { + fn default() -> Self { + Self::SYSTEM_DEFAULT + } +} + +/// Describes how the corners of a window should look like. +/// +/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`]. +/// +/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference +#[repr(i32)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CornerPreference { + /// Corresponds to `DWMWCP_DEFAULT`. + /// + /// Let the system decide when to round window corners. + #[default] + Default = 0, + + /// Corresponds to `DWMWCP_DONOTROUND`. + /// + /// Never round window corners. + DoNotRound = 1, + + /// Corresponds to `DWMWCP_ROUND`. + /// + /// Round the corners, if appropriate. + Round = 2, + + /// Corresponds to `DWMWCP_ROUNDSMALL`. + /// + /// Round the corners if appropriate, with a small radius. + RoundSmall = 3, +} + /// Additional methods on `EventLoop` that are specific to Windows. pub trait EventLoopBuilderExtWindows { /// Whether to allow the event loop to be created off of the main thread. @@ -133,6 +187,26 @@ pub trait WindowExtWindows { /// /// Enabling the shadow causes a thin 1px line to appear on the top of the window. fn set_undecorated_shadow(&self, shadow: bool); + + /// Sets the color of the window border. + /// + /// Supported starting with Windows 11 Build 22000. + fn set_border_color(&self, color: Option); + + /// Sets the background color of the title bar. + /// + /// Supported starting with Windows 11 Build 22000. + fn set_title_background_color(&self, color: Option); + + /// Sets the color of the window title. + /// + /// Supported starting with Windows 11 Build 22000. + fn set_title_text_color(&self, color: Color); + + /// Sets the preferred style of the window corners. + /// + /// Supported starting with Windows 11 Build 22000. + fn set_corner_preference(&self, preference: CornerPreference); } impl WindowExtWindows for Window { @@ -155,6 +229,29 @@ impl WindowExtWindows for Window { fn set_undecorated_shadow(&self, shadow: bool) { self.window.set_undecorated_shadow(shadow) } + + #[inline] + fn set_border_color(&self, color: Option) { + self.window.set_border_color(color.unwrap_or(Color::NONE)) + } + + #[inline] + fn set_title_background_color(&self, color: Option) { + // The windows docs don't mention NONE as a valid options but it works in practice and is useful + // to circumvent the Windows option "Show accent color on title bars and window borders" + self.window + .set_title_background_color(color.unwrap_or(Color::NONE)) + } + + #[inline] + fn set_title_text_color(&self, color: Color) { + self.window.set_title_text_color(color) + } + + #[inline] + fn set_corner_preference(&self, preference: CornerPreference) { + self.window.set_corner_preference(preference) + } } /// Additional methods on `WindowBuilder` that are specific to Windows. @@ -217,6 +314,26 @@ pub trait WindowBuilderExtWindows { /// The shadow is hidden by default. /// Enabling the shadow causes a thin 1px line to appear on the top of the window. fn with_undecorated_shadow(self, shadow: bool) -> Self; + + /// Sets the color of the window border. + /// + /// Supported starting with Windows 11 Build 22000. + fn with_border_color(self, color: Option) -> Self; + + /// Sets the background color of the title bar. + /// + /// Supported starting with Windows 11 Build 22000. + fn with_title_background_color(self, color: Option) -> Self; + + /// Sets the color of the window title. + /// + /// Supported starting with Windows 11 Build 22000. + fn with_title_text_color(self, color: Color) -> Self; + + /// Sets the preferred style of the window corners. + /// + /// Supported starting with Windows 11 Build 22000. + fn with_corner_preference(self, corners: CornerPreference) -> Self; } impl WindowBuilderExtWindows for WindowBuilder { @@ -267,6 +384,30 @@ impl WindowBuilderExtWindows for WindowBuilder { self.window.platform_specific.decoration_shadow = shadow; self } + + #[inline] + fn with_border_color(mut self, color: Option) -> Self { + self.window.platform_specific.border_color = Some(color.unwrap_or(Color::NONE)); + self + } + + #[inline] + fn with_title_background_color(mut self, color: Option) -> Self { + self.window.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE)); + self + } + + #[inline] + fn with_title_text_color(mut self, color: Color) -> Self { + self.window.platform_specific.title_text_color = Some(color); + self + } + + #[inline] + fn with_corner_preference(mut self, corners: CornerPreference) -> Self { + self.window.platform_specific.corner_preference = Some(corners); + self + } } /// Additional methods on `MonitorHandle` that are specific to Windows. diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 43af623847..514d28a16b 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -25,6 +25,7 @@ use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; use crate::keyboard::Key; +use crate::platform::windows::{Color, CornerPreference}; #[derive(Clone, Debug)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -36,6 +37,10 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub skip_taskbar: bool, pub class_name: String, pub decoration_shadow: bool, + pub border_color: Option, + pub title_background_color: Option, + pub title_text_color: Option, + pub corner_preference: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -49,6 +54,10 @@ impl Default for PlatformSpecificWindowBuilderAttributes { skip_taskbar: false, class_name: "Window Class".to_string(), decoration_shadow: false, + border_color: None, + title_background_color: None, + title_text_color: None, + corner_preference: None, } } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 270b3fd1ac..1197e7cf81 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -14,7 +14,11 @@ use windows_sys::Win32::{ HWND, LPARAM, OLE_E_WRONGCOMPOBJ, POINT, POINTS, RECT, RPC_E_CHANGED_MODE, S_OK, WPARAM, }, Graphics::{ - Dwm::{DwmEnableBlurBehindWindow, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND}, + Dwm::{ + DwmEnableBlurBehindWindow, DwmSetWindowAttribute, DWMWA_BORDER_COLOR, + DWMWA_CAPTION_COLOR, DWMWA_TEXT_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, + DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND, DWM_WINDOW_CORNER_PREFERENCE, + }, Gdi::{ ChangeDisplaySettingsExW, ClientToScreen, CreateRectRgn, DeleteObject, InvalidateRgn, RedrawWindow, CDS_FULLSCREEN, DISP_CHANGE_BADFLAGS, DISP_CHANGE_BADMODE, @@ -56,6 +60,7 @@ use windows_sys::Win32::{ use log::warn; +use crate::platform::windows::{Color, CornerPreference}; use crate::{ cursor::Cursor, dpi::{PhysicalPosition, PhysicalSize, Position, Size}, @@ -1060,6 +1065,54 @@ impl Window { ); } } + + #[inline] + pub fn set_border_color(&self, color: Color) { + unsafe { + DwmSetWindowAttribute( + self.hwnd(), + DWMWA_BORDER_COLOR, + &color as *const _ as _, + mem::size_of::() as _, + ); + } + } + + #[inline] + pub fn set_title_background_color(&self, color: Color) { + unsafe { + DwmSetWindowAttribute( + self.hwnd(), + DWMWA_CAPTION_COLOR, + &color as *const _ as _, + mem::size_of::() as _, + ); + } + } + + #[inline] + pub fn set_title_text_color(&self, color: Color) { + unsafe { + DwmSetWindowAttribute( + self.hwnd(), + DWMWA_TEXT_COLOR, + &color as *const _ as _, + mem::size_of::() as _, + ); + } + } + + #[inline] + pub fn set_corner_preference(&self, preference: CornerPreference) { + unsafe { + DwmSetWindowAttribute( + self.hwnd(), + DWMWA_WINDOW_CORNER_PREFERENCE, + &(preference as DWM_WINDOW_CORNER_PREFERENCE) as *const _ as _, + mem::size_of::() as _, + ); + } + } } impl Drop for Window { @@ -1265,6 +1318,19 @@ impl<'a> InitData<'a> { if let Some(position) = attributes.position { win.set_outer_position(position); } + + if let Some(color) = self.attributes.platform_specific.border_color { + win.set_border_color(color); + } + if let Some(color) = self.attributes.platform_specific.title_background_color { + win.set_title_background_color(color); + } + if let Some(color) = self.attributes.platform_specific.title_text_color { + win.set_title_text_color(color); + } + if let Some(corner) = self.attributes.platform_specific.corner_preference { + win.set_corner_preference(corner); + } } } unsafe fn init(