embedded_ui/kit/
icon.rs

1use embedded_graphics::geometry::Point;
2use embedded_graphics::iterator::raw::RawDataSlice;
3use embedded_graphics::pixelcolor::raw::{BigEndian, RawData, RawU1};
4
5use crate::el::El;
6use crate::icons::icons5::Icons5;
7use crate::icons::{IconData, IconKind, IconSet};
8use crate::layout::{LayoutNode, Limits, Viewport};
9use crate::log::logger::warning;
10use crate::size::Length;
11use crate::{color::UiColor, event::Event, render::Renderer, size::Size, widget::Widget};
12
13pub struct IconPicker;
14
15impl IconPicker {
16    pub fn by_size(&self, size: u32, kind: IconKind) -> Option<IconData> {
17        match size {
18            5.. => Icons5.pick(kind),
19            _ => None,
20        }
21    }
22
23    pub fn flex_size(&self, length: Length, limits: &Limits) -> u32 {
24        let fit_square = limits.resolve_square(length);
25        match fit_square {
26            5.. => 5,
27            _ => 0,
28        }
29    }
30
31    pub fn flex(&self, length: Length, limits: &Limits, kind: IconKind) -> Option<IconData> {
32        let size = self.flex_size(length, limits);
33        self.by_size(size, kind)
34    }
35}
36
37#[derive(Clone, Copy)]
38pub struct Icon<R>
39where
40    R: Renderer,
41{
42    size: Length,
43    kind: IconKind,
44    color: R::Color,
45    background: R::Color,
46}
47
48impl<R> Icon<R>
49where
50    R: Renderer,
51{
52    pub fn new(kind: IconKind) -> Self {
53        // assert_eq!((size * size).div_ceil(8) as usize, data.len());
54
55        Self {
56            size: Length::Fill,
57            kind,
58            color: R::Color::default_foreground(),
59            background: R::Color::default_background(),
60        }
61    }
62
63    pub fn color(mut self, color: impl Into<R::Color>) -> Self {
64        self.color = color.into();
65        self
66    }
67
68    pub fn background(mut self, background: impl Into<R::Color>) -> Self {
69        self.background = background.into();
70        self
71    }
72
73    pub fn invert(mut self) -> Self {
74        self.color = self.background;
75        self.background = self.color;
76        self
77    }
78
79    pub fn size(mut self, size: impl Into<Length>) -> Self {
80        self.size = size.into();
81        self
82    }
83
84    pub fn do_invert(self, invert: bool) -> Self {
85        if invert {
86            self.invert()
87        } else {
88            self
89        }
90    }
91}
92
93impl<Message, R, E, S> Widget<Message, R, E, S> for Icon<R>
94where
95    R: Renderer,
96    E: Event,
97{
98    fn id(&self) -> Option<crate::el::ElId> {
99        None
100    }
101
102    fn tree_ids(&self) -> alloc::vec::Vec<crate::el::ElId> {
103        vec![]
104    }
105
106    fn size(&self) -> crate::size::Size<crate::size::Length> {
107        Size::new_equal(self.size)
108    }
109
110    fn layout(
111        &self,
112        _ctx: &mut crate::ui::UiCtx<Message>,
113        _state: &mut crate::state::StateNode,
114        _styler: &S,
115        limits: &crate::layout::Limits,
116        _viewport: &Viewport,
117    ) -> crate::layout::LayoutNode {
118        let size = Size::new_equal(IconPicker.flex_size(self.size, limits));
119
120        LayoutNode::new(limits.resolve_size(size.width, size.height, size))
121    }
122
123    fn draw(
124        &self,
125        _ctx: &mut crate::ui::UiCtx<Message>,
126        _state: &mut crate::state::StateNode,
127        renderer: &mut R,
128        _styler: &S,
129        layout: crate::layout::Layout,
130    ) {
131        let bounds = layout.bounds();
132        let bounds_size = bounds.size.max_square();
133        let icon = IconPicker.by_size(bounds_size, self.kind);
134
135        // TODO: Warn that icon cannot be drawn because no fitted options found
136
137        if let Some(icon) = icon {
138            let icon_size = icon.size;
139
140            // Align icon to the center of bounds
141            // TODO: This may be useless as layout is always of size of the icon
142            let icon_position = bounds.position
143                + Point::new(bounds.size.width as i32, bounds.size.height as i32) / 2
144                - Point::new_equal(icon_size as i32) / 2;
145
146            let bits_iter = RawDataSlice::<RawU1, BigEndian>::new(&icon.data).into_iter();
147
148            let data_width = icon_size.max(8);
149
150            for (index, bit) in bits_iter.enumerate() {
151                if index % data_width as usize >= icon_size as usize {
152                    continue;
153                }
154
155                let y = index / data_width as usize;
156                let x = index % data_width as usize;
157
158                let point = Point::new(x as i32, y as i32) + icon_position;
159
160                let color = match bit.into_inner() {
161                    0 => self.background,
162                    1 => self.color,
163                    _ => unreachable!(),
164                };
165
166                renderer.pixel(point, color);
167            }
168        } else {
169            warning!(
170                "Icon cannot be rendered: icon size: {:?} does not fit into box of size {:?}",
171                bounds_size,
172                bounds.size
173            );
174        }
175    }
176}
177
178impl<'a, Message, R, E, S> From<Icon<R>> for El<'a, Message, R, E, S>
179where
180    Message: 'a,
181    R: Renderer + 'a,
182    E: Event + 'a,
183    S: 'a,
184{
185    fn from(value: Icon<R>) -> Self {
186        Self::new(value)
187    }
188}
189
190impl<'a, Message, R, E, S> From<IconKind> for El<'a, Message, R, E, S>
191where
192    Message: 'a,
193    R: Renderer + 'a,
194    E: Event + 'a,
195    S: 'a,
196{
197    fn from(value: IconKind) -> Self {
198        El::new(Icon::new(value))
199    }
200}
201
202impl<R: Renderer> Into<Icon<R>> for IconKind {
203    fn into(self) -> Icon<R> {
204        Icon::new(self)
205    }
206}