From 0c197e8652beee5ac1abecf9ae9b3cef90fb4bc9 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Sat, 31 Oct 2026 10:25:04 -0700 Subject: [PATCH] refactor(bar-core): move ImageIcon, ImageIconId and the icons cache into the core The icon cache (IconsCache, ICONS_CACHE, ImageIcon, ImageIconId, rgba_to_color_image) was in widgets/mod.rs of both bars with two small differences: Windows has `Hwnd(isize)`, `SystrayIcon(String)` and `remove`, macOS has `WindowId(u32)` and a private rgba_to_color_image. The core file is the Windows one with the macOS variant and `From` added (an enum with every variant, as D26 does for WidgetConfig); both bars re-export the items from widgets/mod.rs so no widget changes. The core gains the `image` dependency (one line in each Cargo.lock) and a Default impl for IconsCache (clippy). Verified: schema harness macOS and Windows byte-identical (3,774 / 9,091 lines); native bar-core tests 3+5; Windows clippy -D warnings --locked clean; macOS fake-CC check --locked 0 errors (re-run after touching the core files). Not run: the bars. --- Cargo.lock | 1 + crates/komorebi-bar-core/Cargo.toml | 1 + crates/komorebi-bar-core/src/widgets/icons.rs | 181 ++++++++++++++++++ crates/komorebi-bar-core/src/widgets/mod.rs | 1 + komorebi-bar/src/widgets/mod.rs | 169 +--------------- komorebi-for-mac/Cargo.lock | 1 + .../komorebi-bar/src/widgets/mod.rs | 162 +--------------- 7 files changed, 194 insertions(+), 322 deletions(-) create mode 100644 crates/komorebi-bar-core/src/widgets/icons.rs diff --git a/Cargo.lock b/Cargo.lock index b0903cbd..553a83be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3447,6 +3447,7 @@ dependencies = [ "chrono-tz", "eframe", "egui-phosphor", + "image 0.25.10", "lazy_static", "reqwest", "schemars 1.2.3", diff --git a/crates/komorebi-bar-core/Cargo.toml b/crates/komorebi-bar-core/Cargo.toml index 5f8ac7ae..794221fc 100644 --- a/crates/komorebi-bar-core/Cargo.toml +++ b/crates/komorebi-bar-core/Cargo.toml @@ -10,6 +10,7 @@ chrono-tz = { workspace = true } eframe = { workspace = true } egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" } lazy_static = { workspace = true } +image = "0.25" reqwest = { version = "0.12", features = ["blocking"] } serde = { workspace = true } serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/crates/komorebi-bar-core/src/widgets/icons.rs b/crates/komorebi-bar-core/src/widgets/icons.rs new file mode 100644 index 00000000..6c6d66ff --- /dev/null +++ b/crates/komorebi-bar-core/src/widgets/icons.rs @@ -0,0 +1,181 @@ +use eframe::egui::ColorImage; +use eframe::egui::Context; +use eframe::egui::TextureHandle; +use eframe::egui::TextureOptions; +use image::RgbaImage; +use std::collections::HashMap; +use std::path::Path; +use std::sync::Arc; +use std::sync::LazyLock; +use std::sync::RwLock; + +/// Global cache for icon images and their associated GPU textures. +pub static ICONS_CACHE: IconsCache = IconsCache::new(); + +/// In-memory cache for icon images and their associated GPU textures. +/// +/// Stores raw [`ColorImage`]s and [`TextureHandle`]s keyed by [`ImageIconId`]. +/// Texture entries are context-dependent and automatically invalidated when the [`Context`] changes. +#[allow(clippy::type_complexity)] +pub struct IconsCache { + textures: LazyLock, HashMap)>>, + images: LazyLock>>>, +} + +impl IconsCache { + /// Creates a new empty IconsCache instance. + #[inline] + pub const fn new() -> Self { + Self { + textures: LazyLock::new(|| RwLock::new((None, HashMap::new()))), + images: LazyLock::new(|| RwLock::new(HashMap::new())), + } + } + + /// Retrieves or creates a texture handle for the given icon ID and image. + /// + /// If a texture for the given ID already exists for the current [`Context`], it is reused. + /// Otherwise, a new texture is created, inserted into the cache, and returned. + /// The cache is reset if the [`Context`] has changed. + #[inline] + pub fn texture(&self, ctx: &Context, id: &ImageIconId, img: &Arc) -> TextureHandle { + if let Some(texture) = self.get_texture(ctx, id) { + return texture; + } + let texture_handle = ctx.load_texture("icon", img.clone(), TextureOptions::default()); + self.insert_texture(ctx, id.clone(), texture_handle.clone()); + texture_handle + } + + /// Returns the cached texture for the given icon ID if it exists and matches the current [`Context`]. + pub fn get_texture(&self, ctx: &Context, id: &ImageIconId) -> Option { + let textures_lock = self.textures.read().unwrap(); + if textures_lock.0.as_ref() == Some(ctx) { + return textures_lock.1.get(id).cloned(); + } + None + } + + /// Inserts a texture handle, resetting the cache if the [`Context`] has changed. + pub fn insert_texture(&self, ctx: &Context, id: ImageIconId, texture: TextureHandle) { + let mut textures_lock = self.textures.write().unwrap(); + + if textures_lock.0.as_ref() != Some(ctx) { + textures_lock.0 = Some(ctx.clone()); + textures_lock.1.clear(); + } + + textures_lock.1.insert(id, texture); + } + + /// Returns the cached image for the given icon ID, if available. + pub fn get_image(&self, id: &ImageIconId) -> Option> { + self.images.read().unwrap().get(id).cloned() + } + + /// Caches a raw [`ColorImage`] associated with the given icon ID. + pub fn insert_image(&self, id: ImageIconId, image: Arc) { + self.images.write().unwrap().insert(id, image); + } + + /// Removes the cached image and texture for the given icon ID. + pub fn remove(&self, id: &ImageIconId) { + self.images.write().unwrap().remove(id); + self.textures.write().unwrap().1.remove(id); + } +} + +#[inline] +pub fn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage { + let size = [rgba_image.width() as usize, rgba_image.height() as usize]; + let pixels = rgba_image.as_flat_samples(); + ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()) +} + +/// Represents an image-based icon with a unique ID and pixel data. +#[derive(Clone, Debug)] +pub struct ImageIcon { + /// Unique identifier for the image icon, used for texture caching. + pub id: ImageIconId, + /// Shared pixel data of the icon in `ColorImage` format. + pub image: Arc, +} + +impl ImageIcon { + /// Creates a new [`ImageIcon`] from the given ID and image data. + #[inline] + pub fn new(id: ImageIconId, image: Arc) -> Self { + Self { id, image } + } + + /// Loads an [`ImageIcon`] from [`ICONS_CACHE`] or calls `loader` if not cached. + /// The loaded image is converted to a [`ColorImage`], cached, and returned. + #[inline] + pub fn try_load(id: impl Into, loader: F) -> Option + where + F: FnOnce() -> Option, + I: Into, + { + let id = id.into(); + let image = ICONS_CACHE.get_image(&id).or_else(|| { + let img = loader()?; + let img = Arc::new(rgba_to_color_image(&img.into())); + ICONS_CACHE.insert_image(id.clone(), img.clone()); + Some(img) + })?; + + Some(ImageIcon::new(id, image)) + } + + /// Returns a texture handle for the icon, using the given [`Context`]. + /// + /// If the texture is already cached in [`ICONS_CACHE`], it is reused. + /// Otherwise, a new texture is created from the [`ColorImage`] and cached. + #[inline] + pub fn texture(&self, ctx: &Context) -> TextureHandle { + ICONS_CACHE.texture(ctx, &self.id, &self.image) + } +} + +/// Unique identifier for an image-based icon. +/// +/// Used to distinguish cached images and textures by either a file path +/// or a Windows window handle. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum ImageIconId { + /// Identifier based on a file system path. + Path(Arc), + /// Windows HWND handle. + Hwnd(isize), + /// System tray icon identifier. + SystrayIcon(String), + /// macOS window ID. + WindowId(u32), +} + +impl From<&Path> for ImageIconId { + #[inline] + fn from(value: &Path) -> Self { + Self::Path(value.into()) + } +} + +impl From for ImageIconId { + #[inline] + fn from(value: isize) -> Self { + Self::Hwnd(value) + } +} + +impl From for ImageIconId { + #[inline] + fn from(value: u32) -> Self { + Self::WindowId(value) + } +} + +impl Default for IconsCache { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/komorebi-bar-core/src/widgets/mod.rs b/crates/komorebi-bar-core/src/widgets/mod.rs index cd38bd89..27b44309 100644 --- a/crates/komorebi-bar-core/src/widgets/mod.rs +++ b/crates/komorebi-bar-core/src/widgets/mod.rs @@ -1,6 +1,7 @@ pub mod battery; pub mod cpu; pub mod date; +pub mod icons; pub mod memory; pub mod storage; pub mod time; diff --git a/komorebi-bar/src/widgets/mod.rs b/komorebi-bar/src/widgets/mod.rs index 054651d3..7f437f5c 100644 --- a/komorebi-bar/src/widgets/mod.rs +++ b/komorebi-bar/src/widgets/mod.rs @@ -1,13 +1,3 @@ -use eframe::egui::ColorImage; -use eframe::egui::Context; -use eframe::egui::TextureHandle; -use eframe::egui::TextureOptions; -use image::RgbaImage; -use std::collections::HashMap; -use std::path::Path; -use std::sync::Arc; -use std::sync::LazyLock; -use std::sync::RwLock; pub mod applications; pub use komorebi_bar_core::widgets::battery; @@ -26,158 +16,7 @@ pub use komorebi_bar_core::widgets::time; pub use komorebi_bar_core::widgets::update; pub mod widget; -/// Global cache for icon images and their associated GPU textures. -pub static ICONS_CACHE: IconsCache = IconsCache::new(); - -/// In-memory cache for icon images and their associated GPU textures. -/// -/// Stores raw [`ColorImage`]s and [`TextureHandle`]s keyed by [`ImageIconId`]. -/// Texture entries are context-dependent and automatically invalidated when the [`Context`] changes. -#[allow(clippy::type_complexity)] -pub struct IconsCache { - textures: LazyLock, HashMap)>>, - images: LazyLock>>>, -} - -impl IconsCache { - /// Creates a new empty IconsCache instance. - #[inline] - pub const fn new() -> Self { - Self { - textures: LazyLock::new(|| RwLock::new((None, HashMap::new()))), - images: LazyLock::new(|| RwLock::new(HashMap::new())), - } - } - - /// Retrieves or creates a texture handle for the given icon ID and image. - /// - /// If a texture for the given ID already exists for the current [`Context`], it is reused. - /// Otherwise, a new texture is created, inserted into the cache, and returned. - /// The cache is reset if the [`Context`] has changed. - #[inline] - pub fn texture(&self, ctx: &Context, id: &ImageIconId, img: &Arc) -> TextureHandle { - if let Some(texture) = self.get_texture(ctx, id) { - return texture; - } - let texture_handle = ctx.load_texture("icon", img.clone(), TextureOptions::default()); - self.insert_texture(ctx, id.clone(), texture_handle.clone()); - texture_handle - } - - /// Returns the cached texture for the given icon ID if it exists and matches the current [`Context`]. - pub fn get_texture(&self, ctx: &Context, id: &ImageIconId) -> Option { - let textures_lock = self.textures.read().unwrap(); - if textures_lock.0.as_ref() == Some(ctx) { - return textures_lock.1.get(id).cloned(); - } - None - } - - /// Inserts a texture handle, resetting the cache if the [`Context`] has changed. - pub fn insert_texture(&self, ctx: &Context, id: ImageIconId, texture: TextureHandle) { - let mut textures_lock = self.textures.write().unwrap(); - - if textures_lock.0.as_ref() != Some(ctx) { - textures_lock.0 = Some(ctx.clone()); - textures_lock.1.clear(); - } - - textures_lock.1.insert(id, texture); - } - - /// Returns the cached image for the given icon ID, if available. - pub fn get_image(&self, id: &ImageIconId) -> Option> { - self.images.read().unwrap().get(id).cloned() - } - - /// Caches a raw [`ColorImage`] associated with the given icon ID. - pub fn insert_image(&self, id: ImageIconId, image: Arc) { - self.images.write().unwrap().insert(id, image); - } - - /// Removes the cached image and texture for the given icon ID. - pub fn remove(&self, id: &ImageIconId) { - self.images.write().unwrap().remove(id); - self.textures.write().unwrap().1.remove(id); - } -} - -#[inline] -pub(crate) fn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage { - let size = [rgba_image.width() as usize, rgba_image.height() as usize]; - let pixels = rgba_image.as_flat_samples(); - ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()) -} - -/// Represents an image-based icon with a unique ID and pixel data. -#[derive(Clone, Debug)] -pub struct ImageIcon { - /// Unique identifier for the image icon, used for texture caching. - pub id: ImageIconId, - /// Shared pixel data of the icon in `ColorImage` format. - pub image: Arc, -} - -impl ImageIcon { - /// Creates a new [`ImageIcon`] from the given ID and image data. - #[inline] - pub fn new(id: ImageIconId, image: Arc) -> Self { - Self { id, image } - } - - /// Loads an [`ImageIcon`] from [`ICONS_CACHE`] or calls `loader` if not cached. - /// The loaded image is converted to a [`ColorImage`], cached, and returned. - #[inline] - pub fn try_load(id: impl Into, loader: F) -> Option - where - F: FnOnce() -> Option, - I: Into, - { - let id = id.into(); - let image = ICONS_CACHE.get_image(&id).or_else(|| { - let img = loader()?; - let img = Arc::new(rgba_to_color_image(&img.into())); - ICONS_CACHE.insert_image(id.clone(), img.clone()); - Some(img) - })?; - - Some(ImageIcon::new(id, image)) - } - - /// Returns a texture handle for the icon, using the given [`Context`]. - /// - /// If the texture is already cached in [`ICONS_CACHE`], it is reused. - /// Otherwise, a new texture is created from the [`ColorImage`] and cached. - #[inline] - pub fn texture(&self, ctx: &Context) -> TextureHandle { - ICONS_CACHE.texture(ctx, &self.id, &self.image) - } -} - -/// Unique identifier for an image-based icon. -/// -/// Used to distinguish cached images and textures by either a file path -/// or a Windows window handle. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum ImageIconId { - /// Identifier based on a file system path. - Path(Arc), - /// Windows HWND handle. - Hwnd(isize), - /// System tray icon identifier. - SystrayIcon(String), -} - -impl From<&Path> for ImageIconId { - #[inline] - fn from(value: &Path) -> Self { - Self::Path(value.into()) - } -} - -impl From for ImageIconId { - #[inline] - fn from(value: isize) -> Self { - Self::Hwnd(value) - } -} +pub use komorebi_bar_core::widgets::icons::ICONS_CACHE; +pub use komorebi_bar_core::widgets::icons::ImageIcon; +pub use komorebi_bar_core::widgets::icons::ImageIconId; +pub use komorebi_bar_core::widgets::icons::rgba_to_color_image; diff --git a/komorebi-for-mac/Cargo.lock b/komorebi-for-mac/Cargo.lock index 5092b3f3..ba3bb9b1 100644 --- a/komorebi-for-mac/Cargo.lock +++ b/komorebi-for-mac/Cargo.lock @@ -3255,6 +3255,7 @@ dependencies = [ "chrono-tz", "eframe", "egui-phosphor", + "image 0.25.10", "lazy_static", "reqwest", "schemars 1.2.3", diff --git a/komorebi-for-mac/komorebi-bar/src/widgets/mod.rs b/komorebi-for-mac/komorebi-bar/src/widgets/mod.rs index 25806bb2..0d34705e 100644 --- a/komorebi-for-mac/komorebi-bar/src/widgets/mod.rs +++ b/komorebi-for-mac/komorebi-bar/src/widgets/mod.rs @@ -1,13 +1,3 @@ -use eframe::egui::ColorImage; -use eframe::egui::Context; -use eframe::egui::TextureHandle; -use eframe::egui::TextureOptions; -use image::RgbaImage; -use std::collections::HashMap; -use std::path::Path; -use std::sync::Arc; -use std::sync::LazyLock; -use std::sync::RwLock; pub mod applications; pub use komorebi_bar_core::widgets::battery; @@ -22,150 +12,8 @@ pub use komorebi_bar_core::widgets::time; pub use komorebi_bar_core::widgets::update; pub mod widget; -/// Global cache for icon images and their associated GPU textures. -pub static ICONS_CACHE: IconsCache = IconsCache::new(); - -/// In-memory cache for icon images and their associated GPU textures. -/// -/// Stores raw [`ColorImage`]s and [`TextureHandle`]s keyed by [`ImageIconId`]. -/// Texture entries are context-dependent and automatically invalidated when the [`Context`] changes. -#[allow(clippy::type_complexity)] -pub struct IconsCache { - textures: LazyLock, HashMap)>>, - images: LazyLock>>>, -} - -impl IconsCache { - /// Creates a new empty IconsCache instance. - #[inline] - pub const fn new() -> Self { - Self { - textures: LazyLock::new(|| RwLock::new((None, HashMap::new()))), - images: LazyLock::new(|| RwLock::new(HashMap::new())), - } - } - - /// Retrieves or creates a texture handle for the given icon ID and image. - /// - /// If a texture for the given ID already exists for the current [`Context`], it is reused. - /// Otherwise, a new texture is created, inserted into the cache, and returned. - /// The cache is reset if the [`Context`] has changed. - #[inline] - pub fn texture(&self, ctx: &Context, id: &ImageIconId, img: &Arc) -> TextureHandle { - if let Some(texture) = self.get_texture(ctx, id) { - return texture; - } - let texture_handle = ctx.load_texture("icon", img.clone(), TextureOptions::default()); - self.insert_texture(ctx, id.clone(), texture_handle.clone()); - texture_handle - } - - /// Returns the cached texture for the given icon ID if it exists and matches the current [`Context`]. - pub fn get_texture(&self, ctx: &Context, id: &ImageIconId) -> Option { - let textures_lock = self.textures.read().unwrap(); - if textures_lock.0.as_ref() == Some(ctx) { - return textures_lock.1.get(id).cloned(); - } - None - } - - /// Inserts a texture handle, resetting the cache if the [`Context`] has changed. - pub fn insert_texture(&self, ctx: &Context, id: ImageIconId, texture: TextureHandle) { - let mut textures_lock = self.textures.write().unwrap(); - - if textures_lock.0.as_ref() != Some(ctx) { - textures_lock.0 = Some(ctx.clone()); - textures_lock.1.clear(); - } - - textures_lock.1.insert(id, texture); - } - - /// Returns the cached image for the given icon ID, if available. - pub fn get_image(&self, id: &ImageIconId) -> Option> { - self.images.read().unwrap().get(id).cloned() - } - - /// Caches a raw [`ColorImage`] associated with the given icon ID. - pub fn insert_image(&self, id: ImageIconId, image: Arc) { - self.images.write().unwrap().insert(id, image); - } -} - -#[inline] -fn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage { - let size = [rgba_image.width() as usize, rgba_image.height() as usize]; - let pixels = rgba_image.as_flat_samples(); - ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()) -} - -/// Represents an image-based icon with a unique ID and pixel data. -#[derive(Clone, Debug)] -pub struct ImageIcon { - /// Unique identifier for the image icon, used for texture caching. - pub id: ImageIconId, - /// Shared pixel data of the icon in `ColorImage` format. - pub image: Arc, -} - -impl ImageIcon { - /// Creates a new [`ImageIcon`] from the given ID and image data. - #[inline] - pub fn new(id: ImageIconId, image: Arc) -> Self { - Self { id, image } - } - - /// Loads an [`ImageIcon`] from [`ICONS_CACHE`] or calls `loader` if not cached. - /// The loaded image is converted to a [`ColorImage`], cached, and returned. - #[inline] - pub fn try_load(id: impl Into, loader: F) -> Option - where - F: FnOnce() -> Option, - I: Into, - { - let id = id.into(); - let image = ICONS_CACHE.get_image(&id).or_else(|| { - let img = loader()?; - let img = Arc::new(rgba_to_color_image(&img.into())); - ICONS_CACHE.insert_image(id.clone(), img.clone()); - Some(img) - })?; - - Some(ImageIcon::new(id, image)) - } - - /// Returns a texture handle for the icon, using the given [`Context`]. - /// - /// If the texture is already cached in [`ICONS_CACHE`], it is reused. - /// Otherwise, a new texture is created from the [`ColorImage`] and cached. - #[inline] - pub fn texture(&self, ctx: &Context) -> TextureHandle { - ICONS_CACHE.texture(ctx, &self.id, &self.image) - } -} - -/// Unique identifier for an image-based icon. -/// -/// Used to distinguish cached images and textures by either a file path -/// or a Windows window handle. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum ImageIconId { - /// Identifier based on a file system path. - Path(Arc), - /// Window ID - WindowId(u32), -} - -impl From<&Path> for ImageIconId { - #[inline] - fn from(value: &Path) -> Self { - Self::Path(value.into()) - } -} - -impl From for ImageIconId { - #[inline] - fn from(value: u32) -> Self { - Self::WindowId(value) - } -} +pub use komorebi_bar_core::widgets::icons::ICONS_CACHE; +pub use komorebi_bar_core::widgets::icons::ImageIcon; +pub use komorebi_bar_core::widgets::icons::ImageIconId; +#[allow(unused_imports)] +pub use komorebi_bar_core::widgets::icons::rgba_to_color_image;