Skip to content

Commit 098fd5d

Browse files
davidhewittOsspial
andauthored
Add ability to create Icons from embedded resources on Windows (#1410)
* Add IconExtWindows trait * Move changelog entries to unreleased category Co-authored-by: Osspial <[email protected]>
1 parent 2f27f64 commit 098fd5d

File tree

14 files changed

+258
-118
lines changed

14 files changed

+258
-118
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
- On Wayland, Hide CSD for fullscreen windows.
1313
- On Windows, ignore spurious mouse move messages.
1414
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
15+
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
16+
- Add `BadIcon::OsError` variant for when OS icon functionality fails
1517
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
1618

1719
# 0.21.0 (2020-02-04)

src/icon.rs

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use std::{error::Error, fmt, mem};
1+
use crate::platform_impl::PlatformIcon;
2+
use std::{error::Error, fmt, io, mem};
23

34
#[repr(C)]
45
#[derive(Debug)]
@@ -11,7 +12,7 @@ pub(crate) struct Pixel {
1112

1213
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
1314

14-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15+
#[derive(Debug)]
1516
/// An error produced when using `Icon::from_rgba` with invalid arguments.
1617
pub enum BadIcon {
1718
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
@@ -25,6 +26,8 @@ pub enum BadIcon {
2526
width_x_height: usize,
2627
pixel_count: usize,
2728
},
29+
/// Produced when underlying OS functionality failed to create the icon
30+
OsError(io::Error),
2831
}
2932

3033
impl fmt::Display for BadIcon {
@@ -43,6 +46,7 @@ impl fmt::Display for BadIcon {
4346
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
4447
width, height, pixel_count, width_x_height,
4548
),
49+
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
4650
}
4751
}
4852
}
@@ -54,38 +58,78 @@ impl Error for BadIcon {
5458
}
5559

5660
#[derive(Debug, Clone, PartialEq, Eq)]
57-
/// An icon used for the window titlebar, taskbar, etc.
58-
pub struct Icon {
61+
pub(crate) struct RgbaIcon {
5962
pub(crate) rgba: Vec<u8>,
6063
pub(crate) width: u32,
6164
pub(crate) height: u32,
6265
}
6366

67+
/// For platforms which don't have window icons (e.g. web)
68+
#[derive(Debug, Clone, PartialEq, Eq)]
69+
pub(crate) struct NoIcon;
70+
71+
#[allow(dead_code)] // These are not used on every platform
72+
mod constructors {
73+
use super::*;
74+
75+
impl RgbaIcon {
76+
/// Creates an `Icon` from 32bpp RGBA data.
77+
///
78+
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
79+
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
80+
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
81+
if rgba.len() % PIXEL_SIZE != 0 {
82+
return Err(BadIcon::ByteCountNotDivisibleBy4 {
83+
byte_count: rgba.len(),
84+
});
85+
}
86+
let pixel_count = rgba.len() / PIXEL_SIZE;
87+
if pixel_count != (width * height) as usize {
88+
Err(BadIcon::DimensionsVsPixelCount {
89+
width,
90+
height,
91+
width_x_height: (width * height) as usize,
92+
pixel_count,
93+
})
94+
} else {
95+
Ok(RgbaIcon {
96+
rgba,
97+
width,
98+
height,
99+
})
100+
}
101+
}
102+
}
103+
104+
impl NoIcon {
105+
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
106+
// Create the rgba icon anyway to validate the input
107+
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
108+
Ok(NoIcon)
109+
}
110+
}
111+
}
112+
113+
/// An icon used for the window titlebar, taskbar, etc.
114+
#[derive(Clone)]
115+
pub struct Icon {
116+
pub(crate) inner: PlatformIcon,
117+
}
118+
119+
impl fmt::Debug for Icon {
120+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
121+
fmt::Debug::fmt(&self.inner, formatter)
122+
}
123+
}
124+
64125
impl Icon {
65126
/// Creates an `Icon` from 32bpp RGBA data.
66127
///
67128
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
68129
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
69130
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
70-
if rgba.len() % PIXEL_SIZE != 0 {
71-
return Err(BadIcon::ByteCountNotDivisibleBy4 {
72-
byte_count: rgba.len(),
73-
});
74-
}
75-
let pixel_count = rgba.len() / PIXEL_SIZE;
76-
if pixel_count != (width * height) as usize {
77-
Err(BadIcon::DimensionsVsPixelCount {
78-
width,
79-
height,
80-
width_x_height: (width * height) as usize,
81-
pixel_count,
82-
})
83-
} else {
84-
Ok(Icon {
85-
rgba,
86-
width,
87-
height,
88-
})
89-
}
131+
Ok(Icon {
132+
inner: PlatformIcon::from_rgba(rgba, width, height)?,
133+
})
90134
}
91135
}

