diff options
| author | HampusM <hampus@hampusmat.com> | 2026-07-05 18:49:06 +0200 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2026-07-05 18:49:06 +0200 |
| commit | cca13077329c2c42f456c573ba9e094f1195efe9 (patch) | |
| tree | bce3de83fdee039f1c25068984b2d1eb6d9e3bc7 | |
| parent | 237107c971707d297c1f2d3e536ca52289ae4206 (diff) | |
feat(engine): add icon to window creation attributes
| -rw-r--r-- | engine/Cargo.toml | 2 | ||||
| -rw-r--r-- | engine/src/image.rs | 58 | ||||
| -rw-r--r-- | engine/src/windowing/window.rs | 55 |
3 files changed, 108 insertions, 7 deletions
diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 67f59d8..1dc6431 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -28,7 +28,7 @@ features = ["rwh_06", "wayland", "wayland-dlopen", "x11"] [dependencies.image_rs] version = "0.25.10" default-features = false -features = ["png", "jpeg"] +features = ["png", "jpeg", "ico"] package = "image" [dependencies.zerocopy] diff --git a/engine/src/image.rs b/engine/src/image.rs index dbcad9d..51ba459 100644 --- a/engine/src/image.rs +++ b/engine/src/image.rs @@ -1,6 +1,6 @@ use std::any::type_name; use std::fs::File; -use std::io::BufReader; +use std::io::{BufRead, BufReader, Seek}; use std::path::Path; use image_rs::GenericImageView as _; @@ -19,8 +19,7 @@ impl Image { pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> { - let buffered_reader = - BufReader::new(File::open(&path).map_err(Error::ReadFailed)?); + let buffered_reader = BufReader::new(File::open(&path).map_err(Error::Io)?); let image_reader = image_rs::ImageReader::with_format( buffered_reader, @@ -35,6 +34,20 @@ impl Image }) } + pub fn from_reader(reader: impl BufRead + Seek, format: Format) + -> Result<Self, Error> + { + let image_reader = + image_rs::ImageReader::with_format(reader, format.into_image_rs()); + + Ok(Self { + inner: image_reader + .decode() + // TODO: err might be other errors than decode errors here + .map_err(|err| Error::DecodeFailed(DecodeError(err)))?, + }) + } + pub fn try_from_bytes( bytes: &[u8], dimens: Dimens<u32>, @@ -121,10 +134,24 @@ impl Image Self { inner: self.inner.to_rgba8().into() } } + /// Consumes the image and returns a RGBA8 image. If it already is RGBA8, the image is + /// returned as is. If not, the image is converted to RGBA8. + pub fn into_rgba8(self) -> Self + { + Self { + inner: self.inner.into_rgba8().into(), + } + } + pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } + + pub fn into_bytes(self) -> Vec<u8> + { + self.inner.into_bytes() + } } /// An enumeration over supported color types and bit depths @@ -185,11 +212,32 @@ impl From<image_rs::ColorType> for ColorType } } +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +#[non_exhaustive] +pub enum Format +{ + Png, + Jpeg, + Ico, +} + +impl Format +{ + fn into_image_rs(self) -> image_rs::ImageFormat + { + match self { + Self::Png => image_rs::ImageFormat::Png, + Self::Jpeg => image_rs::ImageFormat::Jpeg, + Self::Ico => image_rs::ImageFormat::Ico, + } + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Failed to read file")] - ReadFailed(#[source] std::io::Error), + #[error("I/O error")] + Io(#[source] std::io::Error), #[error("Failed to decode image")] DecodeFailed(DecodeError), diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs index 67554ca..8f95dc8 100644 --- a/engine/src/windowing/window.rs +++ b/engine/src/windowing/window.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use crate::ecs::Component; +use crate::image::Image; use crate::reflection::Reflection; use crate::windowing::dpi::{PhysicalSize, Position, Size}; @@ -32,6 +33,8 @@ pub struct CreationAttributes pub resizable: bool, pub position: Option<Position>, pub inner_size: Option<Size>, + pub icon: Option<Icon>, + pub x_visual_id: Option<XVisualID>, // These do not have equivalents in winit::window::WindowAttributes @@ -62,6 +65,8 @@ gen_creation_attrs_with_fn!(visible, bool); gen_creation_attrs_with_fn!(resizable, bool); gen_creation_attrs_with_fn!(position, Option<Position>); gen_creation_attrs_with_fn!(inner_size, Option<Size>); +gen_creation_attrs_with_fn!(icon, Option<Icon>); + gen_creation_attrs_with_fn!(x_visual_id, Option<XVisualID>); gen_creation_attrs_with_fn!(cursor_visible, bool); @@ -69,6 +74,7 @@ gen_creation_attrs_with_fn!(cursor_grab_mode, CursorGrabMode); impl CreationAttributes { + #[tracing::instrument(skip_all)] pub(crate) fn into_window_attrs(self) -> winit::window::WindowAttributes { let mut window_attrs = winit::window::WindowAttributes::default() @@ -82,7 +88,42 @@ impl CreationAttributes None => None, }) .with_visible(self.visible) - .with_resizable(self.resizable); + .with_resizable(self.resizable) + .with_window_icon(match self.icon { + Some(Icon::Image(image)) => { + let image_dimens = image.dimensions(); + + let result = winit::window::Icon::from_rgba( + image.into_rgba8().into_bytes(), + image_dimens.width, + image_dimens.height, + ); + + if let Err(err) = &result { + tracing::error!("Invalid icon image: {err}"); + } + + result.ok() + } + #[cfg(windows)] + Some(Icon::WindowsResource(resource_name)) => { + use winit::platform::windows::IconExtWindows; + + let result = winit::window::Icon::from_resource_name( + resource_name.as_ref(), + None, + ); + + if let Err(err) = &result { + tracing::error!( + "Invalid icon image in resource {resource_name}: {err}" + ); + } + + result.ok() + } + None => None, + }); window_attrs.position = self.position.map(Into::into); window_attrs.inner_size = self.inner_size.map(Into::into); @@ -116,6 +157,7 @@ impl Default for CreationAttributes resizable: true, position: None, inner_size: None, + icon: None, x_visual_id: None, cursor_visible: true, cursor_grab_mode: CursorGrabMode::None, @@ -130,6 +172,17 @@ pub enum Fullscreen Borderless, } +/// Window icon +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum Icon +{ + Image(Image), + + #[cfg(windows)] + WindowsResource(Cow<'static, str>), +} + #[derive(Debug, Component, Clone, Copy)] pub struct CreationReady; |
