virtual_desktop_manager/
tray_icons.rs1#![allow(dead_code)]
31
32#[allow(unused_imports)]
33use std::{borrow::Cow, io::Cursor, sync::OnceLock};
34
35#[cfg(feature = "tray_icon_hardcoded")]
36mod hardcoded {
37 pub static ICON1: &[u8] = include_bytes!("icons/triangle1.ico");
38 pub static ICON2: &[u8] = include_bytes!("icons/triangle2.ico");
39 pub static ICON3: &[u8] = include_bytes!("icons/triangle3.ico");
40 pub static ICON4: &[u8] = include_bytes!("icons/triangle4.ico");
41 pub static ICON5: &[u8] = include_bytes!("icons/triangle5.ico");
42 pub static ICON6: &[u8] = include_bytes!("icons/triangle6.ico");
43 pub static ICON7: &[u8] = include_bytes!("icons/triangle7.ico");
44 pub static ICON8: &[u8] = include_bytes!("icons/triangle8.ico");
45 pub static ICON9: &[u8] = include_bytes!("icons/triangle9.ico");
46}
47#[cfg(feature = "tray_icon_hardcoded")]
48pub use hardcoded::*;
49
50pub static ICON_EMPTY: &[u8] = include_bytes!("icons/triangleEmpty.ico");
51#[cfg(feature = "tray_icon_with_background")]
52pub static IMAGE_EMPTY: &[u8] = include_bytes!("icons/triangleEmptyImage.png");
53
54#[cfg(any(
69 feature = "tray_icon_with_background",
70 feature = "tray_icon_text_only",
71 feature = "tray_icon_text_only_alt"
72))]
73static NUMBER_FONT: &[u8] = include_bytes!("./OpenSans-Bold-DigitsOnly.ttf");
74
75pub fn get_included_icon(_number: u32) -> Option<&'static [u8]> {
76 #[cfg(feature = "tray_icon_hardcoded")]
77 {
78 Some(match _number {
79 1 => ICON1,
80 2 => ICON2,
81 3 => ICON3,
82 4 => ICON4,
83 5 => ICON5,
84 6 => ICON6,
85 7 => ICON7,
86 8 => ICON8,
87 9 => ICON9,
88 _ => return None,
89 })
90 }
91 #[cfg(not(feature = "tray_icon_hardcoded"))]
92 {
93 None
94 }
95}
96
97pub enum IconType {
98 WithBackground {
99 allow_hardcoded: bool,
100 light_theme: bool,
101 },
102 NoBackground {
103 light_theme: bool,
104 },
105 NoBackgroundAlt,
106}
107impl IconType {
108 pub fn generate_icon(&self, number: u32) -> Cow<'static, [u8]> {
110 match self {
111 Self::WithBackground {
113 allow_hardcoded: true,
114 light_theme,
115 } => match get_included_icon(number).filter(|_| !light_theme) {
116 Some(d) => Cow::Borrowed(d),
117 #[cfg(feature = "tray_icon_with_background")]
118 None => Cow::Owned(generate_icon_with_background(number, *light_theme)),
119 #[cfg(not(feature = "tray_icon_with_background"))]
120 None => Cow::Borrowed(ICON_EMPTY),
121 },
122 Self::WithBackground {
123 allow_hardcoded: false,
124 light_theme,
125 } => {
126 #[cfg(feature = "tray_icon_with_background")]
127 {
128 generate_icon_with_background(number, *light_theme).into()
129 }
130 #[cfg(not(feature = "tray_icon_with_background"))]
131 {
132 Cow::Borrowed(ICON_EMPTY)
133 }
134 }
135 Self::NoBackground { light_theme } => {
136 #[cfg(feature = "tray_icon_text_only")]
137 {
138 generate_icon_without_background(number, *light_theme).into()
139 }
140 #[cfg(not(feature = "tray_icon_text_only"))]
141 {
142 Cow::Borrowed(ICON_EMPTY)
143 }
144 }
145 Self::NoBackgroundAlt => {
146 #[cfg(feature = "tray_icon_text_only_alt")]
147 {
148 generate_icon_without_background_alt(number).into()
149 }
150 #[cfg(not(feature = "tray_icon_text_only_alt"))]
151 {
152 Cow::Borrowed(ICON_EMPTY)
153 }
154 }
155 }
156 }
157}
158
159#[cfg(any(feature = "tray_icon_with_background", feature = "tray_icon_text_only"))]
160fn get_number_font() -> &'static ab_glyph::FontRef<'static> {
161 static CACHED: OnceLock<ab_glyph::FontRef<'static>> = OnceLock::new();
162 CACHED.get_or_init(|| {
163 ab_glyph::FontRef::try_from_slice(NUMBER_FONT).expect("Valid font embedded in binary")
164 })
165}
166
167#[cfg(feature = "tray_icon_with_background")]
168fn get_empty_image() -> &'static image::DynamicImage {
169 static CACHED: OnceLock<image::DynamicImage> = OnceLock::new();
170 CACHED.get_or_init(|| {
171 let image =
172 image::ImageReader::with_format(Cursor::new(IMAGE_EMPTY), image::ImageFormat::Png)
173 .decode()
174 .expect("Failed to load embedded PNG");
175 image.crop_imm(0, 0, 256, 256)
178 })
179}
180
181#[cfg(feature = "tray_icon_with_background")]
183pub fn generate_icon_with_background(number: u32, light_theme: bool) -> Vec<u8> {
184 let text = number.to_string();
185
186 let font = get_number_font();
187 let mut canvas = get_empty_image().clone();
188 if light_theme {
189 canvas.invert();
190 }
191 imageproc::drawing::draw_text_mut(
192 &mut canvas,
193 imageproc::image::Rgba(if light_theme {
194 [0, 0, 0, 255]
195 } else {
196 [255, 255, 255, 255]
197 }),
198 if text.len() >= 2 { 110 } else { 130 },
199 56,
200 ab_glyph::PxScale { x: 150.0, y: 180.0 },
201 font,
202 &text,
203 );
204 let mut data = Vec::new();
206 canvas
207 .write_to(&mut Cursor::new(&mut data), image::ImageFormat::Ico)
208 .expect("Failed to convert generated tray image to ICO format");
209 data
210}
211
212#[cfg(feature = "tray_icon_text_only")]
214pub fn generate_icon_without_background(number: u32, light_theme: bool) -> Vec<u8> {
215 let text = number.to_string();
216
217 let font = get_number_font();
218 let mut canvas = image::ImageBuffer::from_pixel(256, 256, image::Rgba([0_u8, 0, 0, 0]));
219
220 imageproc::drawing::draw_text_mut(
221 &mut canvas,
222 imageproc::image::Rgba(if light_theme {
223 [0, 0, 0, 255]
224 } else {
225 [255, 255, 255, 255]
226 }),
227 if text.len() >= 2 { -8 } else { 0 },
228 -130,
229 ab_glyph::PxScale {
230 x: match text.len() {
231 0 | 1 => 660.0,
232 2 => 330.0,
233 _ => 210.0,
234 },
235 y: 490.0,
236 },
237 font,
238 &text,
239 );
240 let mut data = Vec::new();
242 canvas
243 .write_to(&mut Cursor::new(&mut data), image::ImageFormat::Ico)
244 .expect("Failed to convert generated tray image to ICO format");
245 data
246}
247
248#[cfg(feature = "tray_icon_text_only_alt")]
251pub fn generate_icon_without_background_alt(number: u32) -> Vec<u8> {
252 let renderer = text_to_png::TextRenderer::try_new_with_ttf_font_data(NUMBER_FONT)
253 .expect("Failed to load embedded font");
254
255 let text_png = renderer
256 .render_text_to_png_data(number.to_string(), 128, "Dark Turquoise")
257 .expect("Failed to render text to PNG");
258
259 let mut data = Vec::new();
261 image::io::Reader::with_format(Cursor::new(&text_png.data), image::ImageFormat::Png)
262 .decode()
263 .expect("Failed to read generated PNG")
264 .write_to(&mut Cursor::new(&mut data), image::ImageFormat::Ico)
265 .expect("Failed to convert tray image to ICO format");
266 data
267}