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::Label),
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
136 impl Events for Self {
137 const REDRAW_ON_MOUSE_OVER: bool = true;
138
139 type Data = Data;
140
141 fn probe(&self, _: Coord) -> Id {
142 self.id()
143 }
144
145 fn handle_event(&mut self, cx: &mut EventCx, data: &Data, event: Event) -> IsUsed {
146 match event {
147 Event::Command(cmd, code) if cmd.is_activate() => {
148 self.open_menu(cx, data, true);
149 cx.depress_with_key(&self, code);
150 Used
151 }
152 Event::Command(cmd, _) => self.handle_dir_key(cx, data, cmd),
153 _ => Unused,
154 }
155 }
156
157 fn handle_messages(&mut self, cx: &mut EventCx, data: &Data) {
158 if let Some(Activate(code)) = cx.try_pop() {
159 self.popup.open(cx, data, self.id(), true);
160 cx.depress_with_key(&self, code);
161 } else if let Some(Expand) = cx.try_pop() {
162 self.popup.open(cx, data, self.id(), true);
163 } else if let Some(Collapse) = cx.try_pop() {
164 self.popup.close(cx);
165 } else {
166 self.popup.close(cx);
167 }
168 }
169 }
170
171 impl Menu for Self {
172 fn sub_items(&mut self) -> Option<SubItems<'_>> {
173 Some(SubItems {
174 label: Some(&mut self.label),
175 submenu: Some(&mut self.mark),
176 ..Default::default()
177 })
178 }
179
180 fn menu_is_open(&self) -> bool {
181 self.popup.is_open()
182 }
183
184 fn set_menu_path(
185 &mut self,
186 cx: &mut EventCx,
187 data: &Data,
188 target: Option<&Id>,
189 set_focus: bool,
190 ) {
191 if !self.id_ref().is_valid() {
192 return;
193 }
194
195 match target {
196 Some(id) if self.is_ancestor_of(id) => {
197 self.open_menu(cx, data, set_focus);
198 }
199 _ => self.popup.close(cx),
200 }
201
202 for i in 0..self.popup.inner.len() {
203 self.popup.inner[i].set_menu_path(cx, data, target, set_focus);
204 }
205 }
206 }
207}
208
209const MENU_VIEW_COLS: u32 = 5;
210const fn menu_view_row_info(row: u32) -> layout::GridCellInfo {
211 layout::GridCellInfo {
212 col: 0,
213 last_col: MENU_VIEW_COLS - 1,
214 row,
215 last_row: row,
216 }
217}
218
219#[impl_self]
220mod MenuView {
221 #[widget]
223 struct MenuView<W: Menu> {
224 core: widget_core!(),
225 dim: layout::GridDimensions,
226 store: layout::DynGridStorage, list: Vec<W>,
228 }
229
230 impl Layout for Self {
231 fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
232 self.dim = layout::GridDimensions {
233 cols: MENU_VIEW_COLS,
234 col_spans: self
235 .list
236 .iter_mut()
237 .filter_map(|w| w.sub_items().is_none().then_some(()))
238 .count()
239 .cast(),
240 rows: self.list.len().cast(),
241 row_spans: 0,
242 };
243
244 let store = &mut self.store;
245 let mut solver = layout::GridSolver::<Vec<_>, Vec<_>, _>::new(axis, self.dim, store);
246
247 let frame_rules = cx.frame(FrameStyle::MenuEntry, axis);
248
249 let child_rules = SizeRules::EMPTY;
251 let (_, _, frame_size_flipped) = cx
252 .frame(FrameStyle::MenuEntry, axis.flipped())
253 .surround(child_rules);
254
255 let child_rules = |cx: &mut SizeCx, w: &mut dyn Tile, mut axis: AxisInfo| {
256 axis.map_other(|x| x - frame_size_flipped);
257 let rules = w.size_rules(cx, axis);
258 frame_rules.surround(rules).0
259 };
260
261 for (row, child) in self.list.iter_mut().enumerate() {
262 let row = u32::conv(row);
263 let info = menu_view_row_info(row);
264
265 let rules = child.size_rules(cx, axis);
268
269 if let Some(items) = child.sub_items() {
272 if let Some(w) = items.toggle {
273 let info = layout::GridCellInfo::new(0, row);
274 solver.for_child(store, info, |axis| child_rules(cx, w, axis));
275 }
276 if let Some(w) = items.icon {
277 let info = layout::GridCellInfo::new(1, row);
278 solver.for_child(store, info, |axis| child_rules(cx, w, axis));
279 }
280 if let Some(w) = items.label {
281 let info = layout::GridCellInfo::new(2, row);
282 solver.for_child(store, info, |axis| child_rules(cx, w, axis));
283 }
284 if let Some(w) = items.label2 {
285 let info = layout::GridCellInfo::new(3, row);
286 solver.for_child(store, info, |axis| child_rules(cx, w, axis));
287 }
288 if let Some(w) = items.submenu {
289 let info = layout::GridCellInfo::new(4, row);
290 solver.for_child(store, info, |axis| child_rules(cx, w, axis));
291 }
292 } else {
293 solver.for_child(store, info, |_| rules);
294 }
295 }
296 solver.finish(store)
297 }
298
299 fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, _: AlignHints) {
300 self.core.set_rect(rect);
301 let store = &mut self.store;
302 let hints = AlignHints::NONE;
303 let mut setter = layout::GridSetter::<Vec<_>, Vec<_>, _>::new(rect, self.dim, store);
304
305 let child_rules = SizeRules::EMPTY;
307 let (_, frame_x, frame_w) = cx
308 .frame(FrameStyle::MenuEntry, Direction::Right)
309 .surround(child_rules);
310 let (_, frame_y, frame_h) = 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 ChildIndices::range(0..self.list.len())
367 }
368 fn get_child(&self, index: usize) -> Option<&dyn Tile> {
369 self.list.get(index).map(|w| w.as_tile())
370 }
371 }
372
373 impl Events for Self {
374 fn probe(&self, coord: Coord) -> Id {
375 for child in self.list.iter() {
376 if let Some(id) = child.try_probe(coord) {
377 return id;
378 }
379 }
380 self.id()
381 }
382 }
383
384 impl Widget for Self {
385 type Data = W::Data;
386
387 fn child_node<'n>(&'n mut self, data: &'n W::Data, index: usize) -> Option<Node<'n>> {
388 self.list.get_mut(index).map(|w| w.as_node(data))
389 }
390 }
391
392 impl Self {
393 pub fn new(list: Vec<W>) -> Self {
395 MenuView {
396 core: Default::default(),
397 dim: Default::default(),
398 store: Default::default(),
399 list,
400 }
401 }
402
403 pub fn len(&self) -> usize {
405 self.list.len()
406 }
407 }
408
409 impl std::ops::Index<usize> for Self {
410 type Output = W;
411
412 fn index(&self, index: usize) -> &Self::Output {
413 &self.list[index]
414 }
415 }
416
417 impl std::ops::IndexMut<usize> for Self {
418 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
419 &mut self.list[index]
420 }
421 }
422}