embedded_menu/
collection.rs

1use core::marker::PhantomData;
2
3use embedded_graphics::{
4    mono_font::MonoTextStyle,
5    pixelcolor::BinaryColor,
6    prelude::{DrawTarget, Point, Size},
7    primitives::Rectangle,
8};
9use embedded_layout::{object_chain::ChainElement, prelude::*, view_group::ViewGroup};
10
11use crate::items::{Marker, MenuListItem};
12
13/// Menu-related extensions for object chain elements
14pub trait MenuItemCollection<R> {
15    fn bounds_of(&self, nth: usize) -> Rectangle;
16    fn value_of(&self, nth: usize) -> R;
17    fn interact_with(&mut self, nth: usize) -> R;
18    /// Whether an item is selectable. If not, the item will be skipped.
19    fn selectable(&self, nth: usize) -> bool;
20    fn count(&self) -> usize;
21    fn draw_styled<D>(
22        &self,
23        text_style: &MonoTextStyle<'static, BinaryColor>,
24        display: &mut D,
25    ) -> Result<(), D::Error>
26    where
27        D: DrawTarget<Color = BinaryColor>;
28}
29
30// Treat any MenuItem impl as a 1-element collection
31impl<I, R> MenuItemCollection<R> for I
32where
33    I: MenuListItem<R> + Marker,
34{
35    fn bounds_of(&self, nth: usize) -> Rectangle {
36        debug_assert!(nth == 0);
37        self.bounds()
38    }
39
40    fn value_of(&self, nth: usize) -> R {
41        debug_assert!(nth == 0);
42        self.value_of()
43    }
44
45    fn interact_with(&mut self, nth: usize) -> R {
46        debug_assert!(nth == 0);
47        self.interact()
48    }
49
50    fn selectable(&self, nth: usize) -> bool {
51        debug_assert!(nth == 0);
52        self.selectable()
53    }
54
55    fn count(&self) -> usize {
56        1
57    }
58
59    fn draw_styled<D>(
60        &self,
61        text_style: &MonoTextStyle<'static, BinaryColor>,
62        display: &mut D,
63    ) -> Result<(), D::Error>
64    where
65        D: DrawTarget<Color = BinaryColor>,
66    {
67        MenuListItem::draw_styled(self, text_style, display)
68    }
69}
70
71pub struct MenuItems<C, I, R>
72where
73    C: AsRef<[I]> + AsMut<[I]>,
74    I: MenuListItem<R>,
75{
76    items: C,
77    /// Used to keep track of the whole collection's position in case it's empty.
78    position: Point,
79    _marker: PhantomData<(I, R)>,
80}
81
82impl<C, I, R> MenuItems<C, I, R>
83where
84    C: AsRef<[I]> + AsMut<[I]>,
85    I: MenuListItem<R>,
86{
87    pub fn new(mut items: C) -> Self {
88        let mut offset = 0;
89
90        for item in items.as_mut().iter_mut() {
91            item.translate_mut(Point::new(0, offset));
92            offset += item.bounds().size.height as i32;
93        }
94
95        Self {
96            items,
97            position: Point::zero(),
98            _marker: PhantomData,
99        }
100    }
101}
102
103impl<C, I, R> MenuItemCollection<R> for MenuItems<C, I, R>
104where
105    C: AsRef<[I]> + AsMut<[I]>,
106    I: MenuListItem<R>,
107{
108    fn bounds_of(&self, nth: usize) -> Rectangle {
109        self.items.as_ref()[nth].bounds()
110    }
111
112    fn value_of(&self, nth: usize) -> R {
113        self.items.as_ref()[nth].value_of()
114    }
115
116    fn interact_with(&mut self, nth: usize) -> R {
117        self.items.as_mut()[nth].interact()
118    }
119
120    fn selectable(&self, nth: usize) -> bool {
121        self.items.as_ref()[nth].selectable()
122    }
123
124    fn count(&self) -> usize {
125        self.items.as_ref().len()
126    }
127
128    fn draw_styled<D>(
129        &self,
130        text_style: &MonoTextStyle<'static, BinaryColor>,
131        display: &mut D,
132    ) -> Result<(), D::Error>
133    where
134        D: DrawTarget<Color = BinaryColor>,
135    {
136        for item in self.items.as_ref() {
137            item.draw_styled(text_style, display)?;
138        }
139
140        Ok(())
141    }
142}
143
144impl<C, I, R> View for MenuItems<C, I, R>
145where
146    C: AsRef<[I]> + AsMut<[I]>,
147    I: MenuListItem<R>,
148{
149    fn translate_impl(&mut self, by: Point) {
150        self.position += by;
151        for view in self.items.as_mut().iter_mut() {
152            view.translate_impl(by);
153        }
154    }
155
156    fn bounds(&self) -> Rectangle {
157        let mut size = Size::zero();
158
159        for view in self.items.as_ref().iter() {
160            let view_size = view.bounds().size;
161            size = Size::new(
162                size.width.max(view_size.width),
163                size.height + view_size.height,
164            );
165        }
166
167        Rectangle::new(self.position, size)
168    }
169}
170
171impl<C, I, R> ViewGroup for MenuItems<C, I, R>
172where
173    C: AsRef<[I]> + AsMut<[I]>,
174    I: MenuListItem<R>,
175{
176    fn len(&self) -> usize {
177        self.count()
178    }
179
180    fn at(&self, idx: usize) -> &dyn View {
181        &self.items.as_ref()[idx]
182    }
183
184    fn at_mut(&mut self, idx: usize) -> &mut dyn View {
185        &mut self.items.as_mut()[idx]
186    }
187}
188
189impl<I, R> MenuItemCollection<R> for Chain<I>
190where
191    I: MenuItemCollection<R>,
192{
193    fn bounds_of(&self, nth: usize) -> Rectangle {
194        self.object.bounds_of(nth)
195    }
196
197    fn value_of(&self, nth: usize) -> R {
198        self.object.value_of(nth)
199    }
200
201    fn interact_with(&mut self, nth: usize) -> R {
202        self.object.interact_with(nth)
203    }
204
205    fn selectable(&self, nth: usize) -> bool {
206        self.object.selectable(nth)
207    }
208
209    fn count(&self) -> usize {
210        self.object.count()
211    }
212
213    fn draw_styled<D>(
214        &self,
215        text_style: &MonoTextStyle<'static, BinaryColor>,
216        display: &mut D,
217    ) -> Result<(), D::Error>
218    where
219        D: DrawTarget<Color = BinaryColor>,
220    {
221        self.object.draw_styled(text_style, display)
222    }
223}
224
225impl<I, LE, R> MenuItemCollection<R> for Link<I, LE>
226where
227    I: MenuItemCollection<R>,
228    LE: MenuItemCollection<R> + ChainElement,
229{
230    fn bounds_of(&self, nth: usize) -> Rectangle {
231        let count = self.parent.count();
232        if nth < count {
233            self.parent.bounds_of(nth)
234        } else {
235            self.object.bounds_of(nth - count)
236        }
237    }
238
239    fn value_of(&self, nth: usize) -> R {
240        let count = self.parent.count();
241        if nth < count {
242            self.parent.value_of(nth)
243        } else {
244            self.object.value_of(nth - count)
245        }
246    }
247
248    fn interact_with(&mut self, nth: usize) -> R {
249        let count = self.parent.count();
250        if nth < count {
251            self.parent.interact_with(nth)
252        } else {
253            self.object.interact_with(nth - count)
254        }
255    }
256
257    fn selectable(&self, nth: usize) -> bool {
258        let count = self.parent.count();
259        if nth < count {
260            self.parent.selectable(nth)
261        } else {
262            self.object.selectable(nth - count)
263        }
264    }
265
266    fn count(&self) -> usize {
267        self.object.count() + self.parent.count()
268    }
269
270    fn draw_styled<D>(
271        &self,
272        text_style: &MonoTextStyle<'static, BinaryColor>,
273        display: &mut D,
274    ) -> Result<(), D::Error>
275    where
276        D: DrawTarget<Color = BinaryColor>,
277    {
278        self.parent.draw_styled(text_style, display)?;
279        self.object.draw_styled(text_style, display)?;
280
281        Ok(())
282    }
283}