1use crate::ui::menu_item_view::*;
2use crate::ui::prelude::*;
3use std::hash::Hash;
4
5#[derive(Debug)]
6pub struct MenuBar<Key: Hash + Copy + PartialEq + Eq + Debug> {
7 bounds: Rect,
8 full_bounds: Rect,
9 style: MenuBarStyle,
10 items: Vec<MenuItemView<Key>>,
11 state: ViewState,
12 fill_width: bool,
13 screen_size: (usize, usize),
14}
15
16#[derive(Debug)]
17pub struct MenuBarItem<Key: Hash + Copy + PartialEq + Eq + Debug> {
18 pub(crate) id: Key,
19 pub(crate) name: String,
20 pub(crate) children: Option<Vec<MenuBarItem<Key>>>,
21 pub(crate) checkable: Option<bool>,
22}
23
24impl<Key: Hash + Copy + PartialEq + Eq + Debug> MenuBarItem<Key> {
25 pub fn new_button(id: Key, name: &str) -> Self {
27 Self {
28 id,
29 name: name.to_string(),
30 children: None,
31 checkable: None,
32 }
33 }
34
35 pub fn new_checkable(id: Key, name: &str, default: bool) -> Self {
38 Self {
39 id,
40 name: name.to_string(),
41 checkable: Some(default),
42 children: None,
43 }
44 }
45
46 pub fn new_options(id: Key, name: &str, children: &[(Key, &str)], default: usize) -> Self {
47 let default = if default >= children.len() {
48 0
49 } else {
50 default
51 };
52 Self {
53 id,
54 name: name.to_string(),
55 children: Some(
56 children
57 .iter()
58 .enumerate()
59 .map(|(i, p)| MenuBarItem::new_checkable(p.0, p.1, i == default))
60 .collect(),
61 ),
62 checkable: None,
63 }
64 }
65
66 pub fn new_menu(id: Key, name: &str, children: &[(Key, &str)]) -> Self {
68 Self {
69 id,
70 name: name.to_string(),
71 children: Some(
72 children
73 .iter()
74 .map(|p| MenuBarItem::new_button(p.0, p.1))
75 .collect(),
76 ),
77 checkable: None,
78 }
79 }
80
81 pub fn new(id: Key, name: &str, children: Vec<MenuBarItem<Key>>) -> Self {
82 Self {
83 id,
84 name: name.to_string(),
85 children: Some(children),
86 checkable: None,
87 }
88 }
89}
90
91impl<Key: Hash + Copy + PartialEq + Eq + Debug> MenuBar<Key> {
92 pub fn new(
93 style: &MenuBarStyle,
94 pos: Coord,
95 screen_size: (usize, usize),
96 fill_width: bool,
97 items: &[MenuBarItem<Key>],
98 ) -> MenuBar<Key> {
99 let views: Vec<MenuItemView<Key>> = items
100 .iter()
101 .map(|item| MenuItemView::new(item, true))
102 .collect();
103 let bounds = Rect::new(pos, (0, 0));
104 let mut menu_bar = MenuBar {
105 full_bounds: bounds.clone(),
106 bounds,
107 style: style.clone(),
108 items: views,
109 state: ViewState::Normal,
110 fill_width,
111 screen_size,
112 };
113 menu_bar.layout();
114 menu_bar
115 }
116
117 fn layout(&mut self) {
118 self.bounds = layout_titles(
119 self.bounds.top_left(),
120 &self.style,
121 self.screen_size,
122 self.fill_width,
123 &mut self.items,
124 );
125
126 self.calc_bounds();
127 }
128}
129
130impl<Key: Hash + Copy + PartialEq + Eq + Debug> MenuBar<Key> {
131 pub fn full_bounds(&self) -> &Rect {
132 &self.full_bounds
133 }
134
135 pub fn on_mouse_move(&mut self, xy: Coord) {
136 if self.full_bounds.contains(xy) {
137 self.items.iter_mut().for_each(|c| (*c).on_mouse_move(xy));
138 }
139 self.calc_bounds();
140 }
141
142 pub fn on_mouse_click(&mut self, down_at: Coord, up_at: Coord) -> Option<Key> {
151 if !self.full_bounds.contains(up_at) {
152 self.collapse();
153 return None;
154 }
155 let path = on_click_path(&self.items, down_at, up_at);
156 self.collapse();
157 path
158 }
159
160 pub fn is_expanded(&self) -> bool {
161 self.items.iter().any(|v| v.focused)
162 }
163
164 pub fn label_for(&self, id: Key) -> Option<&str> {
166 self.get_view(id).map(|v| v.name.as_str())
167 }
168
169 pub fn set_state(&mut self, id: Key, new_state: ViewState) {
173 if let Some(v) = self.get_view_mut(id) {
174 v.state = new_state;
175 if new_state == ViewState::Disabled {
176 v.focused = false;
177 }
178 }
179 self.calc_bounds();
180 }
181
182 pub fn get_state(&self, id: Key) -> Option<ViewState> {
186 self.get_view(id).map(|v| v.state)
187 }
188
189 pub fn is_checked(&self, id: Key) -> Option<bool> {
191 self.get_view(id).and_then(|v| {
192 if let ItemContent::Checkable(checked) = v.content {
193 Some(checked)
194 } else {
195 None
196 }
197 })
198 }
199
200 pub fn set_checked(&mut self, id: Key, value: bool) {
203 if let Some(view) = self.get_view_mut(id) {
204 if let ItemContent::Checkable(checked) = &mut view.content {
205 *checked = value;
206 }
207 }
208 }
209
210 pub fn uncheck_all_children(&mut self, id: Key) {
212 if let Some(view) = self.get_view_mut(id) {
213 if let ItemContent::Parent(children, _, _) = &mut view.content {
214 for child in children {
215 if matches!(child.content, ItemContent::Checkable(_)) {
216 child.content = ItemContent::Checkable(false);
217 }
218 }
219 }
220 }
221 }
222
223 fn get_view(&self, id: Key) -> Option<&MenuItemView<Key>> {
224 Self::get_view_from_list(&self.items, id)
225 }
226
227 fn get_view_from_list(list: &[MenuItemView<Key>], id: Key) -> Option<&MenuItemView<Key>> {
228 for item in list {
229 if item.id == id {
230 return Some(item);
231 } else if let ItemContent::Parent(children, _, _) = &item.content {
232 let result = Self::get_view_from_list(children, id);
233 if result.is_some() {
234 return result;
235 }
236 }
237 }
238 None
239 }
240
241 fn get_view_mut(&mut self, id: Key) -> Option<&mut MenuItemView<Key>> {
242 Self::get_view_mut_from_list(&mut self.items, id)
243 }
244
245 fn get_view_mut_from_list(
246 list: &mut [MenuItemView<Key>],
247 id: Key,
248 ) -> Option<&mut MenuItemView<Key>> {
249 for item in list {
250 if item.id == id {
251 return Some(item);
252 } else if let ItemContent::Parent(children, _, _) = &mut item.content {
253 let result = Self::get_view_mut_from_list(children, id);
254 if result.is_some() {
255 return result;
256 }
257 }
258 }
259 None
260 }
261
262 fn calc_bounds(&mut self) {
263 self.full_bounds = self.bounds.clone();
264 for children in &self.items {
265 if let Some(extra) = focused_bounds(children) {
266 self.full_bounds = union(&self.full_bounds, &extra);
267 }
268 }
269 }
270
271 pub fn collapse(&mut self) {
272 collapse_menu(&mut self.items);
273 }
274}
275
276impl<Key: Hash + Copy + PartialEq + Eq + Debug> PixelView for MenuBar<Key> {
277 fn set_position(&mut self, top_left: Coord) {
278 self.bounds = self.bounds.move_to(top_left);
279 self.layout();
280 }
281
282 fn bounds(&self) -> &Rect {
285 &self.bounds
286 }
287
288 fn render(&self, graphics: &mut Graphics, mouse: &MouseData) {
289 let hovering = self.bounds.contains(mouse.xy);
290 let (error, disabled) = self.state.get_err_dis();
291 if let Some(bg) = self.style.background.get(hovering, error, disabled) {
292 graphics.draw_rect(self.bounds.clone(), fill(bg));
293 }
294 draw_titles(graphics, mouse.xy, &self.style, &self.items);
295 }
296
297 fn update(&mut self, _: &Timing) {}
298
299 fn set_state(&mut self, new_state: ViewState) {
300 self.state = new_state;
301 }
302
303 fn get_state(&self) -> ViewState {
304 self.state
305 }
306}
307
308impl<Key: Hash + Copy + PartialEq + Eq + Debug> LayoutView for MenuBar<Key> {
309 fn set_bounds(&mut self, bounds: Rect) {
310 self.bounds = bounds;
311 self.layout();
312 }
313}