1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(test)]
4mod tests;
5
6use core::cell::RefCell;
7
8#[cfg(not(feature = "std"))]
9mod types {
10 pub type ActionFunc<'a> = &'a (dyn Fn() + 'a + Send);
11 pub type ItemList<'a> = &'a [super::Item<'a>];
12 pub type DispFunc<'a> = &'a (dyn Fn(Name) + 'a + Send);
13 pub type Name = &'static str;
14}
15#[cfg(feature = "std")]
16mod types {
17 pub type ActionFunc<'a> = Box<dyn Fn() + 'a + Send>;
18 pub type ItemList<'a> = Vec<super::Item<'a>>;
19 pub type DispFunc<'a> = Box<dyn Fn(&str) + 'a + Send>;
20 pub type Name = String;
21}
22
23pub struct Action<'a> {
24 name: types::Name,
25 f: types::ActionFunc<'a>,
26 #[cfg(feature = "std")]
27 show_active: Option<Box<dyn Fn() -> bool + 'a + Send>>,
28}
29pub struct SubMenu<'a> {
30 name: types::Name,
31 position: RefCell<u8>,
32 items: types::ItemList<'a>,
33 #[cfg(feature = "std")]
34 show_active: Option<Box<dyn Fn() -> bool + 'a + Send>>,
35}
36
37impl SubMenu<'_> {
38 fn get_text(&self) -> types::Name {
39 self.get_item().get_name()
40 }
41
42 fn get_item(&self) -> &Item {
43 if *self.position.borrow() >= self.items.len() as u8 {
44 &Item::Back
45 } else {
46 &self.items[*self.position.borrow() as usize]
47 }
48 }
49
50 fn go_next(&self, no_back: bool) {
51 let next_pos = if no_back {
52 (*self.position.borrow() + 1) % self.items.len() as u8
53 } else {
54 (*self.position.borrow() + 1) % (self.items.len() as u8 + 1)
55 };
56 *self.position.borrow_mut() = next_pos;
57 }
58}
59
60pub enum Item<'a> {
61 Action(Action<'a>),
62 SubMenu(SubMenu<'a>),
63 Back,
64}
65
66impl<'a> Item<'a> {
67 #[cfg(not(feature = "std"))]
68 pub fn new_action(name: types::Name, f: types::ActionFunc<'a>) -> Self {
69 Self::Action(Action { name, f })
70 }
71 #[cfg(feature = "std")]
72 pub fn new_action(name: impl Into<types::Name>, f: impl Fn() + 'a + Send) -> Self {
73 Self::Action(Action {
74 name: name.into(),
75 f: Box::new(f),
76 show_active: None,
77 })
78 }
79 pub fn new_submenu(name: impl Into<types::Name>, items: types::ItemList<'a>) -> Self {
80 Self::SubMenu(SubMenu {
81 position: RefCell::new(0),
82 name: name.into(),
83 #[cfg(feature = "std")]
84 show_active: None,
85 items,
86 })
87 }
88 #[cfg(feature = "std")]
89 pub fn show_active(mut self, check_active: impl Fn() -> bool + 'a + Send) -> Self {
90 match &mut self {
91 Item::Action(action) => action.show_active = Some(Box::new(check_active)),
92 Item::SubMenu(sub) => sub.show_active = Some(Box::new(check_active)),
93 Item::Back => {}
94 }
95 self
96 }
97 #[cfg(feature = "std")]
98 fn get_name(&self) -> types::Name {
99 match self {
100 Item::Action(action) => get_text(
101 &action.name,
102 action.show_active.as_ref().is_some_and(|f| f()),
103 ),
104 Item::SubMenu(sub) => {
105 get_text(&sub.name, sub.show_active.as_ref().is_some_and(|f| f()))
106 }
107 Item::Back => "Back".into(),
108 }
109 }
110 #[cfg(not(feature = "std"))]
111 fn get_name(&self) -> types::Name {
112 match self {
113 Item::Action(action) => action.name,
114 Item::SubMenu(sub) => sub.name,
115 Item::Back => "Back".into(),
116 }
117 }
118}
119
120#[cfg(feature = "std")]
121fn get_text(name: &str, active: bool) -> String {
122 if active {
123 format!("* {name} *")
124 } else {
125 name.into()
126 }
127}
128
129pub struct Menu<'a> {
130 root: SubMenu<'a>,
131 depth: u8,
132 disp: types::DispFunc<'a>,
133}
134
135impl<'a> Menu<'a> {
136 #[cfg(not(feature = "std"))]
137 pub fn new(items: types::ItemList<'a>, disp: types::DispFunc<'a>) -> Self {
138 Self::inner_new(items, disp)
139 }
140 #[cfg(feature = "std")]
141 pub fn new(items: types::ItemList<'a>, disp: impl Fn(&str) + 'a + Send) -> Self {
142 Self::inner_new(items, Box::new(disp))
143 }
144
145 fn inner_new(items: types::ItemList<'a>, disp: types::DispFunc<'a>) -> Self {
146 let menu = Self {
147 disp,
148 depth: 0,
149 root: SubMenu {
150 name: "root".into(),
151 position: RefCell::new(0),
152 #[cfg(feature = "std")]
153 show_active: None,
154 items,
155 },
156 };
157 menu.display();
158 menu
159 }
160
161 pub fn skip(&mut self) {
163 let skip_back = self.depth == 0;
164 self.get_submenu().go_next(skip_back);
165 self.display();
166 }
167
168 pub fn ok(&mut self) {
170 let menu = self.get_submenu();
171 let item = menu.get_item();
172 match item {
173 Item::Action(action) => (action.f)(),
174 Item::SubMenu(_) => self.depth += 1,
175 Item::Back => {
176 *menu.position.borrow_mut() = 0;
177 self.depth -= 1;
178 }
179 }
180
181 self.display();
182 }
183
184 fn display(&self) {
185 let text = self.get_submenu().get_text();
186 (self.disp)(&text);
187 }
188
189 fn get_submenu(&self) -> &SubMenu {
191 let mut menu = &self.root;
192 for _ in 0..self.depth {
193 if let Item::SubMenu(sub) = &menu.items[*menu.position.borrow() as usize] {
194 menu = sub;
195 } else {
196 panic!("attemped to select sub_menu on wrong item");
197 }
198 }
199 menu
200 }
201}