src/platform/windows.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
#![cfg(target_os = "windows")]
22

33
use std::os::raw::c_void;
4+
use std::path::Path;
45

56
use libc;
7+
use winapi::shared::minwindef::WORD;
68
use winapi::shared::windef::HWND;
79

810
use crate::{
11+
dpi::PhysicalSize,
912
event::DeviceId,
1013
event_loop::EventLoop,
1114
monitor::MonitorHandle,
12-
platform_impl::EventLoop as WindowsEventLoop,
13-
window::{Icon, Window, WindowBuilder},
15+
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
16+
window::{BadIcon, Icon, Window, WindowBuilder},
1417
};
1518

1619
/// Additional methods on `EventLoop` that are specific to Windows.
@@ -171,3 +174,40 @@ impl DeviceIdExtWindows for DeviceId {
171174
self.0.persistent_identifier()
172175
}
173176
}
177+
178+
/// Additional methods on `Icon` that are specific to Windows.
179+
pub trait IconExtWindows: Sized {
180+
/// Create an icon from a file path.
181+
///
182+
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
183+
/// icon size from the file.
184+
///
185+
/// In cases where the specified size does not exist in the file, Windows may perform scaling
186+
/// to get an icon of the desired size.
187+
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
188+
-> Result<Self, BadIcon>;
189+
190+
/// Create an icon from a resource embedded in this executable or library.
191+
///
192+
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
193+
/// icon size from the file.
194+
///
195+
/// In cases where the specified size does not exist in the file, Windows may perform scaling
196+
/// to get an icon of the desired size.
197+
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
198+
}
199+
200+
impl IconExtWindows for Icon {
201+
fn from_path<P: AsRef<Path>>(
202+
path: P,
203+
size: Option<PhysicalSize<u32>>,
204+
) -> Result<Self, BadIcon> {
205+
let win_icon = WinIcon::from_path(path, size)?;
206+
Ok(Icon { inner: win_icon })
207+
}
208+
209+
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
210+
let win_icon = WinIcon::from_resource(ordinal, size)?;
211+
Ok(Icon { inner: win_icon })
212+
}
213+
}

src/platform_impl/android/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use crate::{
2222
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
2323
use CreationError::OsError;
2424

25+
pub(crate) use crate::icon::NoIcon as PlatformIcon;
26+
2527
pub type OsError = std::io::Error;
2628

2729
pub struct EventLoop {

src/platform_impl/ios/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ pub use self::{
8383
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
8484
};
8585

86+
pub(crate) use crate::icon::NoIcon as PlatformIcon;
87+
8688
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8789
pub struct DeviceId {
8890
uiscreen: ffi::id,

src/platform_impl/linux/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use crate::{
1818
window::{CursorIcon, Fullscreen, WindowAttributes},
1919
};
2020

21+
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
22+
2123
pub mod wayland;
2224
pub mod x11;
2325

src/platform_impl/linux/x11/util/icon.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::*;
2-
use crate::window::{Icon, Pixel, PIXEL_SIZE};
2+
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
33

44
impl Pixel {
55
pub fn to_packed_argb(&self) -> Cardinal {
@@ -18,13 +18,14 @@ impl Pixel {
1818

1919
impl Icon {
2020
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
21-
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
22-
let pixel_count = self.rgba.len() / PIXEL_SIZE;
23-
assert_eq!(pixel_count, (self.width * self.height) as usize);
21+
let rgba_icon = &self.inner;
22+
assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0);
23+
let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE;
24+
assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize);
2425
let mut data = Vec::with_capacity(pixel_count);
25-
data.push(self.width as Cardinal);
26-
data.push(self.height as Cardinal);
27-
let pixels = self.rgba.as_ptr() as *const Pixel;
26+
data.push(rgba_icon.width as Cardinal);
27+
data.push(rgba_icon.height as Cardinal);
28+
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
2829
for pixel_index in 0..pixel_count {
2930
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
3031
data.push(pixel.to_packed_argb());

src/platform_impl/macos/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use crate::{
2525
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
2626
};
2727

28+
pub(crate) use crate::icon::NoIcon as PlatformIcon;
29+
2830
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2931
pub struct DeviceId;
3032

src/platform_impl/web/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,5 @@ pub use self::window::{
4444
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
4545
Window,
4646
};
47+
48+
pub(crate) use crate::icon::NoIcon as PlatformIcon;

0 commit comments

Comments
 (0)