1use crate::{
2 collection::{MenuItemCollection, MenuItems},
3 interaction::{InputAdapterSource, InputState},
4 items::{menu_item::SelectValue, MenuItem, MenuListItem},
5 selection_indicator::{style::IndicatorStyle, SelectionIndicatorController},
6 theme::Theme,
7 Menu, MenuState, MenuStyle, NoItems,
8};
9use core::marker::PhantomData;
10use embedded_layout::{
11 layout::linear::LinearLayout,
12 object_chain::ChainElement,
13 prelude::*,
14 view_group::{EmptyViewGroup, ViewGroup},
15};
16
17pub struct MenuBuilder<T, IT, LL, R, P, S, C>
18where
19 T: AsRef<str>,
20 IT: InputAdapterSource<R>,
21 S: IndicatorStyle,
22 P: SelectionIndicatorController,
23 C: Theme,
24{
25 title: T,
26 items: LL,
27 style: MenuStyle<S, IT, P, R, C>,
28}
29
30impl<T, R, S, IT, P, C> MenuBuilder<T, IT, NoItems, R, P, S, C>
31where
32 T: AsRef<str>,
33 S: IndicatorStyle,
34 IT: InputAdapterSource<R>,
35 P: SelectionIndicatorController,
36 C: Theme,
37{
38 pub const fn new(title: T, style: MenuStyle<S, IT, P, R, C>) -> Self {
40 Self {
41 title,
42 items: NoItems,
43 style,
44 }
45 }
46}
47
48impl<T, IT, R, P, S, C> MenuBuilder<T, IT, NoItems, R, P, S, C>
49where
50 T: AsRef<str>,
51 IT: InputAdapterSource<R>,
52 P: SelectionIndicatorController,
53 S: IndicatorStyle,
54 C: Theme,
55{
56 pub fn add_section_title<T2: AsRef<str>>(
58 self,
59 title: T2,
60 ) -> MenuBuilder<T, IT, Chain<MenuItem<T2, R, (), false>>, R, P, S, C> {
61 self.add_menu_item(
62 MenuItem::new(title, ())
63 .with_value_converter(|_| unreachable!())
64 .selectable::<false>(),
65 )
66 }
67
68 pub fn add_item<T2: AsRef<str>, V: SelectValue>(
74 self,
75 title: T2,
76 value: V,
77 converter: fn(V) -> R,
78 ) -> MenuBuilder<T, IT, Chain<MenuItem<T2, R, V, true>>, R, P, S, C> {
79 self.add_menu_item(MenuItem::new(title, value).with_value_converter(converter))
80 }
81
82 pub fn add_menu_item<I: MenuListItem<R>>(
84 self,
85 mut item: I,
86 ) -> MenuBuilder<T, IT, Chain<I>, R, P, S, C> {
87 item.set_style(&self.style.text_style());
88
89 MenuBuilder {
90 title: self.title,
91 items: Chain::new(item),
92 style: self.style,
93 }
94 }
95
96 pub fn add_menu_items<I, IC>(
98 self,
99 mut items: IC,
100 ) -> MenuBuilder<T, IT, Chain<MenuItems<IC, I, R>>, R, P, S, C>
101 where
102 I: MenuListItem<R>,
103 IC: AsRef<[I]> + AsMut<[I]>,
104 {
105 items
106 .as_mut()
107 .iter_mut()
108 .for_each(|i| i.set_style(&self.style.text_style()));
109
110 MenuBuilder {
111 title: self.title,
112 items: Chain::new(MenuItems::new(items)),
113 style: self.style,
114 }
115 }
116}
117
118impl<T, IT, CE, R, P, S, C> MenuBuilder<T, IT, CE, R, P, S, C>
119where
120 T: AsRef<str>,
121 IT: InputAdapterSource<R>,
122 CE: MenuItemCollection<R> + ChainElement,
123 P: SelectionIndicatorController,
124 S: IndicatorStyle,
125 C: Theme,
126{
127 pub fn add_section_title<T2: AsRef<str>>(
129 self,
130 title: T2,
131 ) -> MenuBuilder<T, IT, Link<MenuItem<T2, R, (), false>, CE>, R, P, S, C> {
132 self.add_menu_item(
133 MenuItem::new(title, ())
134 .with_value_converter(|_| unreachable!())
135 .selectable::<false>(),
136 )
137 }
138
139 pub fn add_item<T2: AsRef<str>, V: SelectValue>(
145 self,
146 title: T2,
147 value: V,
148 converter: fn(V) -> R,
149 ) -> MenuBuilder<T, IT, Link<MenuItem<T2, R, V, true>, CE>, R, P, S, C> {
150 self.add_menu_item(MenuItem::new(title, value).with_value_converter(converter))
151 }
152
153 pub fn add_menu_item<I: MenuListItem<R>>(
155 self,
156 mut item: I,
157 ) -> MenuBuilder<T, IT, Link<I, CE>, R, P, S, C> {
158 item.set_style(&self.style.text_style());
159
160 MenuBuilder {
161 title: self.title,
162 items: Link {
163 parent: self.items,
164 object: item,
165 },
166 style: self.style,
167 }
168 }
169
170 pub fn add_menu_items<I, IC>(
172 self,
173 mut items: IC,
174 ) -> MenuBuilder<T, IT, Link<MenuItems<IC, I, R>, CE>, R, P, S, C>
175 where
176 I: MenuListItem<R>,
177 IC: AsRef<[I]> + AsMut<[I]>,
178 {
179 items
180 .as_mut()
181 .iter_mut()
182 .for_each(|i| i.set_style(&self.style.text_style()));
183
184 MenuBuilder {
185 title: self.title,
186 items: Link {
187 parent: self.items,
188 object: MenuItems::new(items),
189 },
190 style: self.style,
191 }
192 }
193}
194
195impl<T, IT, VG, R, P, S, C> MenuBuilder<T, IT, VG, R, P, S, C>
196where
197 T: AsRef<str>,
198 IT: InputAdapterSource<R>,
199 VG: ViewGroup + MenuItemCollection<R>,
200 P: SelectionIndicatorController,
201 S: IndicatorStyle,
202 C: Theme,
203{
204 pub fn build(self) -> Menu<T, IT, VG, R, P, S, C> {
206 self.build_with_state(MenuState {
207 selected: 0,
208 list_offset: 0,
209 interaction_state: Default::default(),
210 indicator_state: Default::default(),
211 last_input_state: InputState::Idle,
212 })
213 }
214
215 pub fn build_with_state(
217 mut self,
218 mut state: MenuState<IT::InputAdapter, P, S>,
219 ) -> Menu<T, IT, VG, R, P, S, C> {
220 let max_idx = self.items.count().saturating_sub(1);
222
223 LinearLayout::vertical(EmptyViewGroup).arrange_view_group(&mut self.items);
224
225 state.set_selected_item(state.selected, &self.items, &self.style);
226 if max_idx < state.selected {
227 self.style
228 .indicator
229 .jump_to_target(&mut state.indicator_state);
230 }
231
232 Menu {
233 state,
234 _return_type: PhantomData,
235 title: self.title,
236 items: self.items,
237 style: self.style,
238 }
239 }
240}