diff options
-rw-r--r-- | native/Cargo.toml | 1 | ||||
-rw-r--r-- | native/src/window.rs | 3 | ||||
-rw-r--r-- | native/src/window/action.rs | 21 | ||||
-rw-r--r-- | native/src/window/icon.rs | 80 | ||||
-rw-r--r-- | src/window/icon.rs | 209 | ||||
-rw-r--r-- | winit/src/application.rs | 3 | ||||
-rw-r--r-- | winit/src/conversion.rs | 9 | ||||
-rw-r--r-- | winit/src/settings.rs | 7 | ||||
-rw-r--r-- | winit/src/window.rs | 9 |
9 files changed, 178 insertions, 164 deletions
diff --git a/native/Cargo.toml b/native/Cargo.toml index 3f92783e..1eedf0da 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,6 +14,7 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" +thiserror = "1" [dependencies.iced_core] version = "0.8" diff --git a/native/src/window.rs b/native/src/window.rs index a5cdc8ce..1ae89dba 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -5,8 +5,11 @@ mod mode; mod redraw_request; mod user_attention; +pub mod icon; + pub use action::Action; pub use event::Event; +pub use icon::Icon; pub use mode::Mode; pub use redraw_request::RedrawRequest; pub use user_attention::UserAttention; diff --git a/native/src/window/action.rs b/native/src/window/action.rs index ce36d129..095a8eec 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,4 +1,4 @@ -use crate::window::{Mode, UserAttention}; +use crate::window::{Icon, Mode, UserAttention}; use iced_futures::MaybeSend; use std::fmt; @@ -78,6 +78,21 @@ pub enum Action<T> { ChangeAlwaysOnTop(bool), /// Fetch an identifier unique to the window. FetchId(Box<dyn FnOnce(u64) -> T + 'static>), + /// Changes the window [`Icon`]. + /// + /// On Windows and X11, this is typically the small icon in the top-left + /// corner of the titlebar. + /// + /// ## Platform-specific + /// + /// - **Web / Wayland / macOS:** Unsupported. + /// + /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's + /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. + /// + /// - **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. + ChangeIcon(Icon), } impl<T> Action<T> { @@ -108,6 +123,7 @@ impl<T> Action<T> { Action::ChangeAlwaysOnTop(on_top) } Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))), + Self::ChangeIcon(icon) => Action::ChangeIcon(icon), } } } @@ -142,6 +158,9 @@ impl<T> fmt::Debug for Action<T> { write!(f, "Action::AlwaysOnTop({on_top})") } Self::FetchId(_) => write!(f, "Action::FetchId"), + Self::ChangeIcon(_icon) => { + write!(f, "Action::ChangeIcon(icon)") + } } } } diff --git a/native/src/window/icon.rs b/native/src/window/icon.rs new file mode 100644 index 00000000..31868ecf --- /dev/null +++ b/native/src/window/icon.rs @@ -0,0 +1,80 @@ +//! Change the icon of a window. +use crate::Size; + +use std::mem; + +/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space. +pub fn from_rgba( + rgba: Vec<u8>, + width: u32, + height: u32, +) -> Result<Icon, Error> { + const PIXEL_SIZE: usize = mem::size_of::<u8>() * 4; + + if rgba.len() % PIXEL_SIZE != 0 { + return Err(Error::ByteCountNotDivisibleBy4 { + byte_count: rgba.len(), + }); + } + + let pixel_count = rgba.len() / PIXEL_SIZE; + + if pixel_count != (width * height) as usize { + return Err(Error::DimensionsVsPixelCount { + width, + height, + width_x_height: (width * height) as usize, + pixel_count, + }); + } + + Ok(Icon { + rgba, + size: Size::new(width, height), + }) +} + +/// An window icon normally used for the titlebar or taskbar. +#[derive(Debug, Clone)] +pub struct Icon { + rgba: Vec<u8>, + size: Size<u32>, +} + +impl Icon { + /// Returns the raw data of the [`Icon`]. + pub fn into_raw(self) -> (Vec<u8>, Size<u32>) { + (self.rgba, self.size) + } +} + +#[derive(Debug, thiserror::Error)] +/// An error produced when using [`Icon::from_rgba`] with invalid arguments. +pub enum Error { + /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be + /// safely interpreted as 32bpp RGBA pixels. + #[error( + "The provided RGBA data (with length {byte_count}) isn't divisible \ + by 4. Therefore, it cannot be safely interpreted as 32bpp RGBA pixels" + )] + ByteCountNotDivisibleBy4 { + /// The length of the provided RGBA data. + 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. + #[error( + "The number of RGBA pixels ({pixel_count}) does not match the \ + provided dimensions ({width}x{height})." + )] + DimensionsVsPixelCount { + /// The provided width. + width: u32, + /// The provided height. + height: u32, + /// The product of `width` and `height`. + width_x_height: usize, + /// The amount of pixels of the provided RGBA data. + pixel_count: usize, + }, +} diff --git a/src/window/icon.rs b/src/window/icon.rs index 659d2b64..e09dd048 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -1,175 +1,66 @@ //! Attach an icon to the window of your application. -use std::fmt; +pub use crate::runtime::window::icon::*; + +use crate::runtime::window::icon; + use std::io; -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] use std::path::Path; -/// The icon of a window. -#[derive(Clone)] -pub struct Icon(iced_winit::winit::window::Icon); +/// Creates an icon from an image file. +/// +/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead. +#[cfg(feature = "image")] +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Icon, Error> { + let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8(); -impl fmt::Debug for Icon { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Icon").field(&format_args!("_")).finish() - } + Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?) } -impl Icon { - /// Creates an icon from 32bpp RGBA data. - pub fn from_rgba( - rgba: Vec<u8>, - width: u32, - height: u32, - ) -> Result<Self, Error> { - let raw = - iced_winit::winit::window::Icon::from_rgba(rgba, width, height)?; - - Ok(Icon(raw)) - } - - /// Creates an icon from an image file. - /// - /// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead. - #[cfg(feature = "image_rs")] - pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Self, Error> { - let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8(); - - Self::from_rgba(icon.to_vec(), icon.width(), icon.height()) - } - - /// Creates an icon from the content of an image file. - /// - /// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro. \ - /// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime. - #[cfg(feature = "image_rs")] - pub fn from_file_data( - data: &[u8], - explicit_format: Option<image_rs::ImageFormat>, - ) -> Result<Self, Error> { - let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data)); - let icon_with_format = match explicit_format { - Some(format) => { - icon.set_format(format); - icon - } - None => icon.with_guessed_format()?, - }; +/// Creates an icon from the content of an image file. +/// +/// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro. +/// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime. +#[cfg(feature = "image")] +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub fn from_file_data( + data: &[u8], + explicit_format: Option<image_rs::ImageFormat>, +) -> Result<Icon, Error> { + let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data)); + let icon_with_format = match explicit_format { + Some(format) => { + icon.set_format(format); + icon + } + None => icon.with_guessed_format()?, + }; - let pixels = icon_with_format.decode()?.to_rgba8(); + let pixels = icon_with_format.decode()?.to_rgba8(); - Self::from_rgba(pixels.to_vec(), pixels.width(), pixels.height()) - } + Ok(icon::from_rgba( + pixels.to_vec(), + pixels.width(), + pixels.height(), + )?) } -/// An error produced when using `Icon::from_rgba` with invalid arguments. -#[derive(Debug)] +/// An error produced when creating an [`Icon`]. +#[derive(Debug, thiserror::Error)] pub enum Error { - /// The provided RGBA data isn't divisble by 4. - /// - /// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels. - InvalidData { - /// The length of the provided RGBA data. - byte_count: usize, - }, - - /// The number of RGBA pixels does not match the provided dimensions. - DimensionsMismatch { - /// The provided width. - width: u32, - /// The provided height. - height: u32, - /// The amount of pixels of the provided RGBA data. - pixel_count: usize, - }, + /// The [`Icon`] is not valid. + #[error("The icon is invalid: {0}")] + InvalidError(#[from] icon::Error), /// The underlying OS failed to create the icon. - OsError(io::Error), - - /// The `image` crate reported an error - #[cfg(feature = "image_rs")] - ImageError(image_rs::error::ImageError), -} - -impl From<std::io::Error> for Error { - fn from(os_error: std::io::Error) -> Self { - Error::OsError(os_error) - } -} - -impl From<iced_winit::winit::window::BadIcon> for Error { - fn from(error: iced_winit::winit::window::BadIcon) -> Self { - use iced_winit::winit::window::BadIcon; - - match error { - BadIcon::ByteCountNotDivisibleBy4 { byte_count } => { - Error::InvalidData { byte_count } - } - BadIcon::DimensionsVsPixelCount { - width, - height, - pixel_count, - .. - } => Error::DimensionsMismatch { - width, - height, - pixel_count, - }, - BadIcon::OsError(os_error) => Error::OsError(os_error), - } - } -} - -impl From<Icon> for iced_winit::winit::window::Icon { - fn from(icon: Icon) -> Self { - icon.0 - } -} - -#[cfg(feature = "image_rs")] -impl From<image_rs::error::ImageError> for Error { - fn from(image_error: image_rs::error::ImageError) -> Self { - Self::ImageError(image_error) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::InvalidData { byte_count } => { - write!( - f, - "The provided RGBA data (with length {byte_count:?}) isn't divisble by \ - 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \ - pixels." - ) - } - Error::DimensionsMismatch { - width, - height, - pixel_count, - } => { - write!( - f, - "The number of RGBA pixels ({pixel_count:?}) does not match the provided \ - dimensions ({width:?}x{height:?})." - ) - } - Error::OsError(e) => write!( - f, - "The underlying OS failed to create the window \ - icon: {e:?}" - ), - #[cfg(feature = "image_rs")] - Error::ImageError(e) => { - write!(f, "Unable to create icon from a file: {e:?}") - } - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(self) - } + #[error("The underlying OS failted to create the window icon: {0}")] + OsError(#[from] io::Error), + + /// The `image` crate reported an error. + #[cfg(feature = "image")] + #[cfg_attr(docsrs, doc(cfg(feature = "image")))] + #[error("Unable to create icon from a file: {0}")] + ImageError(#[from] image_rs::error::ImageError), } diff --git a/winit/src/application.rs b/winit/src/application.rs index 31654f26..dd345785 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -770,6 +770,9 @@ pub fn run_command<A, E>( mode, )); } + window::Action::ChangeIcon(icon) => { + window.set_window_icon(conversion::icon(icon)) + } window::Action::FetchMode(tag) => { let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index d2dc9c06..cf066ef6 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -510,6 +510,15 @@ pub fn user_attention( } } +/// Converts some [`Icon`] into it's `winit` counterpart. +/// +/// Returns `None` if there is an error during the conversion. +pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> { + let (pixels, size) = icon.into_raw(); + + winit::window::Icon::from_rgba(pixels, size.width, size.height).ok() +} + // As defined in: http://www.unicode.org/faq/private_use.html pub(crate) fn is_private_use_character(c: char) -> bool { matches!( diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 78d58000..6658773d 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -92,7 +92,7 @@ pub struct Window { pub always_on_top: bool, /// The window icon, which is also usually used in the taskbar - pub icon: Option<winit::window::Icon>, + pub icon: Option<crate::window::Icon>, /// Platform specific settings. pub platform_specific: platform::PlatformSpecific, @@ -134,8 +134,9 @@ impl Window { .with_resizable(self.resizable) .with_decorations(self.decorations) .with_transparent(self.transparent) - .with_window_icon(self.icon) - .with_always_on_top(self.always_on_top); + .with_window_icon(self.icon.and_then(conversion::icon)) + .with_always_on_top(self.always_on_top) + .with_visible(self.visible); if let Some(position) = conversion::position( primary_monitor.as_ref(), diff --git a/winit/src/window.rs b/winit/src/window.rs index 961562bd..ba0180c8 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,9 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{frames, Event, Mode, RedrawRequest, UserAttention}; +pub use window::{ + frames, icon, Event, Icon, Mode, RedrawRequest, UserAttention, +}; /// Closes the current window and exits the application. pub fn close<Message>() -> Command<Message> { @@ -104,3 +106,8 @@ pub fn fetch_id<Message>( f, )))) } + +/// Changes the [`Icon`] of the window. +pub fn change_icon<Message>(icon: Icon) -> Command<Message> { + Command::single(command::Action::Window(window::Action::ChangeIcon(icon))) +} |