diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb9be9ec4..dd285a5f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`. +- On Windows, add custom cursor icon support. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. diff --git a/Cargo.toml b/Cargo.toml index 017e5927ab..238c3156ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ raw-window-handle = "0.3" bitflags = "1" [dev-dependencies] -image = "0.23" +png = "0.16" simple_logger = "1" [target.'cfg(target_os = "android")'.dependencies] diff --git a/examples/cursor.rs b/examples/cursor.rs index de45adae33..4091aeabbe 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,7 +1,9 @@ +use std::{fs::File, path::Path}; use winit::{ + dpi::{PhysicalPosition, PhysicalSize}, event::{ElementState, Event, KeyboardInput, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, WindowBuilder}, + window::{CursorIcon, CustomCursorIcon, RgbaBuffer, WindowBuilder}, }; fn main() { @@ -13,6 +15,69 @@ fn main() { let mut cursor_idx = 0; + let custom_cursor_icon = { + let base_path = Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/icons/icon_folder/" + )); + + CustomCursorIcon::from_rgba_fn(move |size, _| { + let path = base_path.join(format!("{}.png", size.width)); + let (icon_rgba, icon_size) = { + let decoder = png::Decoder::new(File::open(path)?); + let (info, mut reader) = decoder.read_info()?; + + let mut rgba = vec![0; info.buffer_size()]; + reader.next_frame(&mut rgba).unwrap(); + + (rgba, PhysicalSize::new(info.width, info.height)) + }; + Ok(( + RgbaBuffer::from_rgba(icon_rgba, icon_size), + PhysicalPosition::new(0, 0), + )) + }) + }; + + let cursors = vec![ + CursorIcon::Custom(custom_cursor_icon), + CursorIcon::Default, + CursorIcon::Crosshair, + CursorIcon::Hand, + CursorIcon::Arrow, + CursorIcon::Move, + CursorIcon::Text, + CursorIcon::Wait, + CursorIcon::Help, + CursorIcon::Progress, + CursorIcon::NotAllowed, + CursorIcon::ContextMenu, + CursorIcon::Cell, + CursorIcon::VerticalText, + CursorIcon::Alias, + CursorIcon::Copy, + CursorIcon::NoDrop, + CursorIcon::Grab, + CursorIcon::Grabbing, + CursorIcon::AllScroll, + CursorIcon::ZoomIn, + CursorIcon::ZoomOut, + CursorIcon::EResize, + CursorIcon::NResize, + CursorIcon::NeResize, + CursorIcon::NwResize, + CursorIcon::SResize, + CursorIcon::SeResize, + CursorIcon::SwResize, + CursorIcon::WResize, + CursorIcon::EwResize, + CursorIcon::NsResize, + CursorIcon::NeswResize, + CursorIcon::NwseResize, + CursorIcon::ColResize, + CursorIcon::RowResize, + ]; + event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -29,9 +94,9 @@ fn main() { }, .. } => { - println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); - window.set_cursor_icon(CURSORS[cursor_idx]); - if cursor_idx < CURSORS.len() - 1 { + println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]); + window.set_cursor_icon(cursors[cursor_idx].clone()); + if cursor_idx < cursors.len() - 1 { cursor_idx += 1; } else { cursor_idx = 0; @@ -48,41 +113,3 @@ fn main() { } }); } - -const CURSORS: &[CursorIcon] = &[ - CursorIcon::Default, - CursorIcon::Crosshair, - CursorIcon::Hand, - CursorIcon::Arrow, - CursorIcon::Move, - CursorIcon::Text, - CursorIcon::Wait, - CursorIcon::Help, - CursorIcon::Progress, - CursorIcon::NotAllowed, - CursorIcon::ContextMenu, - CursorIcon::Cell, - CursorIcon::VerticalText, - CursorIcon::Alias, - CursorIcon::Copy, - CursorIcon::NoDrop, - CursorIcon::Grab, - CursorIcon::Grabbing, - CursorIcon::AllScroll, - CursorIcon::ZoomIn, - CursorIcon::ZoomOut, - CursorIcon::EResize, - CursorIcon::NResize, - CursorIcon::NeResize, - CursorIcon::NwResize, - CursorIcon::SResize, - CursorIcon::SeResize, - CursorIcon::SwResize, - CursorIcon::WResize, - CursorIcon::EwResize, - CursorIcon::NsResize, - CursorIcon::NeswResize, - CursorIcon::NwseResize, - CursorIcon::ColResize, - CursorIcon::RowResize, -]; diff --git a/examples/icon.png b/examples/icon.png deleted file mode 100644 index aa3fbf3cfc..0000000000 Binary files a/examples/icon.png and /dev/null differ diff --git a/examples/icons/ferris.png b/examples/icons/ferris.png new file mode 100644 index 0000000000..88f2cd1691 Binary files /dev/null and b/examples/icons/ferris.png differ diff --git a/examples/icons/icon_folder/128.png b/examples/icons/icon_folder/128.png new file mode 100644 index 0000000000..3e12f0b33e Binary files /dev/null and b/examples/icons/icon_folder/128.png differ diff --git a/examples/icons/icon_folder/16.png b/examples/icons/icon_folder/16.png new file mode 100644 index 0000000000..bdac1fe9c4 Binary files /dev/null and b/examples/icons/icon_folder/16.png differ diff --git a/examples/icons/icon_folder/24.png b/examples/icons/icon_folder/24.png new file mode 100644 index 0000000000..bad1d6d150 Binary files /dev/null and b/examples/icons/icon_folder/24.png differ diff --git a/examples/icons/icon_folder/256.png b/examples/icons/icon_folder/256.png new file mode 100644 index 0000000000..2a7557fcbf Binary files /dev/null and b/examples/icons/icon_folder/256.png differ diff --git a/examples/icons/icon_folder/32.png b/examples/icons/icon_folder/32.png new file mode 100644 index 0000000000..24e27d9f32 Binary files /dev/null and b/examples/icons/icon_folder/32.png differ diff --git a/examples/icons/icon_folder/48.png b/examples/icons/icon_folder/48.png new file mode 100644 index 0000000000..16323fae88 Binary files /dev/null and b/examples/icons/icon_folder/48.png differ diff --git a/examples/icons/icon_folder/64.png b/examples/icons/icon_folder/64.png new file mode 100644 index 0000000000..29c1a73015 Binary files /dev/null and b/examples/icons/icon_folder/64.png differ diff --git a/examples/icons/icon_folder/96.png b/examples/icons/icon_folder/96.png new file mode 100644 index 0000000000..0ed0fe497c Binary files /dev/null and b/examples/icons/icon_folder/96.png differ diff --git a/examples/window_icon.rs b/examples/window_icon.rs index 734debf4d4..78d4a121a5 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -1,21 +1,20 @@ -extern crate image; -use std::path::Path; +use std::{ffi::OsStr, fs::File, path::Path}; use winit::{ + dpi::PhysicalSize, event::Event, event_loop::{ControlFlow, EventLoop}, - window::{Icon, WindowBuilder}, + window::{CustomWindowIcon, RgbaBuffer, WindowBuilder}, }; fn main() { simple_logger::init().unwrap(); - // You'll have to choose an icon size at your own discretion. On X11, the desired size varies - // by WM, and on Windows, you still have to account for screen scaling. Here we use 32px, - // since it seems to work well enough in most cases. Be careful about going too high, or - // you'll be bitten by the low-quality downscaling built into the WM. - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png"); + let path = Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/icons/icon_folder/" + )); - let icon = load_icon(Path::new(path)); + let icon = load_icon(&path); let event_loop = EventLoop::new(); @@ -43,14 +42,31 @@ fn main() { }); } -fn load_icon(path: &Path) -> Icon { - let (icon_rgba, icon_width, icon_height) = { - let image = image::open(path) - .expect("Failed to open icon path") - .into_rgba(); - let (width, height) = image.dimensions(); - let rgba = image.into_raw(); - (rgba, width, height) +fn load_icon(path: &Path) -> CustomWindowIcon { + let decode_png = |path: &Path| { + let decoder = png::Decoder::new(File::open(path).unwrap()); + let (info, mut reader) = decoder.read_info().unwrap(); + + let mut rgba = vec![0; info.buffer_size()]; + reader.next_frame(&mut rgba).unwrap(); + + (rgba, PhysicalSize::new(info.width, info.height)) }; - Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") + if path.is_file() { + if path.extension() == Some(OsStr::new("png")) { + let (icon_rgba, icon_size) = decode_png(path); + CustomWindowIcon::from_rgba(&icon_rgba, icon_size).unwrap() + } else { + panic!("unsupported file extension: {:?}", path.extension()); + } + } else if path.is_dir() { + let path = path.to_owned(); + CustomWindowIcon::from_rgba_fn(move |size, _| { + let path = path.join(format!("{}.png", size.width)); + let (icon_rgba, icon_size) = decode_png(&path); + Ok(RgbaBuffer::from_rgba(icon_rgba, icon_size)) + }) + } else { + panic!("path {} is neither file nor directory", path.display()); + } } diff --git a/src/dpi.rs b/src/dpi.rs index 8a56ae6e06..7bc3add6b2 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -225,7 +225,7 @@ impl Into<[X; 2]> for LogicalPosition

{ } /// A position represented in physical pixels. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalPosition

{ pub x: P, diff --git a/src/icon.rs b/src/icon.rs index 418ea03afa..fcf52e6953 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -1,5 +1,8 @@ -use crate::platform_impl::PlatformIcon; -use std::{error::Error, fmt, io, mem}; +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + platform_impl::{PlatformCustomCursorIcon, PlatformCustomWindowIcon}, +}; +use std::{error::Error, fmt, io, mem, ops::Deref}; #[repr(C)] #[derive(Debug)] @@ -12,124 +15,224 @@ pub(crate) struct Pixel { pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); -#[derive(Debug)] -/// An error produced when using `Icon::from_rgba` 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. - ByteCountNotDivisibleBy4 { byte_count: usize }, - /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. - /// At least one of your arguments is incorrect. - DimensionsVsPixelCount { - width: u32, - height: u32, - width_x_height: usize, - pixel_count: usize, - }, - /// Produced when underlying OS functionality failed to create the icon - OsError(io::Error), -} - -impl fmt::Display for BadIcon { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f, - "The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.", - byte_count, - ), - BadIcon::DimensionsVsPixelCount { - width, - height, - width_x_height, - pixel_count, - } => write!(f, - "The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.", - width, height, pixel_count, width_x_height, - ), - BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e), - } - } +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct RgbaBuffer> { + pub(crate) rgba: I, + pub(crate) size: PhysicalSize, } -impl Error for BadIcon { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(self) - } -} +/// For platforms which don't have window icons (e.g. web) +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NoWindowIcon; +/// For platforms which don't have cursor icons #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct RgbaIcon { - pub(crate) rgba: Vec, - pub(crate) width: u32, - pub(crate) height: u32, +pub(crate) struct NoCursorIcon; + +/// An icon used for the window titlebar, taskbar, or cursor. +#[derive(Clone, PartialEq, Eq)] +pub struct CustomWindowIcon { + pub(crate) inner: PlatformCustomWindowIcon, } -/// For platforms which don't have window icons (e.g. web) -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct NoIcon; +#[derive(Clone, PartialEq, Eq)] +pub struct CustomCursorIcon { + pub(crate) inner: PlatformCustomCursorIcon, +} #[allow(dead_code)] // These are not used on every platform mod constructors { use super::*; - impl RgbaIcon { - /// 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 { - 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 NoWindowIcon { + pub fn from_rgba(_rgba: Vec, _size: PhysicalSize) -> Result { + Ok(Self) + } + + pub fn from_rgba_fn(_get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) + -> Result>, Box>, + { + Self } } - 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) + impl NoCursorIcon { + pub fn from_rgba( + _rgba: Vec, + _size: PhysicalSize, + _hot_spot: PhysicalPosition, + ) -> Result { + Ok(Self) + } + + pub fn from_rgba_fn(_get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result< + (RgbaBuffer>, PhysicalPosition), + Box, + >, + { + Self } } } -/// An icon used for the window titlebar, taskbar, etc. -#[derive(Clone)] -pub struct Icon { - pub(crate) inner: PlatformIcon, +impl> fmt::Debug for RgbaBuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let RgbaBuffer { rgba, size } = self; + f.debug_struct("RgbaBuffer") + .field("size", &size) + .field("rgba", &(&**rgba as *const [u8])) + .finish() + } +} +impl> RgbaBuffer { + /// Creates a `RgbaBuffer` from 32bpp RGBA data. + /// + /// ## Panics + /// Panics if the length of `rgba` is not divisible by 4, or if `width * height` doesn't + /// equal `rgba.len() / 4`. + pub fn from_rgba(rgba: I, size: PhysicalSize) -> Self { + let PhysicalSize { width, height } = size; + if rgba.len() % PIXEL_SIZE != 0 { + panic!( + "The length of the `rgba` argument ({:?}) isn't divisible by 4, making \ + it impossible to interpret as 32bpp RGBA pixels.", + rgba.len(), + ); + } + let pixel_count = rgba.len() / PIXEL_SIZE; + if pixel_count != (width * height) as usize { + panic!( + "The specified dimensions ({:?}x{:?}) don't match the number of pixels \ + supplied by the `rgba` argument ({:?}). For those dimensions, the expected \ + pixel count is {:?}.", + width, + height, + pixel_count, + width * height, + ) + } + + RgbaBuffer { rgba, size } + } + + pub fn into_custom_window_icon(self) -> Result { + CustomWindowIcon::from_rgba(&*self.rgba, self.size) + } + + pub fn into_custom_cursor_icon( + self, + hot_spot: PhysicalPosition, + ) -> Result { + CustomCursorIcon::from_rgba(&*self.rgba, self.size, hot_spot) + } } -impl fmt::Debug for Icon { +impl fmt::Debug for CustomWindowIcon { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fmt::Debug::fmt(&self.inner, formatter) } } -impl Icon { +impl fmt::Debug for CustomCursorIcon { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.inner, formatter) + } +} + +impl CustomWindowIcon { /// 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 { - Ok(Icon { - inner: PlatformIcon::from_rgba(rgba, width, height)?, + /// ## Panics + /// Panics if the length of `rgba` is not divisible by 4, or if `width * height` doesn't equal + /// `rgba.len() / 4`. + pub fn from_rgba(rgba: &[u8], size: PhysicalSize) -> Result { + Ok(CustomWindowIcon { + inner: PlatformCustomWindowIcon::from_rgba(rgba.into(), size)?, }) } + + /// Lazily create an icon from several scaled source images. + /// + /// `get_icon` will be lazily called for a particular icon size whenever the window manager + /// needs an icon of that size. The `PhysicalSize` parameter specifies the window manager's + /// suggested icon size for a particular scale factor, and will always be a square. The `f64` + /// parameter specifies the scale factor that the window manager is requesting the icon with. + /// `get_icon` will only be called once for any given suggested icon size. + /// + /// If `get_icon` returns `Err(e)` for a given size, Winit will invoke `warn!` on the returned + /// error and will try to retrieve a differently-sized icon from `get_icon`. + pub fn from_rgba_fn(mut get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result, Box>, + B: Deref + Into>, + { + CustomWindowIcon { + inner: PlatformCustomWindowIcon::from_rgba_fn(move |size, scale_factor| { + let icon = get_icon(size, scale_factor)?; + Ok(RgbaBuffer { + rgba: icon.rgba.into(), + size: icon.size, + }) + }), + } + } +} + +impl CustomCursorIcon { + /// Creates an `Icon` from 32bpp RGBA data, with a defined cursor hot spot. The hot spot is + /// the exact pixel in the icon image where the cursor clicking point is, and is ignored when + /// the icon is used as a window icon. + /// + /// ## Panics + /// Panics if the length of `rgba` is not divisible by 4, or if `width * height` doesn't equal + /// `rgba.len() / 4`. + pub fn from_rgba( + rgba: &[u8], + size: PhysicalSize, + hot_spot: PhysicalPosition, + ) -> Result { + Ok(CustomCursorIcon { + inner: PlatformCustomCursorIcon::from_rgba(rgba.into(), size, hot_spot)?, + }) + } + + pub fn from_rgba_fn(mut get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) + -> Result<(RgbaBuffer, PhysicalPosition), Box>, + B: Deref + Into>, + { + CustomCursorIcon { + inner: PlatformCustomCursorIcon::from_rgba_fn(move |size, scale_factor| { + let (icon, hot_spot) = get_icon(size, scale_factor)?; + Ok(( + RgbaBuffer { + rgba: icon.rgba.into(), + size: icon.size, + }, + hot_spot, + )) + }), + } + } } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a040be363f..2b19f30565 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1,19 +1,19 @@ #![cfg(target_os = "windows")] -use std::os::raw::c_void; -use std::path::Path; +use std::{io, os::raw::c_void, path::Path}; use libc; use winapi::shared::minwindef::WORD; use winapi::shared::windef::HWND; use crate::{ - dpi::PhysicalSize, event::DeviceId, event_loop::EventLoop, monitor::MonitorHandle, - platform_impl::{EventLoop as WindowsEventLoop, WinIcon}, - window::{BadIcon, Icon, Window, WindowBuilder}, + platform_impl::{ + EventLoop as WindowsEventLoop, PlatformCustomCursorIcon, PlatformCustomWindowIcon, + }, + window::{CustomCursorIcon, CustomWindowIcon, Window, WindowBuilder}, }; /// Additional methods on `EventLoop` that are specific to Windows. @@ -78,8 +78,11 @@ pub trait WindowExtWindows { /// The pointer will become invalid when the native window was destroyed. fn hwnd(&self) -> *mut libc::c_void; - /// This sets `ICON_BIG`. A good ceiling here is 256x256. - fn set_taskbar_icon(&self, taskbar_icon: Option); + #[deprecated( + note = "Deprecated. `with_window_icon` now sets the taskbar icon via automatic icon scaling." + )] + /// Deprecated in favor of automatic icon scaling via `Icon::from_rgba_fn` or `IconExtWindows::from_(path|resource)` + 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; @@ -97,8 +100,8 @@ impl WindowExtWindows for Window { } #[inline] - fn set_taskbar_icon(&self, taskbar_icon: Option) { - self.window.set_taskbar_icon(taskbar_icon) + fn set_taskbar_icon(&self, _taskbar_icon: Option) { + warn!("set_taskbar_icon has been deprecated in favor of automatic icon scaling, and currently does nothing"); } #[inline] @@ -112,8 +115,11 @@ pub trait WindowBuilderExtWindows { /// Sets a parent to the window to be created. fn with_parent_window(self, parent: HWND) -> WindowBuilder; - /// This sets `ICON_BIG`. A good ceiling here is 256x256. - fn with_taskbar_icon(self, taskbar_icon: Option) -> WindowBuilder; + #[deprecated( + note = "Deprecated. `with_window_icon` now sets the taskbar icon via automatic icon scaling." + )] + /// Deprecated in favor of automatic icon scaling via `Icon::from_rgba_fn` or `IconExtWindows::from_(path|resource)` + fn with_taskbar_icon(self, taskbar_icon: Option) -> WindowBuilder; /// This sets `WS_EX_NOREDIRECTIONBITMAP`. fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder; @@ -135,8 +141,8 @@ impl WindowBuilderExtWindows for WindowBuilder { } #[inline] - fn with_taskbar_icon(mut self, taskbar_icon: Option) -> WindowBuilder { - self.platform_specific.taskbar_icon = taskbar_icon; + fn with_taskbar_icon(self, _taskbar_icon: Option) -> WindowBuilder { + warn!("with_taskbar_icon icon has been deprecated in favor of automatic icon scaling, and currently does nothing"); self } @@ -190,38 +196,47 @@ impl DeviceIdExtWindows for DeviceId { } /// Additional methods on `Icon` that are specific to Windows. -pub trait IconExtWindows: Sized { +pub trait CustomWindowIconExtWindows: Sized { /// Create an icon from a file path. /// - /// 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_path>(path: P, size: Option>) - -> Result; + /// Winit will lazily load images at different sizes from the file as needed by Windows. + fn from_path>(path: P) -> Result; /// Create an icon from a resource embedded in this executable or library. + fn from_resource(ordinal: WORD) -> Result; +} + +impl CustomWindowIconExtWindows for CustomWindowIcon { + fn from_path>(path: P) -> Result { + let win_icon = PlatformCustomWindowIcon::from_path(path)?; + Ok(CustomWindowIcon { inner: win_icon }) + } + + fn from_resource(ordinal: WORD) -> Result { + let win_icon = PlatformCustomWindowIcon::from_resource(ordinal)?; + Ok(CustomWindowIcon { inner: win_icon }) + } +} + +/// Additional methods on `Icon` that are specific to Windows. +pub trait CustomCursorIconExtWindows: Sized { + /// Create an icon from a file path. /// - /// 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: WORD, size: Option>) -> Result; + /// Winit will lazily load images at different sizes from the file as needed by Windows. + fn from_path>(path: P) -> Result; + + /// Create an icon from a resource embedded in this executable or library. + fn from_resource(ordinal: WORD) -> Result; } -impl IconExtWindows for Icon { - fn from_path>( - path: P, - size: Option>, - ) -> Result { - let win_icon = WinIcon::from_path(path, size)?; - Ok(Icon { inner: win_icon }) +impl CustomCursorIconExtWindows for CustomCursorIcon { + fn from_path>(path: P) -> Result { + let win_icon = PlatformCustomCursorIcon::from_path(path)?; + Ok(CustomCursorIcon { inner: win_icon }) } - fn from_resource(ordinal: WORD, size: Option>) -> Result { - let win_icon = WinIcon::from_resource(ordinal, size)?; - Ok(Icon { inner: win_icon }) + fn from_resource(ordinal: WORD) -> Result { + let win_icon = PlatformCustomCursorIcon::from_resource(ordinal)?; + Ok(CustomCursorIcon { inner: win_icon }) } } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 0fee68324e..a3a26ea78d 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -9,9 +9,9 @@ #[cfg(all(not(feature = "x11"), not(feature = "wayland")))] compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); -use std::{collections::VecDeque, env, fmt}; +use std::{collections::VecDeque, env, error::Error, fmt, io, sync::Arc}; #[cfg(feature = "x11")] -use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; +use std::{ffi::CStr, mem::MaybeUninit, os::raw::*}; #[cfg(feature = "x11")] use parking_lot::Mutex; @@ -28,13 +28,11 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - icon::Icon, + icon::{CustomWindowIcon, RgbaBuffer}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; -pub(crate) use crate::icon::RgbaIcon as PlatformIcon; - #[cfg(feature = "wayland")] pub mod wayland; #[cfg(feature = "x11")] @@ -400,7 +398,7 @@ impl Window { } #[inline] - pub fn set_window_icon(&self, _window_icon: Option) { + pub fn set_window_icon(&self, _window_icon: Option) { match self { #[cfg(feature = "x11")] &Window::X(ref w) => w.set_window_icon(_window_icon), @@ -683,6 +681,117 @@ impl EventLoopWindowTarget { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PlatformCustomWindowIcon { + icon: RgbaBuffer>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PlatformCustomCursorIcon { + icon: RgbaBuffer>, + hot_spot: PhysicalPosition, +} + +impl PlatformCustomWindowIcon { + pub fn from_rgba(rgba: &[u8], size: PhysicalSize) -> Result { + Ok(PlatformCustomWindowIcon { + icon: RgbaBuffer::from_rgba(rgba.into(), size), + }) + } + + pub fn from_rgba_fn(mut get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) + -> Result>, Box>, + { + let mut get_icon = + |size, scale_factor| match get_icon(PhysicalSize::new(size, size), scale_factor) { + Ok(i) => Some(i), + Err(e) => { + warn!("could not load icon at size {0}x{0}: {1}", size, e); + None + } + }; + let icon = get_icon(32, 1.0) + .or_else(|| get_icon(32, 1.0)) + .or_else(|| get_icon(24, 1.0)) + .or_else(|| get_icon(16, 1.0)) + .or_else(|| get_icon(48, 1.0)) + .or_else(|| get_icon(64, 1.0)) + .or_else(|| get_icon(96, 1.0)) + .or_else(|| get_icon(128, 1.0)) + .or_else(|| get_icon(256, 1.0)) + .unwrap_or_else(|| RgbaBuffer::from_rgba(Box::new([]), PhysicalSize::new(0, 0))); + PlatformCustomWindowIcon { + // TODO: IMPLEMENT ACTUAL LAZY ICON SCALING + icon: RgbaBuffer { + rgba: icon.rgba.into(), + size: icon.size, + }, + } + } +} + +impl PlatformCustomCursorIcon { + pub fn from_rgba( + rgba: &[u8], + size: PhysicalSize, + hot_spot: PhysicalPosition, + ) -> Result { + Ok(PlatformCustomCursorIcon { + icon: RgbaBuffer::from_rgba(rgba.into(), size), + hot_spot, + }) + } + + pub fn from_rgba_fn(mut get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result< + (RgbaBuffer>, PhysicalPosition), + Box, + >, + { + let mut get_icon = + |size, scale_factor| match get_icon(PhysicalSize::new(size, size), scale_factor) { + Ok(i) => Some(i), + Err(e) => { + warn!("could not load icon at size {0}x{0}: {1}", size, e); + None + } + }; + let (icon, hot_spot) = get_icon(32, 1.0) + .or_else(|| get_icon(32, 1.0)) + .or_else(|| get_icon(24, 1.0)) + .or_else(|| get_icon(16, 1.0)) + .or_else(|| get_icon(48, 1.0)) + .or_else(|| get_icon(64, 1.0)) + .or_else(|| get_icon(96, 1.0)) + .or_else(|| get_icon(128, 1.0)) + .or_else(|| get_icon(256, 1.0)) + .unwrap_or_else(|| { + ( + RgbaBuffer::from_rgba(Box::new([]), PhysicalSize::new(0, 0)), + PhysicalPosition::new(0, 0), + ) + }); + PlatformCustomCursorIcon { + icon: RgbaBuffer { + rgba: icon.rgba.into(), + size: icon.size, + }, + hot_spot, + } + } +} + fn sticky_exit_callback( evt: Event<'_, T>, target: &RootELW, diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 77215b9ce9..b10a88dc79 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -127,7 +127,7 @@ impl CursorManager { (**pointer).set_cursor(0, None, 0, 0); } } else { - self.set_cursor_icon_impl(self.current_cursor); + self.set_cursor_icon_impl(self.current_cursor.clone()); } self.cursor_visible = visible; } @@ -137,13 +137,13 @@ impl CursorManager { if !self.cursor_visible { self.set_cursor_visible(false); } else { - self.set_cursor_icon_impl(self.current_cursor); + self.set_cursor_icon_impl(self.current_cursor.clone()); } } pub fn set_cursor_icon(&mut self, cursor: CursorIcon) { if cursor != self.current_cursor { - self.current_cursor = cursor; + self.current_cursor = cursor.clone(); if self.cursor_visible { self.set_cursor_icon_impl(cursor); } @@ -162,7 +162,7 @@ impl CursorManager { CursorIcon::Cell => "plus", CursorIcon::Copy => "copy", CursorIcon::Crosshair => "crosshair", - CursorIcon::Default => "left_ptr", + CursorIcon::Custom(_) | CursorIcon::Default => "left_ptr", CursorIcon::Hand => "hand", CursorIcon::Help => "question_arrow", CursorIcon::Move => "move", diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs index 684af49da9..c1c3e3d8e3 100644 --- a/src/platform_impl/linux/x11/util/cursor.rs +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -1,4 +1,5 @@ use crate::window::CursorIcon; +use std::mem; use super::*; @@ -7,7 +8,7 @@ impl XConnection { let cursor = *self .cursor_cache .lock() - .entry(cursor) + .entry(cursor.as_ref().map(|c| mem::discriminant(c))) .or_insert_with(|| self.get_cursor(cursor)); self.update_cursor(window, cursor); @@ -80,7 +81,7 @@ impl XConnection { CursorIcon::Cell => load(b"plus\0"), CursorIcon::Copy => load(b"copy\0"), CursorIcon::Crosshair => load(b"crosshair\0"), - CursorIcon::Default => load(b"left_ptr\0"), + CursorIcon::Custom(_) | CursorIcon::Default => load(b"left_ptr\0"), CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]), CursorIcon::Help => load(b"question_arrow\0"), CursorIcon::Move => load(b"move\0"), diff --git a/src/platform_impl/linux/x11/util/icon.rs b/src/platform_impl/linux/x11/util/icon.rs index eb13d48b5b..a114b37b1f 100644 --- a/src/platform_impl/linux/x11/util/icon.rs +++ b/src/platform_impl/linux/x11/util/icon.rs @@ -1,5 +1,5 @@ use super::*; -use crate::icon::{Icon, Pixel, PIXEL_SIZE}; +use crate::icon::{CustomWindowIcon, Pixel, PIXEL_SIZE}; impl Pixel { pub fn to_packed_argb(&self) -> Cardinal { @@ -16,15 +16,18 @@ impl Pixel { } } -impl Icon { +impl CustomWindowIcon { pub(crate) fn to_cardinals(&self) -> Vec { - let rgba_icon = &self.inner; + let rgba_icon = &self.inner.icon; assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0); let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE; - assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize); + assert_eq!( + pixel_count, + (rgba_icon.size.width * rgba_icon.size.height) as usize + ); let mut data = Vec::with_capacity(pixel_count); - data.push(rgba_icon.width as Cardinal); - data.push(rgba_icon.height as Cardinal); + data.push(rgba_icon.size.width as Cardinal); + data.push(rgba_icon.size.height as Cardinal); let pixels = rgba_icon.rgba.as_ptr() as *const Pixel; for pixel_index in 0..pixel_count { let pixel = unsafe { &*pixels.offset(pixel_index as isize) }; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index ae63f436a4..ce162e17a2 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -23,7 +23,7 @@ use crate::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, - window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, + window::{CursorIcon, CustomWindowIcon, Fullscreen, WindowAttributes}, }; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; @@ -871,7 +871,7 @@ impl UnownedWindow { .expect("Failed to set always-on-top state"); } - fn set_icon_inner(&self, icon: Icon) -> util::Flusher<'_> { + fn set_icon_inner(&self, icon: CustomWindowIcon) -> util::Flusher<'_> { let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; let data = icon.to_cardinals(); self.xconn.change_property( @@ -896,7 +896,7 @@ impl UnownedWindow { } #[inline] - pub fn set_window_icon(&self, icon: Option) { + pub fn set_window_icon(&self, icon: Option) { match icon { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), @@ -1189,7 +1189,7 @@ impl UnownedWindow { #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - let old_cursor = replace(&mut *self.cursor.lock(), cursor); + let old_cursor = replace(&mut *self.cursor.lock(), cursor.clone()); if cursor != old_cursor && *self.cursor_visible.lock() { self.xconn.set_cursor_icon(self.xwindow, Some(cursor)); } @@ -1264,7 +1264,7 @@ impl UnownedWindow { return; } let cursor = if visible { - Some(*self.cursor.lock()) + Some(self.cursor.lock().clone()) } else { None }; diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 25065f045d..381653f303 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr}; +use std::{collections::HashMap, error::Error, fmt, mem::Discriminant, os::raw::c_int, ptr}; use libc; use parking_lot::Mutex; @@ -21,7 +21,10 @@ pub struct XConnection { pub display: *mut ffi::Display, pub x11_fd: c_int, pub latest_error: Mutex>, - pub cursor_cache: Mutex, ffi::Cursor>>, + /// We're using `Discriminant` here to work around the fact that you can't hash + /// `CursorIcon::Custom`. The discriminant for `CursorIcon::Custom` should *not* be stored in + /// this map. + pub cursor_cache: Mutex>, ffi::Cursor>>, } unsafe impl Send for XConnection {} diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 72b8e0a332..f1b9f0cb6b 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -25,7 +25,8 @@ use crate::{ error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, }; -pub(crate) use crate::icon::NoIcon as PlatformIcon; +pub(crate) type PlatformCustomWindowIcon = crate::icon::NoWindowIcon; +pub(crate) type PlatformCustomCursorIcon = crate::icon::NoCursorIcon; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index 5c4d1537a7..4f1f5ccca2 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -18,7 +18,9 @@ impl From for Cursor { fn from(cursor: CursorIcon) -> Self { // See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc. match cursor { - CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"), + CursorIcon::Custom(_) | CursorIcon::Arrow | CursorIcon::Default => { + Cursor::Native("arrowCursor") + } CursorIcon::Hand => Cursor::Native("pointingHandCursor"), CursorIcon::Grab => Cursor::Native("openHandCursor"), CursorIcon::Grabbing => Cursor::Native("closedHandCursor"), diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 5505b661af..45d1870bbd 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -14,7 +14,7 @@ use crate::{ LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, }, error::{ExternalError, NotSupportedError, OsError as RootOsError}, - icon::Icon, + icon::CustomWindowIcon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, platform_impl::platform::{ @@ -923,7 +923,7 @@ impl UnownedWindow { } #[inline] - pub fn set_window_icon(&self, _icon: Option) { + pub fn set_window_icon(&self, _icon: Option) { // macOS doesn't have window icons. Though, there is // `setRepresentedFilename`, but that's semantically distinct and should // only be used when the window is in some way representing a specific diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index bd525056fa..f50457c25e 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -126,7 +126,7 @@ impl Window { #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { let text = match cursor { - CursorIcon::Default => "auto", + CursorIcon::Custom(_) | CursorIcon::Default => "auto", CursorIcon::Crosshair => "crosshair", CursorIcon::Hand => "pointer", CursorIcon::Arrow => "default", diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index eb0a65762b..f42181c16c 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1598,7 +1598,7 @@ unsafe extern "system" fn public_window_callback( .cursor_flags() .contains(CursorFlags::IN_WINDOW) { - Some(window_state.mouse.cursor) + Some(window_state.mouse.cursor.to_windows_cursor_scaled()) } else { None } @@ -1606,7 +1606,6 @@ unsafe extern "system" fn public_window_callback( match set_cursor_to { Some(cursor) => { - let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); winuser::SetCursor(cursor); 0 } @@ -1668,6 +1667,10 @@ unsafe extern "system" fn public_window_callback( return 0; } + if let Some(window_icon) = &window_state.window_icon { + window_icon.inner.set_for_window(window, new_scale_factor); + } + window_state.fullscreen.is_none() && !window_state.window_flags().contains(WindowFlags::MAXIMIZED) }; @@ -1774,11 +1777,7 @@ unsafe extern "system" fn public_window_callback( // relative horizontal position in the title bar is preserved. if dragging_window { let bias = { - let cursor_pos = { - let mut pos = mem::zeroed(); - winuser::GetCursorPos(&mut pos); - pos - }; + let cursor_pos = util::get_cursor_position(); let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) as f64 / (suggested_rect.right - suggested_rect.left) as f64; diff --git a/src/platform_impl/windows/icon.rs b/src/platform_impl/windows/icon.rs index 1308c7467e..06489e381c 100644 --- a/src/platform_impl/windows/icon.rs +++ b/src/platform_impl/windows/icon.rs @@ -1,169 +1,630 @@ -use std::{fmt, io, iter::once, mem, os::windows::ffi::OsStrExt, path::Path, ptr, sync::Arc}; - +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + icon::{Pixel, RgbaBuffer, PIXEL_SIZE}, + platform_impl::platform::{monitor, util}, +}; +use parking_lot::Mutex; +use std::{ + cmp::{Eq, PartialEq}, + error::Error, + fmt, io, + iter::once, + mem, + os::windows::ffi::OsStrExt, + path::Path, + ptr, + sync::{ + atomic::{AtomicPtr, Ordering}, + Arc, + }, +}; use winapi::{ ctypes::{c_int, wchar_t}, shared::{ - minwindef::{BYTE, LPARAM, WORD, WPARAM}, - windef::{HICON, HWND}, + minwindef::{LPARAM, UINT, WORD, WPARAM}, + windef::{HCURSOR, HICON, HICON__, HWND}, }, um::libloaderapi, - um::winuser, + um::{wingdi, winuser}, }; -use crate::dpi::PhysicalSize; -use crate::icon::*; - impl Pixel { fn to_bgra(&mut self) { mem::swap(&mut self.r, &mut self.b); } } -impl RgbaIcon { - fn into_windows_icon(self) -> Result { - let mut 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_mut_ptr() as *mut Pixel, pixel_count) }; - for pixel in pixels { - and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel - pixel.to_bgra(); - } - assert_eq!(and_mask.len(), pixel_count); - let handle = unsafe { - winuser::CreateIcon( - ptr::null_mut(), - self.width as c_int, - self.height as c_int, +impl RgbaBuffer> { + fn into_windows_icon( + self, + hot_spot: Option>, + ) -> Result { + unsafe { + let mut rgba = self.rgba; + let pixel_count = rgba.len() / PIXEL_SIZE; + let mut and_mask = Vec::with_capacity(pixel_count); + let pixels = + std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count); + for pixel in pixels { + and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel + pixel.to_bgra(); + } + assert_eq!(and_mask.len(), pixel_count); + + let width = self.size.width as c_int; + let height = self.size.height as c_int; + let and_bitmap = wingdi::CreateBitmap( + width, + height, 1, - (PIXEL_SIZE * 8) as BYTE, - and_mask.as_ptr() as *const BYTE, - rgba.as_ptr() as *const BYTE, - ) as HICON - }; - if !handle.is_null() { - Ok(WinIcon::from_handle(handle)) - } else { - Err(BadIcon::OsError(io::Error::last_os_error())) + (PIXEL_SIZE * 8) as UINT, + and_mask.as_ptr() as *const _, + ); + let color_bitmap = wingdi::CreateBitmap( + width, + height, + 1, + (PIXEL_SIZE * 8) as UINT, + rgba.as_ptr() as *const _, + ); + + let mut icon_info = winuser::ICONINFO { + // if it's None then it's a window icon, other wise it's a cursor + fIcon: hot_spot.is_none() as _, + xHotspot: hot_spot.map(|h| h.x).unwrap_or(0), + yHotspot: hot_spot.map(|h| h.y).unwrap_or(0), + hbmMask: and_bitmap, + hbmColor: color_bitmap, + }; + let handle = winuser::CreateIconIndirect(&mut icon_info); + + wingdi::DeleteObject(and_bitmap as _); + wingdi::DeleteObject(color_bitmap as _); + + if !handle.is_null() { + Ok(handle) + } else { + Err(io::Error::last_os_error()) + } } } } +#[derive(Debug, PartialEq, Eq)] +enum RaiiIcon { + Path(PathIcon), + Resource(ResourceIcon), + Single(HICON), + Function(FunctionIcon), +} + +unsafe impl Send for RaiiIcon {} +unsafe impl Sync for RaiiIcon {} + #[derive(Debug)] -pub enum IconType { - Small = winuser::ICON_SMALL as isize, - Big = winuser::ICON_BIG as isize, +struct PathIcon { + wide_path: Vec, + icon_set: LazyIconSet, } #[derive(Debug)] -struct RaiiIcon { - handle: HICON, +struct ResourceIcon { + resource_id: WORD, + icon_set: LazyIconSet, } -#[derive(Clone)] -pub struct WinIcon { - inner: Arc, +struct FunctionIcon { + get_icon: Box< + Mutex< + dyn FnMut( + PhysicalSize, + f64, + ) -> Result< + (RgbaBuffer>, Option>), + Box, + >, + >, + >, + icon_set: LazyIconSet, +} + +impl Eq for PathIcon {} +impl PartialEq for PathIcon { + fn eq(&self, other: &Self) -> bool { + self.wide_path == other.wide_path + } +} + +impl Eq for ResourceIcon {} +impl PartialEq for ResourceIcon { + fn eq(&self, other: &Self) -> bool { + self.resource_id == other.resource_id + } +} + +impl Eq for FunctionIcon {} +impl PartialEq for FunctionIcon { + fn eq(&self, other: &Self) -> bool { + &*self.get_icon as *const _ == &*other.get_icon as *const _ + } +} + +impl fmt::Debug for FunctionIcon { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_tuple("FunctionIcon") + .field(&(&*self.get_icon as *const Mutex<_>)) + .finish() + } +} + +type AtomicHICON = AtomicPtr; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum IconType { + WindowIcon = winuser::IMAGE_ICON as isize, + CursorIcon = winuser::IMAGE_CURSOR as isize, +} + +#[derive(Default, Debug)] +struct LazyIconSet { + i_16: AtomicHICON, + i_24: AtomicHICON, + i_32: AtomicHICON, + i_48: AtomicHICON, + i_64: AtomicHICON, + i_96: AtomicHICON, + i_128: AtomicHICON, + i_256: AtomicHICON, } -unsafe impl Send for WinIcon {} - -impl WinIcon { - pub fn as_raw_handle(&self) -> HICON { - self.inner.handle - } - - pub fn from_path>( - path: P, - size: Option>, - ) -> Result { - let wide_path: Vec = path - .as_ref() - .as_os_str() - .encode_wide() - .chain(once(0)) - .collect(); - - // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size - let (width, height) = size.map(Into::into).unwrap_or((0, 0)); - - let handle = unsafe { - winuser::LoadImageW( - ptr::null_mut(), - wide_path.as_ptr() as *const wchar_t, - winuser::IMAGE_ICON, - width as c_int, - height as c_int, - winuser::LR_DEFAULTSIZE | winuser::LR_LOADFROMFILE, - ) as HICON +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IconSize { + I16, + I24, + I32, + I48, + I64, + I96, + I128, + I256, +} + +fn cursor_scale_factor() -> f64 { + let cursor_position = util::get_cursor_position(); + let monitor = monitor::monitor_from_position(cursor_position); + monitor.scale_factor() +} + +impl IconType { + fn as_image_type(&self) -> UINT { + *self as _ + } +} + +impl IconSize { + pub fn adjust_for_scale_factor(&self, scale_factor: f64) -> IconSize { + use IconSize::*; + + let num = match *self { + I16 => 16, + I24 => 24, + I32 => 32, + I48 => 48, + I64 => 64, + I96 => 96, + I128 => 128, + I256 => 256, }; - if !handle.is_null() { - Ok(WinIcon::from_handle(handle)) - } else { - Err(BadIcon::OsError(io::Error::last_os_error())) + let scaled_num = (num as f64 * scale_factor) as u32; + match scaled_num / 8 { + 0 | 1 | 2 => I16, + 3 => I24, + 4 | 5 => I32, + 6 | 7 => I48, + 8 | 9 | 10 | 11 => I64, + 12 | 13 | 14 | 15 => I96, + 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 => I128, + 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | _ => I256, } } +} + +impl LazyIconSet { + fn load_icon(&self, icon_size: IconSize, mut load_icon: F) -> Result + where + F: FnMut(c_int) -> Result, + { + use IconSize::*; + let attempt_order = match icon_size { + I16 => [I16, I24, I32, I48, I64, I96, I128, I256], + I24 => [I24, I16, I32, I48, I64, I96, I128, I256], + I32 => [I32, I24, I16, I48, I64, I96, I128, I256], + I48 => [I48, I32, I24, I16, I64, I96, I128, I256], + I64 => [I64, I48, I32, I24, I16, I96, I128, I256], + I96 => [I96, I64, I48, I32, I24, I16, I128, I256], + I128 => [I128, I96, I64, I48, I32, I24, I16, I256], + I256 => [I256, I128, I96, I64, I48, I32, I24, I16], + }; + let mut error = None; + for icon_size in attempt_order.iter().cloned() { + let (hicon, dim) = match icon_size { + I16 => (&self.i_16, 16), + I24 => (&self.i_24, 24), + I32 => (&self.i_32, 32), + I48 => (&self.i_48, 48), + I64 => (&self.i_64, 64), + I96 => (&self.i_96, 96), + I128 => (&self.i_128, 128), + I256 => (&self.i_256, 256), + }; + + let current_icon = hicon.load(Ordering::SeqCst); + let is_valid = |icon: HICON| !(icon.is_null() || icon == (1 as HICON)); + + if current_icon.is_null() { + match load_icon(dim) { + Ok(icon_loaded) => { + let old_icon = hicon.swap(icon_loaded, Ordering::SeqCst); + if is_valid(old_icon) { + unsafe { winuser::DestroyIcon(old_icon) }; + } + return Ok(icon_loaded); + } + Err(e) => { + warn!("could not load icon at size {0}x{0}: {1}", dim, e); + error = Some(e); + let old_icon = hicon.swap(1 as HICON, Ordering::SeqCst); + if is_valid(old_icon) { + unsafe { winuser::DestroyIcon(old_icon) }; + } + continue; + } + } + } else if current_icon == 1 as HICON { + continue; + } else { + return Ok(current_icon); + } + } + Err(error + .unwrap_or_else(|| io::Error::new(io::ErrorKind::Other, "icon loading alreay failed"))) + } +} + +impl PathIcon { + fn load_icon(&self, icon_size: IconSize, icon_type: IconType) -> Result { + self.icon_set.load_icon(icon_size, |dim| { + let icon = unsafe { + winuser::LoadImageW( + libloaderapi::GetModuleHandleW(ptr::null_mut()), + self.wide_path.as_ptr() as *const wchar_t, + icon_type.as_image_type(), + dim, + dim, + winuser::LR_LOADFROMFILE, + ) as HICON + }; + if icon.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(icon) + } + }) + } +} + +impl ResourceIcon { + fn load_icon(&self, icon_size: IconSize, icon_type: IconType) -> Result { + self.icon_set.load_icon(icon_size, |dim| { + let icon = unsafe { + winuser::LoadImageW( + libloaderapi::GetModuleHandleW(ptr::null_mut()), + winuser::MAKEINTRESOURCEW(self.resource_id), + icon_type.as_image_type(), + dim, + dim, + 0, + ) as HICON + }; + if icon.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(icon) + } + }) + } +} + +impl FunctionIcon { + fn load_icon(&self, icon_size: IconSize, scale_factor: f64) -> Result { + self.icon_set.load_icon(icon_size, |dim| { + let mut get_icon = self.get_icon.lock(); + let icon = (&mut *get_icon)(PhysicalSize::new(dim as u32, dim as u32), scale_factor); + icon.map_err(|mut e| { + if let Some(ioe) = e.downcast_mut::() { + mem::replace(ioe, io::Error::from_raw_os_error(0)) + } else { + io::Error::new(io::ErrorKind::Other, e) + } + }) + .and_then(|(rgba_icon, hot_spot)| rgba_icon.into_windows_icon(hot_spot)) + }) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct CustomWindowIcon { + inner: Arc, +} - pub fn from_resource( - resource_id: WORD, - size: Option>, - ) -> Result { - // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size - let (width, height) = size.map(Into::into).unwrap_or((0, 0)); - let handle = unsafe { - winuser::LoadImageW( - libloaderapi::GetModuleHandleW(ptr::null_mut()), - winuser::MAKEINTRESOURCEW(resource_id), - winuser::IMAGE_ICON, - width as c_int, - height as c_int, - winuser::LR_DEFAULTSIZE, - ) as HICON +#[derive(Clone, PartialEq, Eq)] +pub struct CustomCursorIcon { + inner: Arc, +} + +impl RaiiIcon { + fn from_path>(path: P, icon_type: IconType) -> Result { + let path = path.as_ref(); + let wide_path: Vec = path.as_os_str().encode_wide().chain(once(0)).collect(); + + let path_icon = PathIcon { + wide_path, + icon_set: LazyIconSet::default(), + }; + let icon_size = match icon_type { + IconType::WindowIcon => IconSize::I24.adjust_for_scale_factor(cursor_scale_factor()), + IconType::CursorIcon => IconSize::I32, + }; + path_icon.load_icon(icon_size, icon_type)?; + Ok(RaiiIcon::Path(path_icon)) + } + + fn from_resource(resource_id: WORD, icon_type: IconType) -> Result { + let resource_icon = ResourceIcon { + resource_id, + icon_set: LazyIconSet::default(), + }; + let icon_size = match icon_type { + IconType::WindowIcon => IconSize::I24.adjust_for_scale_factor(cursor_scale_factor()), + IconType::CursorIcon => IconSize::I32, }; - if !handle.is_null() { - Ok(WinIcon::from_handle(handle)) - } else { - Err(BadIcon::OsError(io::Error::last_os_error())) + resource_icon.load_icon(icon_size, icon_type)?; + Ok(RaiiIcon::Resource(resource_icon)) + } + + fn from_rgba(rgba: &[u8], size: PhysicalSize) -> Result { + Ok(RaiiIcon::Single( + RgbaBuffer::from_rgba(Box::from(rgba), size).into_windows_icon(None)?, + )) + } + + fn from_rgba_with_hot_spot( + rgba: &[u8], + size: PhysicalSize, + hot_spot: PhysicalPosition, + ) -> Result { + Ok(RaiiIcon::Single( + RgbaBuffer::from_rgba(Box::from(rgba), size).into_windows_icon(Some(hot_spot))?, + )) + } + + fn from_rgba_fn(mut get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result>, Box>, + { + let function_icon = FunctionIcon { + get_icon: Box::new(Mutex::new(move |size, scale_factor| { + Ok((get_icon(size, scale_factor)?, None)) + })), + icon_set: LazyIconSet::default(), + }; + RaiiIcon::Function(function_icon) + } + + fn from_rgba_fn_with_hot_spot(mut get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result< + (RgbaBuffer>, PhysicalPosition), + Box, + >, + { + let function_icon = FunctionIcon { + get_icon: Box::new(Mutex::new(move |size, scale_factor| { + let (rgba, hot_spot) = get_icon(size, scale_factor)?; + Ok((rgba, Some(hot_spot))) + })), + icon_set: LazyIconSet::default(), + }; + RaiiIcon::Function(function_icon) + } +} + +impl CustomWindowIcon { + pub fn from_rgba(rgba: &[u8], size: PhysicalSize) -> Result { + RaiiIcon::from_rgba(rgba, size).map(|i| CustomWindowIcon { inner: Arc::new(i) }) + } + + pub fn from_rgba_fn(get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result>, Box>, + { + CustomWindowIcon { + inner: Arc::new(RaiiIcon::from_rgba_fn(get_icon)), } } - 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 fn from_path>(path: P) -> Result { + RaiiIcon::from_path(path, IconType::WindowIcon) + .map(|i| CustomWindowIcon { inner: Arc::new(i) }) + } + + pub fn from_resource(resource_id: WORD) -> Result { + RaiiIcon::from_resource(resource_id, IconType::WindowIcon) + .map(|i| CustomWindowIcon { inner: Arc::new(i) }) + } + + fn as_raw_icon_handle(&self, icon_size: IconSize, scale_factor: f64) -> Option { + let icon_size = icon_size.adjust_for_scale_factor(scale_factor); + match &*self.inner { + RaiiIcon::Path(icon) => icon.load_icon(icon_size, IconType::WindowIcon).ok(), + RaiiIcon::Resource(icon) => icon.load_icon(icon_size, IconType::WindowIcon).ok(), + RaiiIcon::Single(icon) => Some(*icon), + RaiiIcon::Function(icon) => icon.load_icon(icon_size, scale_factor).ok(), + } } - pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { + pub fn set_for_window(&self, hwnd: HWND, scale_factor: f64) { unsafe { + let small_icon = self + .as_raw_icon_handle(IconSize::I16, scale_factor) + .map(|i| i as LPARAM) + .unwrap_or(0); + let big_icon = self + .as_raw_icon_handle(IconSize::I24, scale_factor) + .map(|i| i as LPARAM) + .unwrap_or(0); winuser::SendMessageW( hwnd, winuser::WM_SETICON, - icon_type as WPARAM, - self.as_raw_handle() as LPARAM, + winuser::ICON_SMALL as WPARAM, + small_icon as LPARAM, + ); + winuser::SendMessageW( + hwnd, + winuser::WM_SETICON, + winuser::ICON_BIG as WPARAM, + big_icon as LPARAM, ); } } +} - fn from_handle(handle: HICON) -> Self { - Self { - inner: Arc::new(RaiiIcon { handle }), +impl CustomCursorIcon { + pub fn from_rgba( + rgba: &[u8], + size: PhysicalSize, + hot_spot: PhysicalPosition, + ) -> Result { + RaiiIcon::from_rgba_with_hot_spot(rgba, size, hot_spot) + .map(|i| CustomCursorIcon { inner: Arc::new(i) }) + } + + pub fn from_rgba_fn(get_icon: F) -> Self + where + F: 'static + + FnMut( + PhysicalSize, + f64, + ) -> Result< + (RgbaBuffer>, PhysicalPosition), + Box, + >, + { + CustomCursorIcon { + inner: Arc::new(RaiiIcon::from_rgba_fn_with_hot_spot(get_icon)), + } + } + + pub fn from_path>(path: P) -> Result { + RaiiIcon::from_path(path, IconType::CursorIcon) + .map(|i| CustomCursorIcon { inner: Arc::new(i) }) + } + + pub fn from_resource(resource_id: WORD) -> Result { + RaiiIcon::from_resource(resource_id, IconType::CursorIcon) + .map(|i| CustomCursorIcon { inner: Arc::new(i) }) + } + + pub fn as_raw_scaled_cursor_handle(&self) -> Option { + let scale_factor = cursor_scale_factor(); + let cursor_size = IconSize::I32.adjust_for_scale_factor(scale_factor); + + match &*self.inner { + RaiiIcon::Path(icon) => icon.load_icon(cursor_size, IconType::CursorIcon).ok(), + RaiiIcon::Resource(icon) => icon.load_icon(cursor_size, IconType::CursorIcon).ok(), + RaiiIcon::Single(icon) => Some(*icon), + RaiiIcon::Function(icon) => icon.load_icon(cursor_size, scale_factor).ok(), } } } -impl Drop for RaiiIcon { +impl Drop for LazyIconSet { fn drop(&mut self) { - unsafe { winuser::DestroyIcon(self.handle) }; + unsafe { + let LazyIconSet { + i_16, + i_24, + i_32, + i_48, + i_64, + i_96, + i_128, + i_256, + } = self; + + let i_16 = i_16.load(Ordering::SeqCst); + if !i_16.is_null() { + winuser::DestroyIcon(i_16); + } + let i_24 = i_24.load(Ordering::SeqCst); + if !i_24.is_null() { + winuser::DestroyIcon(i_24); + } + let i_32 = i_32.load(Ordering::SeqCst); + if !i_32.is_null() { + winuser::DestroyIcon(i_32); + } + let i_48 = i_48.load(Ordering::SeqCst); + if !i_48.is_null() { + winuser::DestroyIcon(i_48); + } + let i_64 = i_64.load(Ordering::SeqCst); + if !i_64.is_null() { + winuser::DestroyIcon(i_64); + } + let i_96 = i_96.load(Ordering::SeqCst); + if !i_96.is_null() { + winuser::DestroyIcon(i_96); + } + let i_128 = i_128.load(Ordering::SeqCst); + if !i_128.is_null() { + winuser::DestroyIcon(i_128); + } + let i_256 = i_256.load(Ordering::SeqCst); + if !i_256.is_null() { + winuser::DestroyIcon(i_256); + } + } + } +} + +impl fmt::Debug for CustomWindowIcon { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + (*self.inner).fmt(formatter) } } -impl fmt::Debug for WinIcon { +impl fmt::Debug for CustomCursorIcon { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { (*self.inner).fmt(formatter) } } -pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { +pub fn unset_for_window(hwnd: HWND) { unsafe { - winuser::SendMessageW(hwnd, winuser::WM_SETICON, icon_type as WPARAM, 0 as LPARAM); + winuser::SendMessageW(hwnd, winuser::WM_SETICON, winuser::ICON_SMALL as WPARAM, 0); + winuser::SendMessageW(hwnd, winuser::WM_SETICON, winuser::ICON_BIG as WPARAM, 0); } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 498cdfb80c..3fe3f03755 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -4,20 +4,20 @@ use winapi::{self, shared::windef::HWND}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, - icon::WinIcon, + icon::CustomWindowIcon, monitor::{MonitorHandle, VideoMode}, window::Window, }; -pub use self::icon::WinIcon as PlatformIcon; +pub use self::icon::{ + CustomCursorIcon as PlatformCustomCursorIcon, CustomWindowIcon as PlatformCustomWindowIcon, +}; use crate::event::DeviceId as RootDeviceId; -use crate::icon::Icon; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub parent: Option, - pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, } @@ -26,7 +26,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { parent: None, - taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, } diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 7fa4c73dc5..3104a7bde7 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -126,6 +126,15 @@ pub fn current_monitor(hwnd: HWND) -> MonitorHandle { MonitorHandle::new(hmonitor) } +pub fn monitor_from_position(position: PhysicalPosition) -> MonitorHandle { + let point = POINT { + x: position.x, + y: position.y, + }; + let hmonitor = unsafe { winuser::MonitorFromPoint(point, winuser::MONITOR_DEFAULTTONEAREST) }; + MonitorHandle::new(hmonitor) +} + impl Window { pub fn available_monitors(&self) -> VecDeque { available_monitors() diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 5faaae3e68..65e397f67e 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -6,12 +6,15 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; -use crate::{dpi::PhysicalSize, window::CursorIcon}; +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + window::CursorIcon, +}; use winapi::{ ctypes::wchar_t, shared::{ minwindef::{BOOL, DWORD, UINT}, - windef::{DPI_AWARENESS_CONTEXT, HMONITOR, HWND, LPRECT, RECT}, + windef::{DPI_AWARENESS_CONTEXT, HCURSOR, HMONITOR, HWND, LPRECT, RECT}, }, um::{ libloaderapi::{GetProcAddress, LoadLibraryA}, @@ -159,6 +162,14 @@ pub fn set_cursor_hidden(hidden: bool) { } } +pub fn get_cursor_position() -> PhysicalPosition { + unsafe { + let mut pos = mem::zeroed(); + winuser::GetCursorPos(&mut pos); + PhysicalPosition::new(pos.x, pos.y) + } +} + pub fn get_cursor_clip() -> Result { unsafe { let mut rect: RECT = mem::zeroed(); @@ -197,35 +208,45 @@ pub fn is_focused(window: HWND) -> bool { } impl CursorIcon { - pub(crate) fn to_windows_cursor(self) -> *const wchar_t { - match self { - CursorIcon::Arrow | CursorIcon::Default => winuser::IDC_ARROW, - CursorIcon::Hand => winuser::IDC_HAND, - CursorIcon::Crosshair => winuser::IDC_CROSS, - CursorIcon::Text | CursorIcon::VerticalText => winuser::IDC_IBEAM, - CursorIcon::NotAllowed | CursorIcon::NoDrop => winuser::IDC_NO, + pub(crate) fn to_windows_cursor_scaled(&self) -> HCURSOR { + let hcursor = match self { + CursorIcon::Arrow | CursorIcon::Default => Ok(winuser::IDC_ARROW), + CursorIcon::Hand => Ok(winuser::IDC_HAND), + CursorIcon::Crosshair => Ok(winuser::IDC_CROSS), + CursorIcon::Text | CursorIcon::VerticalText => Ok(winuser::IDC_IBEAM), + CursorIcon::NotAllowed | CursorIcon::NoDrop => Ok(winuser::IDC_NO), CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => { - winuser::IDC_SIZEALL + Ok(winuser::IDC_SIZEALL) } CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize - | CursorIcon::ColResize => winuser::IDC_SIZEWE, + | CursorIcon::ColResize => Ok(winuser::IDC_SIZEWE), CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize - | CursorIcon::RowResize => winuser::IDC_SIZENS, + | CursorIcon::RowResize => Ok(winuser::IDC_SIZENS), CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => { - winuser::IDC_SIZENESW + Ok(winuser::IDC_SIZENESW) } CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => { - winuser::IDC_SIZENWSE + Ok(winuser::IDC_SIZENWSE) } - CursorIcon::Wait => winuser::IDC_WAIT, - CursorIcon::Progress => winuser::IDC_APPSTARTING, - CursorIcon::Help => winuser::IDC_HELP, - _ => winuser::IDC_ARROW, // use arrow for the missing cases. - } + CursorIcon::Wait => Ok(winuser::IDC_WAIT), + CursorIcon::Progress => Ok(winuser::IDC_APPSTARTING), + CursorIcon::Help => Ok(winuser::IDC_HELP), + CursorIcon::Custom(icon) => Err(icon), + _ => Ok(winuser::IDC_ARROW), // use arrow for the missing cases). + }; + + hcursor.map_or_else( + |icon| { + icon.inner + .as_raw_scaled_cursor_handle() + .unwrap_or(unsafe { winuser::LoadCursorW(ptr::null_mut(), winuser::IDC_ARROW) }) + }, + |cursor_name| unsafe { winuser::LoadCursorW(ptr::null_mut(), cursor_name) }, + ) } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0f4dcf8bb8..1e50fb886d 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -32,15 +32,14 @@ use winapi::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, - icon::Icon, + icon::CustomWindowIcon, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_dark_mode, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, - icon::{self, IconType}, - monitor, util, + icon, monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, @@ -290,9 +289,9 @@ impl Window { #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.window_state.lock().mouse.cursor = cursor; + self.window_state.lock().mouse.cursor = cursor.clone(); self.thread_executor.execute_in_thread(move || unsafe { - let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); + let cursor = cursor.to_windows_cursor_scaled(); winuser::SetCursor(cursor); }); } @@ -584,29 +583,17 @@ impl Window { } #[inline] - pub fn set_window_icon(&self, window_icon: Option) { + pub fn set_window_icon(&self, window_icon: Option) { if let Some(ref window_icon) = window_icon { window_icon .inner - .set_for_window(self.window.0, IconType::Small); + .set_for_window(self.window.0, self.scale_factor()); } else { - icon::unset_for_window(self.window.0, IconType::Small); + icon::unset_for_window(self.window.0); } self.window_state.lock().window_icon = window_icon; } - #[inline] - pub fn set_taskbar_icon(&self, taskbar_icon: Option) { - if let Some(ref taskbar_icon) = taskbar_icon { - taskbar_icon - .inner - .set_for_window(self.window.0, IconType::Big); - } else { - icon::unset_for_window(self.window.0, IconType::Big); - } - self.window_state.lock().taskbar_icon = taskbar_icon; - } - #[inline] pub fn set_ime_position(&self, _position: Position) { unimplemented!(); @@ -652,7 +639,7 @@ unsafe fn init( .collect::>(); // registering the window class - let class_name = register_window_class(&attributes.window_icon, &pl_attribs.taskbar_icon); + let class_name = &*WINDOW_CLASS; let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::DECORATIONS, attributes.decorations); @@ -742,8 +729,8 @@ unsafe fn init( let window_state = { let window_state = WindowState::new( - &attributes, - pl_attribs.taskbar_icon, + attributes.min_inner_size, + attributes.max_inner_size, scale_factor, dark_mode, ); @@ -767,6 +754,7 @@ unsafe fn init( // `Window::set_inner_size` changes MAXIMIZED to false. win.set_maximized(true); } + win.set_window_icon(attributes.window_icon); win.set_visible(attributes.visible); if let Some(_) = attributes.fullscreen { @@ -777,46 +765,36 @@ unsafe fn init( Ok(win) } -unsafe fn register_window_class( - window_icon: &Option, - taskbar_icon: &Option, -) -> Vec { - let class_name: Vec<_> = OsStr::new("Window Class") - .encode_wide() - .chain(Some(0).into_iter()) - .collect(); - - let h_icon = taskbar_icon - .as_ref() - .map(|icon| icon.inner.as_raw_handle()) - .unwrap_or(ptr::null_mut()); - let h_icon_small = window_icon - .as_ref() - .map(|icon| icon.inner.as_raw_handle()) - .unwrap_or(ptr::null_mut()); - - let class = winuser::WNDCLASSEXW { - cbSize: mem::size_of::() as UINT, - style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC, - lpfnWndProc: Some(winuser::DefWindowProcW), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: libloaderapi::GetModuleHandleW(ptr::null()), - hIcon: h_icon, - hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly - hbrBackground: ptr::null_mut(), - lpszMenuName: ptr::null(), - lpszClassName: class_name.as_ptr(), - hIconSm: h_icon_small, - }; +lazy_static! { + static ref WINDOW_CLASS: Vec = unsafe { + let class_name: Vec<_> = OsStr::new("Winit Window Class") + .encode_wide() + .chain(Some(0).into_iter()) + .collect(); + + let class = winuser::WNDCLASSEXW { + cbSize: mem::size_of::() as UINT, + style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC, + lpfnWndProc: Some(winuser::DefWindowProcW), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: libloaderapi::GetModuleHandleW(ptr::null()), + hIcon: ptr::null_mut(), + hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly + hbrBackground: ptr::null_mut(), + lpszMenuName: ptr::null(), + lpszClassName: class_name.as_ptr(), + hIconSm: ptr::null_mut(), + }; - // We ignore errors because registering the same window class twice would trigger - // an error, and because errors here are detected during CreateWindowEx anyway. - // Also since there is no weird element in the struct, there is no reason for this - // call to fail. - winuser::RegisterClassExW(&class); + // We ignore errors because registering the same window class twice would trigger + // an error, and because errors here are detected during CreateWindowEx anyway. + // Also since there is no weird element in the struct, there is no reason for this + // call to fail. + winuser::RegisterClassExW(&class); - class_name + class_name + }; } struct ComInitialized(*mut ()); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 124090813c..a499cc0144 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,9 +1,9 @@ use crate::{ dpi::{PhysicalPosition, Size}, event::ModifiersState, - icon::Icon, + icon::CustomWindowIcon, platform_impl::platform::{event_loop, util}, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen}, }; use parking_lot::MutexGuard; use std::{io, ptr}; @@ -23,8 +23,7 @@ pub struct WindowState { pub min_size: Option, pub max_size: Option, - pub window_icon: Option, - pub taskbar_icon: Option, + pub window_icon: Option, pub saved_window: Option, pub scale_factor: f64, @@ -97,8 +96,8 @@ bitflags! { impl WindowState { pub fn new( - attributes: &WindowAttributes, - taskbar_icon: Option, + min_size: Option, + max_size: Option, scale_factor: f64, is_dark_mode: bool, ) -> WindowState { @@ -110,11 +109,10 @@ impl WindowState { last_position: None, }, - min_size: attributes.min_inner_size, - max_size: attributes.max_inner_size, + min_size, + max_size, - window_icon: attributes.window_icon.clone(), - taskbar_icon, + window_icon: None, saved_window: None, scale_factor, diff --git a/src/window.rs b/src/window.rs index 000adef735..f86ec7aa2a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,7 +9,7 @@ use crate::{ platform_impl, }; -pub use crate::icon::{BadIcon, Icon}; +pub use crate::icon::{CustomCursorIcon, CustomWindowIcon, RgbaBuffer}; /// Represents a window. /// @@ -160,7 +160,7 @@ pub struct WindowAttributes { /// The window icon. /// /// The default is `None`. - pub window_icon: Option, + pub window_icon: Option, } impl Default for WindowAttributes { @@ -313,7 +313,7 @@ impl WindowBuilder { /// /// [`Window::set_window_icon`]: crate::window::Window::set_window_icon #[inline] - pub fn with_window_icon(mut self, window_icon: Option) -> Self { + pub fn with_window_icon(mut self, window_icon: Option) -> Self { self.window.window_icon = window_icon; self } @@ -669,7 +669,7 @@ impl Window { /// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// said, it's usually in the same ballpark as on Windows. #[inline] - pub fn set_window_icon(&self, window_icon: Option) { + pub fn set_window_icon(&self, window_icon: Option) { self.window.set_window_icon(window_icon) } @@ -793,7 +793,7 @@ unsafe impl raw_window_handle::HasRawWindowHandle for Window { } /// Describes the appearance of the mouse cursor. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CursorIcon { /// The platform-dependent default cursor. @@ -849,6 +849,8 @@ pub enum CursorIcon { NwseResize, ColResize, RowResize, + #[cfg_attr(feature = "serde", serde(skip))] + Custom(CustomCursorIcon), } impl Default for CursorIcon { diff --git a/tests/send_objects.rs b/tests/send_objects.rs index 252b271a4b..75f525c70a 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -25,3 +25,8 @@ fn ids_send() { needs_send::(); needs_send::(); } + +#[test] +fn misc_send() { + needs_send::(); +} diff --git a/tests/sync_object.rs b/tests/sync_object.rs index 56524cb2c4..71729eef68 100644 --- a/tests/sync_object.rs +++ b/tests/sync_object.rs @@ -7,3 +7,8 @@ fn window_sync() { // ensures that `winit::Window` implements `Sync` needs_sync::(); } + +#[test] +fn misc_sync() { + needs_sync::(); +}