1use super::{BoxedMenu, Menu, SubItems};
9use crate::{AccessLabel, Mark};
10use kas::event::{Command, FocusSource};
11use kas::layout::{self, RulesSetter, RulesSolver};
12use kas::prelude::*;
13use kas::theme::{FrameStyle, MarkStyle, TextClass};
14use kas::Popup;
15
16impl_scope! {
17 #[widget {
19 layout = self.label;
20 }]
21 pub struct SubMenu<Data> {
22 core: widget_core!(),
23 pub(crate) navigable: bool,
24 #[widget(&())]
25 label: AccessLabel,
26 #[widget(&())]
28 mark: Mark,
29 #[widget]
30 popup: Popup<MenuView<BoxedMenu<Data>>>,
31 }
32
33 impl Self {
34 pub fn right<S: Into<AccessString>>(label: S, list: Vec<BoxedMenu<Data>>) -> Self {
36 SubMenu::new(label, list, Direction::Right)
37 }
38
39 pub fn down<S: Into<AccessString>>(label: S, list: Vec<BoxedMenu<Data>>) -> Self {
41 SubMenu::new(label, list, Direction::Down)
42 }
43
44 #[inline]
46 pub fn new<S: Into<AccessString>>(
47 label: S,
48 list: Vec<BoxedMenu<Data>>,
49 direction: Direction,
50 ) -> Self {
51 SubMenu {
52 core: Default::default(),
53 navigable: true,
54 label: AccessLabel::new(label).with_class(TextClass::MenuLabel),
55 mark: Mark::new(MarkStyle::Point(direction)),
56 popup: Popup::new(MenuView::new(list), direction),
57 }
58 }
59
60 fn open_menu(&mut self, cx: &mut EventCx, data: &Data, set_focus: bool) {
61 if self.popup.open(cx, data, self.id()) {
62 if set_focus {
63 cx.next_nav_focus(self.id(), false, FocusSource::Key);
64 }
65 }
66 }
67
68 fn handle_dir_key(&mut self, cx: &mut EventCx, data: &Data, cmd: Command) -> IsUsed {
69 if self.menu_is_open() {
70 if let Some(dir) = cmd.as_direction() {
71 if dir.is_vertical() {
72 let rev = dir.is_reversed();
73 cx.next_nav_focus(None, rev, FocusSource::Key);
74 Used
75 } else if dir == self.popup.direction().reversed() {
76 self.popup.close(cx);
77 Used
78 } else {
79 Unused
80 }
81 } else if matches!(cmd, Command::Home | Command::End) {
82 cx.clear_nav_focus();
83 let rev = cmd == Command::End;
84 cx.next_nav_focus(self.id(), rev, FocusSource::Key);
85 Used
86 } else {
87 Unused
88 }
89 } else if Some(self.popup.direction()) == cmd.as_direction() {
90 self.open_menu(cx, data, true);
91 Used
92 } else {
93 Unused
94 }
95 }
96 }
97
98 impl kas::Layout for Self {
99 fn nav_next(&self, _: bool, _: Option<usize>) -> Option<usize> {
100 None
102 }
103
104 fn find_id(&mut self, coord: Coord) -> Option<Id> {
105 self.rect().contains(coord).then(|| self.id())
106 }
107
108 fn draw(&mut self, mut draw: DrawCx) {
109 draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default());
110 self.label.draw(draw.re_id(self.id()));
111 if self.mark.rect().size != Size::ZERO {
112 draw.recurse(&mut self.mark);
113 }
114 }
115 }
116
117 impl Events for Self {
118 type Data = Data;
119
120 fn navigable(&self) -> bool {
121 self.navigable
122 }
123
124 fn handle_event(&mut self, cx: &mut EventCx, data: &Data, event: Event) -> IsUsed {
125 match event {
126 Event::Command(cmd, code) if cmd.is_activate() => {
127 self.open_menu(cx, data, true);
128 cx.depress_with_key(self.id(), code);
129 Used
130 }
131 Event::Command(cmd, _) => self.handle_dir_key(cx, data, cmd),
132 _ => Unused,
133 }
134 }
135
136 fn handle_messages(&mut self, cx: &mut EventCx, data: &Data) {
137 if let Some(kas::messages::Activate(code)) = cx.try_pop() {
138 self.popup.open(cx, data, self.id());
139 cx.depress_with_key(self.id(), code);
140 } else {
141 self.popup.close(cx);
142 }
143 }
144 }
145
146 impl Menu for Self {
147 fn sub_items(&mut self) -> Option<SubItems> {
148 Some(SubItems {
149 label: Some(&mut self.label),
150 submenu: Some(&mut self.mark),
151 ..Default::default()
152 })
153 }
154
155 fn menu_is_open(&self) -> bool {
156 self.popup.is_open()
157 }
158
159 fn set_menu_path(
160 &mut self,
161 cx: &mut EventCx,
162 data: &Data,
163 target: Option<&Id>,
164 set_focus: bool,
165 ) {
166 if !self.id_ref().is_valid() {
167 return;
168 }
169
170 match target {
171 Some(id) if self.is_ancestor_of(id) => {
172 self.open_menu(cx, data, set_focus);
173 }
174 _ => self.popup.close(cx),
175 }
176
177 for i in 0..self.popup.len() {
178 self.popup[i].set_menu_path(cx, data, target, set_focus);
179 }
180 }
181 }
182
183 impl HasStr for Self {
184 fn get_str(&self) -> &str {
185 self.label.get_str()
186 }
187 }
188}
189
190const MENU_VIEW_COLS: u32 = 5;
191const fn menu_view_row_info(row: u32) -> layout::GridCellInfo {
192 layout::GridCellInfo {
193 col: 0,
194 col_end: MENU_VIEW_COLS,
195 row,
196 row_end: row + 1,
197 }
198}
199
200impl_scope! {
201 #[widget]
203 struct MenuView<W: Menu> {
204 core: widget_core!(),
205 dim: layout::GridDimensions,
206 store: layout::DynGridStorage, list: Vec<W>,
208 }
209
210 impl kas::Widget for Self {
211 type Data = W::Data;
212
213 fn for_child_node(
214 &mut self,
215 data: &W::Data,
216 index: usize,
217 closure: Box<dyn FnOnce(Node<'_>) + '_>,
218 ) {
219 if let Some(w) = self.list.get_mut(index) {
220 closure(w.as_node(data));
221 }
222 }
223 }
224
225 impl kas::Layout for Self {
226 #[inline]
227 fn num_children(&self) -> usize {
228 self.list.len()
229 }
230 fn get_child(&self, index: usize) -> Option<&dyn Layout> {
231 self.list.get(index).map(|w| w.as_layout())
232 }
233
234 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
235 self.dim = layout::GridDimensions {
236 cols: MENU_VIEW_COLS,
237 col_spans: self
238 .list
239 .iter_mut()
240 .filter_map(|w| w.sub_items().is_none().then_some(()))
241 .count()
242 .cast(),
243 rows: self.list.len().cast(),
244 row_spans: 0,
245 };
246
247 let store = &mut self.store;
248 let mut solver = layout::GridSolver::<Vec<_>, Vec<_>, _>::new(axis, self.dim, store);
249
250 let frame_rules = sizer.frame(FrameStyle::MenuEntry, axis);
251
252 let child_rules = SizeRules::EMPTY;
254 let (_, _, frame_size_flipped) = sizer
255 .frame(FrameStyle::MenuEntry, axis.flipped())
256 .surround(child_rules);
257
258 let child_rules = |sizer: SizeCx, w: &mut dyn Layout, mut axis: AxisInfo| {
259 axis.sub_other(frame_size_flipped);
260 let rules = w.size_rules(sizer, axis);
261 frame_rules.surround(rules).0
262 };
263
264 for (row, child) in self.list.iter_mut().enumerate() {
265 let row = u32::conv(row);
266 let info = menu_view_row_info(row);
267
268 let rules = child.size_rules(sizer.re(), axis);
271
272 if let Some(items) = child.sub_items() {
275 if let Some(w) = items.toggle {
276 let info = layout::GridCellInfo::new(0, row);
277 solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis));
278 }
279 if let Some(w) = items.icon {
280 let info = layout::GridCellInfo::new(1, row);
281 solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis));
282 }
283 if let Some(w) = items.label {
284 let info = layout::GridCellInfo::new(2, row);
285 solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis));
286 }
287 if let Some(w) = items.label2 {
288 let info = layout::GridCellInfo::new(3, row);
289 solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis));
290 }
291 if let Some(w) = items.submenu {
292 let info = layout::GridCellInfo::new(4, row);
293 solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis));
294 }
295 } else {
296 solver.for_child(store, info, |_| rules);
297 }
298 }
299 solver.finish(store)
300 }
301
302 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, _: AlignHints) {
303 self.core.rect = rect;
304 let store = &mut self.store;
305 let hints = AlignHints::NONE;
306 let mut setter = layout::GridSetter::<Vec<_>, Vec<_>, _>::new(rect, self.dim, store);
307
308 let child_rules = SizeRules::EMPTY;
310 let (_, frame_x, frame_w) = cx
311 .size_cx()
312 .frame(FrameStyle::MenuEntry, Direction::Right)
313 .surround(child_rules);
314 let (_, frame_y, frame_h) = cx
315 .size_cx()
316 .frame(FrameStyle::MenuEntry, Direction::Down)
317 .surround(child_rules);
318 let frame_offset = Offset(frame_x, frame_y);
319 let frame_size = Size(frame_w, frame_h);
320 let subtract_frame = |mut rect: Rect| {
321 rect.pos += frame_offset;
322 rect.size -= frame_size;
323 rect
324 };
325
326 for (row, child) in self.list.iter_mut().enumerate() {
327 let row = u32::conv(row);
328 let child_rect = setter.child_rect(store, menu_view_row_info(row));
329 child.set_rect(cx, child_rect, hints);
331
332 if let Some(items) = child.sub_items() {
333 if let Some(w) = items.toggle {
334 let info = layout::GridCellInfo::new(0, row);
335 w.set_rect(cx, subtract_frame(setter.child_rect(store, info)), hints);
336 }
337 if let Some(w) = items.icon {
338 let info = layout::GridCellInfo::new(1, row);
339 w.set_rect(cx, subtract_frame(setter.child_rect(store, info)), hints);
340 }
341 if let Some(w) = items.label {
342 let info = layout::GridCellInfo::new(2, row);
343 w.set_rect(cx, subtract_frame(setter.child_rect(store, info)), hints);
344 }
345 if let Some(w) = items.label2 {
346 let info = layout::GridCellInfo::new(3, row);
347 w.set_rect(cx, subtract_frame(setter.child_rect(store, info)), hints);
348 }
349 if let Some(w) = items.submenu {
350 let info = layout::GridCellInfo::new(4, row);
351 w.set_rect(cx, subtract_frame(setter.child_rect(store, info)), hints);
352 }
353 }
354 }
355 }
356
357 fn find_id(&mut self, coord: Coord) -> Option<Id> {
358 if !self.rect().contains(coord) {
359 return None;
360 }
361
362 for child in self.list.iter_mut() {
363 if let Some(id) = child.find_id(coord) {
364 return Some(id);
365 }
366 }
367 Some(self.id())
368 }
369
370 fn draw(&mut self, mut draw: DrawCx) {
371 for child in self.list.iter_mut() {
372 draw.recurse(child);
373 }
374 }
375 }
376
377 impl Self {
378 pub fn new(list: Vec<W>) -> Self {
380 MenuView {
381 core: Default::default(),
382 dim: Default::default(),
383 store: Default::default(),
384 list,
385 }
386 }
387
388 pub fn len(&self) -> usize {
390 self.list.len()
391 }
392 }
393
394 impl std::ops::Index<usize> for Self {
395 type Output = W;
396
397 fn index(&self, index: usize) -> &Self::Output {
398 &self.list[index]
399 }
400 }
401
402 impl std::ops::IndexMut<usize> for Self {
403 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
404 &mut self.list[index]
405 }
406 }
407}