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