rio_window/
icon.rs

1use crate::platform_impl::PlatformIcon;
2use std::error::Error;
3use std::{fmt, io, mem};
4
5#[repr(C)]
6#[derive(Debug)]
7pub(crate) struct Pixel {
8    pub(crate) r: u8,
9    pub(crate) g: u8,
10    pub(crate) b: u8,
11    pub(crate) a: u8,
12}
13
14pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
15
16#[derive(Debug)]
17/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
18pub enum BadIcon {
19    /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
20    /// safely interpreted as 32bpp RGBA pixels.
21    ByteCountNotDivisibleBy4 { byte_count: usize },
22    /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
23    /// At least one of your arguments is incorrect.
24    DimensionsVsPixelCount {
25        width: u32,
26        height: u32,
27        width_x_height: usize,
28        pixel_count: usize,
29    },
30    /// Produced when underlying OS functionality failed to create the icon
31    OsError(io::Error),
32}
33
34impl fmt::Display for BadIcon {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(
38                f,
39                "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
40                 it impossible to interpret as 32bpp RGBA pixels.",
41            ),
42            BadIcon::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
43                write!(
44                    f,
45                    "The specified dimensions ({width:?}x{height:?}) don't match the number of \
46                     pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
47                     dimensions, the expected pixel count is {width_x_height:?}.",
48                )
49            },
50            BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"),
51        }
52    }
53}
54
55impl Error for BadIcon {}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub(crate) struct RgbaIcon {
59    pub(crate) rgba: Vec<u8>,
60    pub(crate) width: u32,
61    pub(crate) height: u32,
62}
63
64/// For platforms which don't have window icons (e.g. web)
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub(crate) struct NoIcon;
67
68#[allow(dead_code)] // These are not used on every platform
69mod constructors {
70    use super::*;
71
72    impl RgbaIcon {
73        pub fn from_rgba(
74            rgba: Vec<u8>,
75            width: u32,
76            height: u32,
77        ) -> Result<Self, BadIcon> {
78            if rgba.len() % PIXEL_SIZE != 0 {
79                return Err(BadIcon::ByteCountNotDivisibleBy4 {
80                    byte_count: rgba.len(),
81                });
82            }
83            let pixel_count = rgba.len() / PIXEL_SIZE;
84            if pixel_count != (width * height) as usize {
85                Err(BadIcon::DimensionsVsPixelCount {
86                    width,
87                    height,
88                    width_x_height: (width * height) as usize,
89                    pixel_count,
90                })
91            } else {
92                Ok(RgbaIcon {
93                    rgba,
94                    width,
95                    height,
96                })
97            }
98        }
99    }
100
101    impl NoIcon {
102        pub fn from_rgba(
103            rgba: Vec<u8>,
104            width: u32,
105            height: u32,
106        ) -> Result<Self, BadIcon> {
107            // Create the rgba icon anyway to validate the input
108            let _ = RgbaIcon::from_rgba(rgba, width, height)?;
109            Ok(NoIcon)
110        }
111    }
112}
113
114/// An icon used for the window titlebar, taskbar, etc.
115#[derive(Clone)]
116pub struct Icon {
117    pub(crate) inner: PlatformIcon,
118}
119
120impl fmt::Debug for Icon {
121    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
122        fmt::Debug::fmt(&self.inner, formatter)
123    }
124}
125
126impl Icon {
127    /// Creates an icon from 32bpp RGBA data.
128    ///
129    /// The length of `rgba` must be divisible by 4, and `width * height` must equal
130    /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
131    pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
132        let _span =
133            tracing::debug_span!("rio_window::Icon::from_rgba", width, height).entered();
134
135        Ok(Icon {
136            inner: PlatformIcon::from_rgba(rgba, width, height)?,
137        })
138    }
139}