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 fn next(&mut self) {}
14
15 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 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 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}