embedded_iconoir/
icon.rs

1use embedded_graphics::prelude::*;
2use embedded_graphics::primitives::Rectangle;
3use static_assertions::const_assert;
4
5#[macro_export]
6macro_rules! make_icon {
7    ($name:ident, $size:expr, $category:expr, $file:expr) => {
8        #[derive(Debug, Copy, Clone)]
9        pub struct $name;
10
11        impl $crate::icon::sealed::IconoirInternal for $name {
12            const SIZE: u32 = $size;
13            const DATA: &'static [u8] = include_bytes!(concat!(
14                "../rendered/",
15                stringify!($size),
16                "px/",
17                $category,
18                "/",
19                $file,
20                ".bits"
21            ));
22            const INSTANCE: Self = $name;
23        }
24
25        const_assert!(
26            <$name as $crate::icon::sealed::IconoirInternal>::DATA.len()
27                >= <$name as $crate::icon::sealed::IconoirInternal>::BYTE_COUNT
28        );
29        // macro end
30    };
31}
32
33#[macro_export]
34macro_rules! make_icon_category {
35    ($name:ident, $size:expr, $category:expr, [ $(($icon_name:ident, $file:expr)),* $(,)? ] ) => {
36    paste::paste ! {
37        pub mod [<$name:snake>] {
38            use super::*;
39            $(
40            make_icon!( $icon_name, $size, $category, $file);
41
42            )*
43        }
44    }
45    }
46}
47
48make_icon!(SomeIcon, 24, "Animals", "fish");
49
50/// Struct to store icon color and properties.
51///
52/// There are two ways to instantiate an icon:
53/// ```rust
54/// # use embedded_graphics::pixelcolor::BinaryColor;
55/// # use embedded_iconoir::Icon;
56/// # use embedded_iconoir::prelude::*;
57/// // using constructors on icons (recommended)
58/// let icon = icons::size24px::actions::Download::new(BinaryColor::On);
59/// // using types
60/// let icon: Icon<_, icons::size24px::actions::Download> = Icon::new(BinaryColor::On);
61/// ```
62/// Both result in the same icon (`Icon<COLOR, ICON>`). Use whichever you prefer.
63///
64///
65///
66/// ## Full Usage Example
67///
68/// ```rust
69/// # use embedded_graphics::image::Image;
70/// # use embedded_graphics::pixelcolor::{BinaryColor};
71/// # use embedded_graphics::prelude::*;
72/// # use embedded_graphics::mock_display::MockDisplay;
73/// # let mut  display = MockDisplay::new();
74/// // Import icons and traits
75/// use embedded_iconoir::prelude::*;
76///
77/// // Create an icon
78/// let icon = icons::size24px::actions::Download::new(BinaryColor::On);
79///
80/// // Wrap it in an embedded_graphics image
81/// let image = Image::new(&icon, Point::zero());
82///
83/// // Draw it to a display
84/// image.draw(&mut display).unwrap();
85/// ```
86#[derive(Debug)]
87pub struct Icon<C, Ico>
88where
89    C: PixelColor,
90    Ico: sealed::IconoirInternal,
91{
92    color: C,
93    _icon: Ico,
94}
95
96impl<C: PixelColor, Ico: sealed::IconoirInternal> Icon<C, Ico> {
97    pub fn new(color: C) -> Self {
98        Self {
99            color,
100            _icon: Ico::INSTANCE,
101        }
102    }
103
104    pub fn set_color(&mut self, color: C) {
105        self.color = color;
106    }
107
108    pub fn get_color(&self) -> C {
109        self.color
110    }
111}
112
113/// Marker Trait for all Icons
114pub trait IconoirIcon: Sized + sealed::IconoirInternal {}
115
116impl<T> IconoirIcon for T where T: sealed::IconoirInternal {}
117
118pub trait IconoirNewIcon<C: embedded_graphics::prelude::PixelColor>: Sized
119where
120    Self: sealed::IconoirInternal,
121{
122    fn new(color: C) -> Icon<C, Self>;
123}
124
125impl<C: PixelColor, T> IconoirNewIcon<C> for T
126where
127    T: sealed::IconoirInternal,
128{
129    fn new(color: C) -> Icon<C, Self> {
130        Icon {
131            color,
132            _icon: Self::INSTANCE,
133        }
134    }
135}
136
137pub(crate) mod sealed {
138    pub trait IconoirInternal: Sized {
139        const SIZE: u32;
140        const BIT_COUNT: usize = { Self::SIZE as usize * Self::SIZE as usize };
141        const BYTE_COUNT: usize =
142            { Self::BIT_COUNT / 8 + if Self::BIT_COUNT % 8 > 0 { 1 } else { 0 } };
143        const DATA: &'static [u8];
144        const INSTANCE: Self;
145    }
146}
147
148impl<C, T> ImageDrawable for Icon<C, T>
149where
150    T: sealed::IconoirInternal,
151    C: PixelColor,
152{
153    type Color = C;
154    fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
155    where
156        D: DrawTarget<Color = Self::Color>,
157    {
158        let data = T::DATA;
159        for y in 0..T::SIZE {
160            for x in 0..T::SIZE {
161                if get_bit_unchecked(data, (x + y * T::SIZE) as usize) {
162                    Pixel(Point::new(x as i32, y as i32), self.get_color()).draw(target)?;
163                }
164            }
165        }
166        Ok(())
167    }
168
169    #[inline(always)]
170    fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
171    where
172        D: DrawTarget<Color = Self::Color>,
173    {
174        // from tinytga
175        self.draw(&mut target.translated(-area.top_left).clipped(area))
176    }
177}
178
179impl<C, T> OriginDimensions for Icon<C, T>
180where
181    T: sealed::IconoirInternal,
182    C: PixelColor,
183{
184    #[inline(always)]
185    fn size(&self) -> Size {
186        Size {
187            width: T::SIZE,
188            height: T::SIZE,
189        }
190    }
191}
192
193/// Retrieve the n-th bit from a slice of bytes
194/// without performing in-bounds checking
195fn get_bit_unchecked(target: &[u8], bit: usize) -> bool {
196    let slice_index = bit / 8;
197    let bit_index = bit % 8;
198    (target[slice_index] & (1 << bit_index)) != 0
199}