From 07c25b97035d1a319532cce3c5befa636f732b01 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 23 Mar 2025 20:02:07 +0300 Subject: [PATCH] icon: refactor `Icon` to be `dyn` Same as for `CustomCursor`. However, the API uses `dyn` stuff only because of `Windows` backend at the time of writing, generally, platforms should just have a separate method that deals with all of that, and e.g. top-level winit only guarantees `Rgba`. --- examples/application.rs | 3 +- src/changelog/unreleased.md | 8 +- src/icon.rs | 104 +++++++++---------- src/lib.rs | 2 +- src/platform/windows.rs | 79 ++++++++------- src/platform_impl/android/mod.rs | 1 - src/platform_impl/apple/appkit/mod.rs | 1 - src/platform_impl/apple/uikit/mod.rs | 1 - src/platform_impl/linux/mod.rs | 1 - src/platform_impl/linux/x11/window.rs | 19 ++-- src/platform_impl/orbital/mod.rs | 1 - src/platform_impl/web/mod.rs | 1 - src/platform_impl/windows/icon.rs | 138 +++++++++++--------------- src/platform_impl/windows/mod.rs | 4 +- src/platform_impl/windows/window.rs | 76 ++++++++++---- src/window.rs | 2 +- 16 files changed, 230 insertions(+), 211 deletions(-) diff --git a/examples/application.rs b/examples/application.rs index d0346821..931c8366 100644 --- a/examples/application.rs +++ b/examples/application.rs @@ -20,6 +20,7 @@ use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::error::RequestError; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::icon::RgbaIcon; use winit::keyboard::{Key, ModifiersState}; use winit::monitor::Fullscreen; #[cfg(macos_platform)] @@ -1156,7 +1157,7 @@ fn load_icon(bytes: &[u8]) -> Icon { let rgba = image.into_raw(); (rgba, width, height) }; - Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") + RgbaIcon::new(icon_rgba, icon_width, icon_height).expect("Failed to open icon").into() } fn modifiers_to_string(mods: ModifiersState) -> String { diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 21480289..6cb7a036 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -76,6 +76,8 @@ changelog entry. - On X11, set an "area" attribute on XIM input connection to convey the cursor area. - Implement `CustomCursorProvider` for `CustomCursor` to access cursor API. - Add `CustomCursorSource::Url`, `CustomCursorSource::from_animation`. +- Implement `CustomIconProvider` for `RgbaIcon`. +- Add `icon` module that exposes winit's icon API. ### Changed @@ -196,6 +198,7 @@ changelog entry. - Move `window::Fullscreen` to `monitor::Fullscreen`. - Renamed "super" key to "meta", to match the naming in the W3C specification. `NamedKey::Super` still exists, but it's non-functional and deprecated, `NamedKey::Meta` should be used instead. +- Move `IconExtWindows` into `WinIcon`. ### Removed @@ -230,9 +233,10 @@ changelog entry. - Remove `Window::inner_position`, use the new `Window::surface_position` instead. - Remove `CustomCursorExtWeb`, use the `CustomCursorSource`. - Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead. -- Removed `ApplicationHandler::exited`, the event loop being shut down can now be listened to in +- Remove `ApplicationHandler::exited`, the event loop being shut down can now be listened to in the `Drop` impl on the application handler. -- Removed `NamedKey::Space`, match on `Key::Character(" ")` instead. +- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead. +- Remove `PartialEq` impl for `WindowAttributes`. ### Fixed diff --git a/src/icon.rs b/src/icon.rs index f0f611cd..9aa3ec72 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -1,7 +1,20 @@ use std::error::Error; +use std::sync::Arc; use std::{fmt, io, mem}; -use crate::platform_impl::PlatformIcon; +use crate::utils::{impl_dyn_casting, AsAny}; + +pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); + +/// An icon used for the window titlebar, taskbar, etc. +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct Icon(pub(crate) Arc); + +// TODO remove that once split. +pub trait IconProvider: AsAny + fmt::Debug + Send + Sync {} + +impl_dyn_casting!(IconProvider); #[repr(C)] #[derive(Debug)] @@ -12,10 +25,8 @@ pub(crate) struct Pixel { pub(crate) a: u8, } -pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); - #[derive(Debug)] -/// An error produced when using [`Icon::from_rgba`] with invalid arguments. +/// An error produced when using [`RgbaIcon::new`] with invalid arguments. pub enum BadIcon { /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// safely interpreted as 32bpp RGBA pixels. @@ -50,69 +61,48 @@ impl fmt::Display for BadIcon { impl Error for BadIcon {} -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct RgbaIcon { - pub(crate) rgba: Vec, +#[derive(Debug, Clone)] +pub struct RgbaIcon { pub(crate) width: u32, pub(crate) height: u32, + pub(crate) rgba: Vec, } -/// For platforms which don't have window icons (e.g. Web) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct NoIcon; - -#[allow(dead_code)] // These are not used on every platform -mod constructors { - use super::*; - - impl RgbaIcon { - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - if rgba.len() % PIXEL_SIZE != 0 { - return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() }); - } - let pixel_count = rgba.len() / PIXEL_SIZE; - if pixel_count != (width * height) as usize { - Err(BadIcon::DimensionsVsPixelCount { - width, - height, - width_x_height: (width * height) as usize, - pixel_count, - }) - } else { - Ok(RgbaIcon { rgba, width, height }) - } +impl RgbaIcon { + pub fn new(rgba: Vec, width: u32, height: u32) -> Result { + if rgba.len() % PIXEL_SIZE != 0 { + return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() }); + } + let pixel_count = rgba.len() / PIXEL_SIZE; + if pixel_count != (width * height) as usize { + Err(BadIcon::DimensionsVsPixelCount { + width, + height, + width_x_height: (width * height) as usize, + pixel_count, + }) + } else { + Ok(RgbaIcon { rgba, width, height }) } } - impl NoIcon { - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - // Create the rgba icon anyway to validate the input - let _ = RgbaIcon::from_rgba(rgba, width, height)?; - Ok(NoIcon) - } + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn buffer(&self) -> &[u8] { + self.rgba.as_slice() } } -/// An icon used for the window titlebar, taskbar, etc. -#[derive(Clone, Eq, Hash, PartialEq)] -pub struct Icon { - pub(crate) inner: PlatformIcon, -} +impl IconProvider for RgbaIcon {} -impl fmt::Debug for Icon { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt::Debug::fmt(&self.inner, formatter) - } -} - -impl Icon { - /// Creates an icon from 32bpp RGBA data. - /// - /// The length of `rgba` must be divisible by 4, and `width * height` must equal - /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered(); - - Ok(Icon { inner: PlatformIcon::from_rgba(rgba, width, height)? }) +impl From for Icon { + fn from(value: RgbaIcon) -> Self { + Self(Arc::new(value)) } } diff --git a/src/lib.rs b/src/lib.rs index b5a30876..31edbb39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -302,7 +302,7 @@ pub mod error; mod cursor; pub mod event; pub mod event_loop; -mod icon; +pub mod icon; pub mod keyboard; pub mod monitor; mod platform_impl; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 7357040f..02a7df2b 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -6,6 +6,7 @@ use std::borrow::Borrow; use std::ffi::c_void; use std::ops::Deref; use std::path::Path; +use std::sync::Arc; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -15,7 +16,9 @@ use windows_sys::Win32::Foundation::HANDLE; use crate::dpi::PhysicalSize; use crate::event::DeviceId; use crate::event_loop::EventLoopBuilder; -use crate::window::{BadIcon, Icon, Window, WindowAttributes}; +use crate::icon::BadIcon; +use crate::platform_impl::RaiiIcon; +use crate::window::{Icon, Window, WindowAttributes}; /// Window Handle type used by Win32 API pub type HWND = *mut c_void; @@ -637,7 +640,7 @@ impl DeviceIdExtWindows for DeviceId { } } -/// Additional methods on `Icon` that are specific to Windows. +/// Windows specific `Icon`. /// /// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files). /// @@ -649,7 +652,12 @@ impl DeviceIdExtWindows for DeviceId { /// `filename` is the name of the file that contains the resource. /// /// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal. -pub trait IconExtWindows: Sized { +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct WinIcon { + pub(crate) inner: Arc, +} + +impl WinIcon { /// Create an icon from a file path. /// /// Specify `size` to load a specific icon size from the file, or `None` to load the default @@ -657,22 +665,31 @@ pub trait IconExtWindows: Sized { /// /// In cases where the specified size does not exist in the file, Windows may perform scaling /// to get an icon of the desired size. - fn from_path>(path: P, size: Option>) - -> Result; + pub fn from_path>( + path: P, + size: Option>, + ) -> Result { + Self::from_path_impl(path, size) + } /// Create an icon from a resource embedded in this executable or library by its ordinal id. /// /// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an /// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`. /// - /// [`from_resource_name`]: IconExtWindows::from_resource_name + /// [`from_resource_name`]: Self::from_resource_name /// /// Specify `size` to load a specific icon size from the file, or `None` to load the default /// icon size from the file. /// /// In cases where the specified size does not exist in the file, Windows may perform scaling /// to get an icon of the desired size. - fn from_resource(ordinal: u16, size: Option>) -> Result; + pub fn from_resource( + resource_id: u16, + size: Option>, + ) -> Result { + Self::from_resource_impl(resource_id, size) + } /// Create an icon from a resource embedded in this executable or library by its name. /// @@ -697,17 +714,16 @@ pub trait IconExtWindows: Sized { /// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into /// [`from_resource`]: /// - /// [`from_resource`]: IconExtWindows::from_resource + /// [`from_resource`]: Self::from_resource /// /// ```rust,no_run - /// use winit::platform::windows::IconExtWindows; - /// use winit::window::Icon; + /// use winit::platform::windows::WinIcon; /// - /// assert!(Icon::from_resource_name("app", None).is_ok()); - /// assert!(Icon::from_resource(1, None).is_ok()); - /// assert!(Icon::from_resource(27, None).is_ok()); - /// assert!(Icon::from_resource_name("27", None).is_err()); - /// assert!(Icon::from_resource_name("0027", None).is_err()); + /// assert!(WinIcon::from_resource_name("app", None).is_ok()); + /// assert!(WinIcon::from_resource(1, None).is_ok()); + /// assert!(WinIcon::from_resource(27, None).is_ok()); + /// assert!(WinIcon::from_resource_name("27", None).is_err()); + /// assert!(WinIcon::from_resource_name("0027", None).is_err()); /// ``` /// /// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a @@ -716,30 +732,21 @@ pub trait IconExtWindows: Sized { /// [`from_resource`]: IconExtWindows::from_resource /// /// ```rust,no_run - /// # use winit::platform::windows::IconExtWindows; + /// # use winit::platform::windows::WinIcon; /// # use winit::window::Icon; - /// assert!(Icon::from_resource_name("0", None).is_ok()); - /// assert!(Icon::from_resource(0, None).is_err()); + /// assert!(WinIcon::from_resource_name("0", None).is_ok()); + /// assert!(WinIcon::from_resource(0, None).is_err()); /// ``` - fn from_resource_name(name: &str, size: Option>) -> Result; -} - -impl IconExtWindows for Icon { - fn from_path>( - path: P, + pub fn from_resource_name( + resource_name: &str, size: Option>, ) -> Result { - let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?; - Ok(Icon { inner: win_icon }) - } - - fn from_resource(ordinal: u16, size: Option>) -> Result { - let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?; - Ok(Icon { inner: win_icon }) - } - - fn from_resource_name(name: &str, size: Option>) -> Result { - let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?; - Ok(Icon { inner: win_icon }) + Self::from_resource_name_impl(resource_name, size) + } +} + +impl From for Icon { + fn from(value: WinIcon) -> Self { + Self(Arc::new(value)) } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 7c751bcd..c768e3bb 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -20,7 +20,6 @@ use crate::event_loop::{ EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; -pub(crate) use crate::icon::NoIcon as PlatformIcon; use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use crate::platform::pump_events::PumpStatus; use crate::window::{ diff --git a/src/platform_impl/apple/appkit/mod.rs b/src/platform_impl/apple/appkit/mod.rs index 03f3c18b..ef2e597f 100644 --- a/src/platform_impl/apple/appkit/mod.rs +++ b/src/platform_impl/apple/appkit/mod.rs @@ -21,4 +21,3 @@ pub(crate) use self::event_loop::{ pub(crate) use self::monitor::MonitorHandle; pub(crate) use self::window::Window; pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes; -pub(crate) use crate::icon::NoIcon as PlatformIcon; diff --git a/src/platform_impl/apple/uikit/mod.rs b/src/platform_impl/apple/uikit/mod.rs index 8a1bb167..6a7c088c 100644 --- a/src/platform_impl/apple/uikit/mod.rs +++ b/src/platform_impl/apple/uikit/mod.rs @@ -14,7 +14,6 @@ pub(crate) use self::event_loop::{ }; pub(crate) use self::monitor::MonitorHandle; pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window}; -pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Debug)] pub enum OsError {} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 9749e9b2..d8239572 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -13,7 +13,6 @@ use crate::application::ApplicationHandler; use crate::dpi::Size; use crate::error::{EventLoopError, NotSupportedError}; use crate::event_loop::ActiveEventLoop; -pub(crate) use crate::icon::RgbaIcon as PlatformIcon; use crate::platform::pump_events::PumpStatus; #[cfg(x11_platform)] use crate::platform::x11::WindowType as XWindowType; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 97dcb207..14f8a8d9 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -28,15 +28,16 @@ use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size} use crate::error::{NotSupportedError, RequestError}; use crate::event::{SurfaceSizeWriter, WindowEvent}; use crate::event_loop::AsyncRequestSerial; +use crate::icon::RgbaIcon; use crate::monitor::{ Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider, VideoMode, }; use crate::platform::x11::WindowType; +use crate::platform_impl::common; use crate::platform_impl::x11::atoms::*; use crate::platform_impl::x11::{ xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error, }; -use crate::platform_impl::{common, PlatformIcon}; use crate::window::{ CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, @@ -203,7 +204,11 @@ impl CoreWindow for Window { } fn set_window_icon(&self, window_icon: Option) { - self.0.set_window_icon(window_icon.map(|inner| inner.inner)) + let icon = match window_icon.as_ref() { + Some(icon) => icon.0.cast_ref::(), + None => None, + }; + self.0.set_window_icon(icon) } fn set_ime_cursor_area(&self, position: Position, size: Size) { @@ -765,8 +770,10 @@ impl UnownedWindow { .check()); // Set window icons - if let Some(icon) = window_attrs.window_icon { - leap!(window.set_icon_inner(icon.inner)).ignore_error(); + if let Some(icon) = + window_attrs.window_icon.as_ref().and_then(|icon| icon.0.cast_ref::()) + { + leap!(window.set_icon_inner(icon)).ignore_error(); } // Opt into handling window close and resize synchronization @@ -1404,7 +1411,7 @@ impl UnownedWindow { self.xconn.flush_requests().expect("Failed to set window-level state"); } - fn set_icon_inner(&self, icon: PlatformIcon) -> Result, X11Error> { + fn set_icon_inner(&self, icon: &RgbaIcon) -> Result, X11Error> { let atoms = self.xconn.atoms(); let icon_atom = atoms[_NET_WM_ICON]; let data = icon.to_cardinals(); @@ -1431,7 +1438,7 @@ impl UnownedWindow { } #[inline] - pub(crate) fn set_window_icon(&self, icon: Option) { + pub(crate) fn set_window_icon(&self, icon: Option<&RgbaIcon>) { match icon { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index 3b7f62df..a1d771e3 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -4,7 +4,6 @@ use std::{fmt, str}; pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop}; pub use self::window::Window; -pub(crate) use crate::icon::NoIcon as PlatformIcon; mod event_loop; mod window; diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index a644e77b..38161534 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -42,4 +42,3 @@ pub(crate) use self::monitor::{ }; use self::web_sys as backend; pub use self::window::{PlatformSpecificWindowAttributes, Window}; -pub(crate) use crate::icon::NoIcon as PlatformIcon; diff --git a/src/platform_impl/windows/icon.rs b/src/platform_impl/windows/icon.rs index 10360f01..8670f9c9 100644 --- a/src/platform_impl/windows/icon.rs +++ b/src/platform_impl/windows/icon.rs @@ -5,13 +5,12 @@ use std::{fmt, io, mem, ptr}; use cursor_icon::CursorIcon; use windows_sys::core::PCWSTR; -use windows_sys::Win32::Foundation::HWND; use windows_sys::Win32::Graphics::Gdi::{ CreateBitmap, CreateCompatibleBitmap, DeleteObject, GetDC, ReleaseDC, SetBitmapBits, }; use windows_sys::Win32::UI::WindowsAndMessaging::{ - CreateIcon, CreateIconIndirect, DestroyCursor, DestroyIcon, LoadImageW, SendMessageW, HCURSOR, - HICON, ICONINFO, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE, LR_LOADFROMFILE, WM_SETICON, + CreateIcon, CreateIconIndirect, DestroyCursor, DestroyIcon, LoadImageW, HCURSOR, HICON, + ICONINFO, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE, LR_LOADFROMFILE, }; use super::util; @@ -19,71 +18,12 @@ use crate::cursor::{CursorImage, CustomCursorProvider}; use crate::dpi::PhysicalSize; use crate::error::RequestError; use crate::icon::*; - -impl Pixel { - fn convert_to_bgra(&mut self) { - mem::swap(&mut self.r, &mut self.b); - } -} - -impl RgbaIcon { - fn into_windows_icon(self) -> Result { - let rgba = self.rgba; - let pixel_count = rgba.len() / PIXEL_SIZE; - let mut and_mask = Vec::with_capacity(pixel_count); - let pixels = - unsafe { std::slice::from_raw_parts_mut(rgba.as_ptr() as *mut Pixel, pixel_count) }; - for pixel in pixels { - and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel - pixel.convert_to_bgra(); - } - assert_eq!(and_mask.len(), pixel_count); - let handle = unsafe { - CreateIcon( - ptr::null_mut(), - self.width as i32, - self.height as i32, - 1, - (PIXEL_SIZE * 8) as u8, - and_mask.as_ptr(), - rgba.as_ptr(), - ) - }; - if !handle.is_null() { - Ok(WinIcon::from_handle(handle)) - } else { - Err(BadIcon::OsError(io::Error::last_os_error())) - } - } -} - -#[derive(Debug)] -pub enum IconType { - Small = ICON_SMALL as isize, - Big = ICON_BIG as isize, -} - -#[derive(Debug, PartialEq, Eq, Hash)] -struct RaiiIcon { - handle: HICON, -} - -unsafe impl Send for RaiiIcon {} -unsafe impl Sync for RaiiIcon {} - -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct WinIcon { - inner: Arc, -} +use crate::platform::windows::WinIcon; unsafe impl Send for WinIcon {} impl WinIcon { - pub fn as_raw_handle(&self) -> HICON { - self.inner.handle - } - - pub fn from_path>( + pub(crate) fn from_path_impl>( path: P, size: Option>, ) -> Result { @@ -109,14 +49,14 @@ impl WinIcon { } } - pub fn from_resource( + pub(crate) fn from_resource_impl( resource_id: u16, size: Option>, ) -> Result { Self::from_resource_ptr(resource_id as PCWSTR, size) } - pub fn from_resource_name( + pub(crate) fn from_resource_name_impl( resource_name: &str, size: Option>, ) -> Result { @@ -147,14 +87,36 @@ impl WinIcon { } } - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?; - rgba_icon.into_windows_icon() + pub(crate) fn as_raw_handle(&self) -> HICON { + self.inner.handle } - pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { - unsafe { - SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle() as isize); + pub(crate) fn from_rgba(rgba: &RgbaIcon) -> Result { + let pixel_count = rgba.rgba.len() / PIXEL_SIZE; + let mut and_mask = Vec::with_capacity(pixel_count); + let pixels = unsafe { + std::slice::from_raw_parts_mut(rgba.rgba.as_ptr() as *mut Pixel, pixel_count) + }; + for pixel in pixels { + and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel + pixel.convert_to_bgra(); + } + assert_eq!(and_mask.len(), pixel_count); + let handle = unsafe { + CreateIcon( + ptr::null_mut(), + rgba.width as i32, + rgba.height as i32, + 1, + (PIXEL_SIZE * 8) as u8, + and_mask.as_ptr(), + rgba.rgba.as_ptr(), + ) + }; + if !handle.is_null() { + Ok(WinIcon::from_handle(handle)) + } else { + Err(BadIcon::OsError(io::Error::last_os_error())) } } @@ -163,11 +125,7 @@ impl WinIcon { } } -impl Drop for RaiiIcon { - fn drop(&mut self) { - unsafe { DestroyIcon(self.handle) }; - } -} +impl IconProvider for WinIcon {} impl fmt::Debug for WinIcon { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -175,9 +133,29 @@ impl fmt::Debug for WinIcon { } } -pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { - unsafe { - SendMessageW(hwnd, WM_SETICON, icon_type as usize, 0); +impl Pixel { + fn convert_to_bgra(&mut self) { + mem::swap(&mut self.r, &mut self.b); + } +} + +#[derive(Debug, Clone, Copy)] +pub enum IconType { + Small = ICON_SMALL as isize, + Big = ICON_BIG as isize, +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub(crate) struct RaiiIcon { + handle: HICON, +} + +unsafe impl Send for RaiiIcon {} +unsafe impl Sync for RaiiIcon {} + +impl Drop for RaiiIcon { + fn drop(&mut self) { + unsafe { DestroyIcon(self.handle) }; } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index a23dabca..ab007776 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -2,7 +2,7 @@ use windows_sys::Win32::Foundation::HWND; use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX}; pub(crate) use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes}; -pub(crate) use self::icon::{SelectedCursor, WinIcon as PlatformIcon, WinIcon}; +pub(crate) use self::icon::{RaiiIcon, SelectedCursor}; pub(crate) use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use self::monitor::MonitorHandle; pub(crate) use self::window::Window; @@ -10,7 +10,7 @@ use crate::event::DeviceId; use crate::icon::Icon; use crate::platform::windows::{BackdropType, Color, CornerPreference}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PlatformSpecificWindowAttributes { pub owner: Option, pub menu: Option, diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index accf84f8..6374b996 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -37,14 +37,15 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, EnableMenuItem, FlashWindowEx, GetClientRect, GetCursorPos, GetForegroundWindow, GetSystemMenu, GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, - SetCursor, SetCursorPos, SetForegroundWindow, SetMenuDefaultItem, SetWindowDisplayAffinity, - SetWindowPlacement, SetWindowPos, SetWindowTextW, TrackPopupMenu, CS_HREDRAW, CS_VREDRAW, - CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, - GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, - HTTOPLEFT, HTTOPRIGHT, MENU_ITEM_STATE, MFS_DISABLED, MFS_ENABLED, MF_BYCOMMAND, NID_READY, - PM_NOREMOVE, SC_CLOSE, SC_MAXIMIZE, SC_MINIMIZE, SC_MOVE, SC_RESTORE, SC_SIZE, SM_DIGITIZER, - SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, SWP_NOZORDER, TPM_LEFTALIGN, TPM_RETURNCMD, - WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WNDCLASSEXW, + SendMessageW, SetCursor, SetCursorPos, SetForegroundWindow, SetMenuDefaultItem, + SetWindowDisplayAffinity, SetWindowPlacement, SetWindowPos, SetWindowTextW, TrackPopupMenu, + CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, + FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, + HTTOP, HTTOPLEFT, HTTOPRIGHT, MENU_ITEM_STATE, MFS_DISABLED, MFS_ENABLED, MF_BYCOMMAND, + NID_READY, PM_NOREMOVE, SC_CLOSE, SC_MAXIMIZE, SC_MINIMIZE, SC_MOVE, SC_RESTORE, SC_SIZE, + SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, SWP_NOZORDER, TPM_LEFTALIGN, + TPM_RETURNCMD, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SETICON, WM_SYSCOMMAND, + WNDCLASSEXW, }; use super::icon::WinCursor; @@ -52,9 +53,9 @@ use super::MonitorHandle; use crate::cursor::Cursor; use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{NotSupportedError, RequestError}; -use crate::icon::Icon; +use crate::icon::{Icon, RgbaIcon}; use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider}; -use crate::platform::windows::{BackdropType, Color, CornerPreference}; +use crate::platform::windows::{BackdropType, Color, CornerPreference, WinIcon}; use crate::platform_impl::platform::dark_mode::try_theme; use crate::platform_impl::platform::definitions::{ CLSID_TaskbarList, IID_ITaskbarList, IID_ITaskbarList2, ITaskbarList, ITaskbarList2, @@ -66,7 +67,7 @@ use crate::platform_impl::platform::drop_handler::FileDropHandler; use crate::platform_impl::platform::event_loop::{ self, ActiveEventLoop, Event, EventLoopRunner, DESTROY_MSG_ID, }; -use crate::platform_impl::platform::icon::{self, IconType}; +use crate::platform_impl::platform::icon::IconType; use crate::platform_impl::platform::ime::ImeContext; use crate::platform_impl::platform::keyboard::KeyEventBuilder; use crate::platform_impl::platform::window_state::{ @@ -189,12 +190,11 @@ impl Window { } pub fn set_taskbar_icon(&self, taskbar_icon: Option) { - if let Some(ref taskbar_icon) = taskbar_icon { - taskbar_icon.inner.set_for_window(self.hwnd(), IconType::Big); + if let Some(taskbar_icon) = taskbar_icon { + self.set_icon(taskbar_icon, IconType::Big); } else { - icon::unset_for_window(self.hwnd(), IconType::Big); + self.unset_icon(IconType::Big); } - self.window_state_lock().taskbar_icon = taskbar_icon; } unsafe fn handle_os_dragging(&self, wparam: WPARAM) { @@ -349,6 +349,45 @@ impl Window { ); } } + + fn set_icon(&self, mut new_icon: Icon, icon_type: IconType) { + if let Some(icon) = new_icon.0.cast_ref::() { + let icon = match WinIcon::from_rgba(icon) { + Ok(icon) => icon, + Err(err) => { + warn!("{}", err); + return; + }, + }; + new_icon = Icon(Arc::new(icon)); + } + + if let Some(icon) = new_icon.0.cast_ref::() { + unsafe { + SendMessageW( + self.hwnd(), + WM_SETICON, + icon_type as usize, + icon.as_raw_handle() as isize, + ); + } + + match icon_type { + IconType::Small => self.window_state_lock().window_icon = Some(new_icon), + IconType::Big => self.window_state_lock().taskbar_icon = Some(new_icon), + } + } + } + + fn unset_icon(&self, icon_type: IconType) { + unsafe { + SendMessageW(self.hwnd(), WM_SETICON, icon_type as usize, 0); + } + match icon_type { + IconType::Small => self.window_state_lock().window_icon = None, + IconType::Big => self.window_state_lock().taskbar_icon = None, + } + } } impl Drop for Window { @@ -974,12 +1013,11 @@ impl CoreWindow for Window { } fn set_window_icon(&self, window_icon: Option) { - if let Some(ref window_icon) = window_icon { - window_icon.inner.set_for_window(self.hwnd(), IconType::Small); + if let Some(window_icon) = window_icon { + self.set_icon(window_icon, IconType::Small); } else { - icon::unset_for_window(self.hwnd(), IconType::Small); + self.unset_icon(IconType::Small); } - self.window_state_lock().window_icon = window_icon; } fn set_ime_cursor_area(&self, spot: Position, size: Size) { diff --git a/src/window.rs b/src/window.rs index 4a1cb1ec..51907828 100644 --- a/src/window.rs +++ b/src/window.rs @@ -46,7 +46,7 @@ impl fmt::Debug for WindowId { } /// Attributes used when creating a window. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct WindowAttributes { pub surface_size: Option, pub min_surface_size: Option,