teksilo_core/window/icon.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Window icon.
5//!
6//! A raw RGBA8 bitmap plus dimensions. The app-level window manager
7//! converts this into the winit `Icon` type at window-creation time.
8
9/// Window icon as raw RGBA8 bytes (4 bytes per pixel, row-major,
10/// top-left origin).
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct WindowIcon {
13 pub rgba: Vec<u8>,
14 pub width: u32,
15 pub height: u32,
16}
17
18impl WindowIcon {
19 /// Construct an icon from a row-major RGBA8 buffer.
20 ///
21 /// The buffer must contain exactly `width * height * 4` bytes. The
22 /// app-level manager validates this when converting to the
23 /// platform icon and logs + drops the icon on mismatch — the
24 /// window still opens, just without a custom icon.
25 pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Self {
26 Self {
27 rgba,
28 width,
29 height,
30 }
31 }
32
33 /// Expected buffer size in bytes for `width × height` RGBA8.
34 pub fn expected_len(&self) -> usize {
35 (self.width as usize) * (self.height as usize) * 4
36 }
37
38 /// `true` when `rgba.len()` matches `width × height × 4`.
39 pub fn is_valid(&self) -> bool {
40 self.rgba.len() == self.expected_len()
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn valid_icon_passes_check() {
50 let icon = WindowIcon::from_rgba(vec![0; 16 * 16 * 4], 16, 16);
51 assert!(icon.is_valid());
52 }
53
54 #[test]
55 fn mismatched_len_is_invalid() {
56 let icon = WindowIcon::from_rgba(vec![0; 100], 16, 16);
57 assert!(!icon.is_valid());
58 }
59}