use crate::deps::{Path, PathBuf};
use crate::error::{TrayError, TrayResult};
#[derive(Debug, Clone)]
pub struct TrayIcon {
pub(crate) source: IconSource,
}
#[derive(Debug, Clone)]
pub(crate) enum IconSource {
File(PathBuf),
Rgba {
data: Vec<u8>,
width: u32,
height: u32,
},
Default,
}
impl TrayIcon {
pub fn from_path(path: impl AsRef<Path>) -> TrayResult<Self> {
let path = path.as_ref();
if !path.exists() {
return Err(TrayError::IconLoadFailed(format!(
"图标文件不存在: {}",
path.display()
)));
}
Ok(Self {
source: IconSource::File(path.to_path_buf()),
})
}
pub fn from_rgba(data: Vec<u8>, width: u32, height: u32) -> TrayResult<Self> {
let expected_len = (width * height * 4) as usize;
if data.len() != expected_len {
return Err(TrayError::IconLoadFailed(format!(
"RGBA 数据长度不匹配: 期望 {}, 实际 {}",
expected_len,
data.len()
)));
}
Ok(Self {
source: IconSource::Rgba {
data,
width,
height,
},
})
}
pub fn default_icon() -> Self {
Self {
source: IconSource::Default,
}
}
pub fn is_default(&self) -> bool {
matches!(self.source, IconSource::Default)
}
pub fn dimensions(&self) -> Option<(u32, u32)> {
match &self.source {
IconSource::Rgba { width, height, .. } => Some((*width, *height)),
_ => None,
}
}
}
impl Default for TrayIcon {
fn default() -> Self {
Self::default_icon()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_icon() {
let icon = TrayIcon::default_icon();
assert!(icon.is_default());
assert!(icon.dimensions().is_none());
}
#[test]
fn test_from_rgba() {
let data = vec![
255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, ];
let icon = TrayIcon::from_rgba(data, 2, 2).unwrap();
assert!(!icon.is_default());
assert_eq!(icon.dimensions(), Some((2, 2)));
}
#[test]
fn test_from_rgba_invalid_size() {
let data = vec![0u8; 10]; let result = TrayIcon::from_rgba(data, 2, 2);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("长度不匹配"));
}
#[test]
fn test_from_path_not_exists() {
let result = TrayIcon::from_path("/nonexistent/icon.png");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("不存在"));
}
}