embedded_menu/items/
menu_item.rs

1use embedded_graphics::{
2    mono_font::MonoTextStyle,
3    pixelcolor::BinaryColor,
4    prelude::{DrawTarget, Point},
5    primitives::Rectangle,
6};
7use embedded_layout::View;
8
9use crate::items::{Marker, MenuLine, MenuListItem};
10
11pub trait SelectValue: Sized + Clone + PartialEq {
12    /// Transforms the value on interaction
13    fn next(&mut self) {}
14
15    /// Returns a displayable marker for the value
16    fn marker(&self) -> &str;
17}
18
19impl SelectValue for bool {
20    fn next(&mut self) {
21        *self = !*self;
22    }
23
24    fn marker(&self) -> &str {
25        match *self {
26            // true => "O",
27            // false => "O\r+\r#", // this only works for certain small fonts, unfortunately
28            false => "[ ]",
29            true => "[X]",
30        }
31    }
32}
33
34impl SelectValue for &str {
35    fn marker(&self) -> &str {
36        self
37    }
38}
39
40impl SelectValue for () {
41    fn marker(&self) -> &str {
42        ""
43    }
44}
45
46pub struct MenuItem<T, R, S, const SELECTABLE: bool>
47where
48    T: AsRef<str>,
49    S: SelectValue,
50{
51    title_text: T,
52    convert: fn(S) -> R,
53    value: S,
54    line: MenuLine,
55}
56
57impl<T, S> MenuItem<T, (), S, true>
58where
59    T: AsRef<str>,
60    S: SelectValue,
61{
62    pub fn new(title_text: T, value: S) -> Self {
63        Self {
64            title_text,
65            value,
66            convert: |_| (),
67            line: MenuLine::empty(),
68        }
69    }
70}
71
72impl<T, R, S, const SELECTABLE: bool> MenuItem<T, R, S, SELECTABLE>
73where
74    T: AsRef<str>,
75    S: SelectValue,
76{
77    pub fn with_value_converter<R2>(self, convert: fn(S) -> R2) -> MenuItem<T, R2, S, SELECTABLE> {
78        MenuItem {
79            convert,
80            title_text: self.title_text,
81            value: self.value,
82            line: self.line,
83        }
84    }
85
86    /// Make the item selectable or not
87    pub fn selectable<const SELECTABLE2: bool>(self) -> MenuItem<T, R, S, SELECTABLE2> {
88        MenuItem {
89            convert: self.convert,
90            title_text: self.title_text,
91            value: self.value,
92            line: self.line,
93        }
94    }
95}
96
97impl<T, R, S, const SELECTABLE: bool> Marker for MenuItem<T, R, S, SELECTABLE>
98where
99    T: AsRef<str>,
100    S: SelectValue,
101{
102}
103
104impl<T, R, S, const SELECTABLE: bool> MenuListItem<R> for MenuItem<T, R, S, SELECTABLE>
105where
106    T: AsRef<str>,
107    S: SelectValue,
108{
109    fn value_of(&self) -> R {
110        (self.convert)(self.value.clone())
111    }
112
113    fn interact(&mut self) -> R {
114        self.value.next();
115        self.value_of()
116    }
117
118    fn selectable(&self) -> bool {
119        SELECTABLE
120    }
121
122    fn set_style(&mut self, text_style: &MonoTextStyle<'_, BinaryColor>) {
123        let mut current = self.value.clone();
124        let mut longest = self.value.clone();
125
126        let mut longest_len = longest.marker().len();
127
128        loop {
129            current.next();
130            if current == self.value {
131                break;
132            }
133
134            if current.marker().len() > longest_len {
135                longest = current.clone();
136                longest_len = longest.marker().len();
137            }
138        }
139
140        self.line = MenuLine::new(longest.marker(), text_style);
141    }
142
143    fn draw_styled<D>(
144        &self,
145        text_style: &MonoTextStyle<'static, BinaryColor>,
146        display: &mut D,
147    ) -> Result<(), D::Error>
148    where
149        D: DrawTarget<Color = BinaryColor>,
150    {
151        self.line.draw_styled(
152            self.title_text.as_ref(),
153            self.value.marker(),
154            text_style,
155            display,
156        )
157    }
158}
159
160impl<T, R, S, const SELECTABLE: bool> View for MenuItem<T, R, S, SELECTABLE>
161where
162    T: AsRef<str>,
163    S: SelectValue,
164{
165    fn translate_impl(&mut self, by: Point) {
166        self.line.translate_mut(by);
167    }
168
169    fn bounds(&self) -> Rectangle {
170        self.line.bounds()
171    }
172}
173
174#[cfg(test)]
175mod test {
176    #[test]
177    fn interaction_selects_next_value_and_returns_converted() {
178        use super::*;
179        use crate::items::MenuListItem;
180
181        let mut item = MenuItem::new("title", false).with_value_converter(|b| b as u8);
182
183        assert_eq!(item.value_of(), 0);
184
185        assert_eq!(item.interact(), 1);
186        assert_eq!(item.value_of(), 1);
187
188        assert_eq!(item.interact(), 0);
189        assert_eq!(item.value_of(), 0);
190    }
191}