Skip to main content

iced_cube/application/
menubar.rs

1//! A horizontal strip of top-level menus, such as File, Edit, View and
2//! Help, along the top of a desktop window.
3//!
4//! Each menu is a [dropdown menu](mod@crate::overlay::dropdown_menu): the same
5//! entries, rows, checkbox and radio items, submenus and shortcut hints.
6//! [`State`] holds the menus, which one is open and which title has the
7//! keyboard focus. Once a menu is open, moving the pointer onto another
8//! title switches to that menu.
9//!
10//! The bar resolves its own keys while a menu is open: Left and Right move
11//! between menus (opening and closing submenus first), and the rest go to
12//! the open menu. While the bar is closed, the app routes key presses from
13//! [`keys::subscription`] through [`State::key_event`]: F10, or Alt pressed
14//! and released on its own, focuses the bar, Left and Right move along it, Down opens the focused menu,
15//! Escape leaves it, and Alt with a menu's mnemonic letter opens that menu.
16//!
17//! ```no_run
18//! use iced::{Element, Subscription};
19//! use iced_cube::dropdown_menu::{item, separator};
20//! use iced_cube::keys;
21//! use iced_cube::menubar::{self, Output, menu, menubar};
22//!
23//! #[derive(Debug, Clone)]
24//! enum Message {
25//!     Menubar(menubar::Event<&'static str>),
26//!     Key(keys::Event),
27//! }
28//!
29//! struct App {
30//!     menubar: menubar::State<&'static str>,
31//! }
32//!
33//! impl App {
34//!     fn new() -> Self {
35//!         let menubar = menubar::State::new([
36//!             menu("File", [item("new", "New").shortcut("Ctrl+N"), separator(), item("quit", "Quit")]),
37//!             menu("Help", [item("about", "About")]),
38//!         ]);
39//!         Self { menubar }
40//!     }
41//!
42//!     fn update(&mut self, message: Message) {
43//!         let event = match message {
44//!             Message::Menubar(event) => Some(event),
45//!             Message::Key(key) => self.menubar.key_event(
46//!                 &menubar::default_keymap(),
47//!                 &iced_cube::dropdown_menu::default_keymap(),
48//!                 &key,
49//!             ),
50//!         };
51//!         if let Some(Output::Activated(id)) = event.and_then(|event| self.menubar.update(event)) {
52//!             println!("Run {id}");
53//!         }
54//!     }
55//!
56//!     fn view(&self) -> Element<'_, Message> {
57//!         menubar(&self.menubar).on_event(Message::Menubar).into()
58//!     }
59//!
60//!     fn subscription(&self) -> Subscription<Message> {
61//!         keys::subscription().map(Message::Key)
62//!     }
63//! }
64//! ```
65
66use std::rc::Rc;
67
68use iced::keyboard::{Key, Modifiers, key::Named};
69use iced::widget::text::{LineHeight, Span};
70use iced::widget::{self, column, container, mouse_area, rich_text, row, span, text};
71use iced::{Background, Border, Color, Element, Length, Padding, Shadow};
72
73use crate::keys::{self, Chord, Keymap};
74use crate::overlay::anchored::{Align, Placement, Side, anchored};
75use crate::overlay::dropdown_menu::{self, Entry, Kind};
76use crate::overlay::menu::LINE_HEIGHT;
77use crate::theme::{Tokens, radius, space, text_size};
78
79pub use crate::overlay::dropdown_menu::Output;
80
81/// Padding inside a menu title: 4 pixels above and below, 8 at the sides.
82pub const TRIGGER_PADDING: Padding = Padding {
83    top: 4.0,
84    bottom: 4.0,
85    left: space::SM,
86    right: space::SM,
87};
88
89/// One top-level menu: its title and its entries.
90#[derive(Debug, Clone, PartialEq)]
91pub struct Menu<Id> {
92    title: String,
93    mnemonic: Option<char>,
94    state: dropdown_menu::State<Id>,
95}
96
97/// A menu titled `title` holding dropdown menu `entries`. Its mnemonic is
98/// the first letter of the title unless [`Menu::mnemonic`] sets another.
99pub fn menu<Id: Copy + PartialEq>(
100    title: impl Into<String>,
101    entries: impl IntoIterator<Item = Entry<Id>>,
102) -> Menu<Id> {
103    Menu {
104        title: title.into(),
105        mnemonic: None,
106        state: dropdown_menu::State::new(entries),
107    }
108}
109
110impl<Id> Menu<Id> {
111    /// The letter that opens this menu with Alt, or on its own while the
112    /// bar has focus. It is underlined in the title while the bar has focus.
113    pub fn mnemonic(mut self, letter: char) -> Self {
114        self.mnemonic = Some(letter);
115        self
116    }
117
118    pub fn title(&self) -> &str {
119        &self.title
120    }
121
122    /// The menu's entries and whether it is open.
123    pub fn state(&self) -> &dropdown_menu::State<Id> {
124        &self.state
125    }
126
127    /// The mnemonic in lower case: the one set, or the title's first letter.
128    pub fn key(&self) -> Option<char> {
129        self.mnemonic
130            .or_else(|| self.title.chars().find(|c| c.is_alphanumeric()))
131            .and_then(|c| c.to_lowercase().next())
132    }
133}
134
135/// Changes to a menubar.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum Event<Id> {
138    /// A title was pressed: opens its menu, or closes it when it is open.
139    Toggle(usize),
140    /// The pointer moved onto a title. Switches to its menu while another
141    /// menu is open, and does nothing otherwise.
142    Hover(usize),
143    /// An event for the menu at this position.
144    Menu(usize, dropdown_menu::Event<Id>),
145    /// Focuses the first title, or leaves the bar when it has focus.
146    Focus,
147    /// Moves to the next title, wrapping, and opens its menu if one is open.
148    Next,
149    /// Moves to the previous title, wrapping, and opens its menu if one is
150    /// open.
151    Previous,
152    /// Opens the focused title's menu on its first item.
153    Open,
154    /// Opens the menu at this position on its first item, as its mnemonic
155    /// does.
156    OpenMenu(usize),
157    /// Closes the open menu and keeps the focus on its title, or leaves the
158    /// bar when no menu is open.
159    Close,
160    /// Closes everything and leaves the bar, as a press outside does.
161    Dismiss,
162}
163
164/// The menus, which one is open and which title has the keyboard focus.
165#[derive(Debug, Clone, PartialEq)]
166pub struct State<Id> {
167    menus: Vec<Menu<Id>>,
168    focused: Option<usize>,
169    keyboard: bool,
170}
171
172impl<Id: Copy + PartialEq> State<Id> {
173    /// A closed menubar. Item ids must be unique across every menu.
174    pub fn new(menus: impl IntoIterator<Item = Menu<Id>>) -> Self {
175        Self {
176            menus: menus.into_iter().collect(),
177            focused: None,
178            keyboard: false,
179        }
180    }
181
182    /// Whether the bar was last used from the keyboard, which underlines
183    /// the mnemonics while it has focus.
184    pub fn shows_mnemonics(&self) -> bool {
185        self.keyboard && self.is_focused()
186    }
187
188    pub fn menus(&self) -> &[Menu<Id>] {
189        &self.menus
190    }
191
192    /// The position of the open menu.
193    pub fn open(&self) -> Option<usize> {
194        self.menus.iter().position(|menu| menu.state.is_open())
195    }
196
197    pub fn is_open(&self) -> bool {
198        self.open().is_some()
199    }
200
201    /// The title with the keyboard focus. The open menu's title keeps it.
202    pub fn focused(&self) -> Option<usize> {
203        self.focused
204    }
205
206    /// Whether the bar takes the keys that move along it.
207    pub fn is_focused(&self) -> bool {
208        self.focused.is_some()
209    }
210
211    /// The highlighted item in the open menu.
212    pub fn highlighted(&self) -> Option<Id> {
213        self.menus.iter().find_map(|menu| menu.state.highlighted())
214    }
215
216    /// Whether a checkbox or radio item in any menu is checked.
217    pub fn is_checked(&self, id: Id) -> bool {
218        self.menus.iter().any(|menu| menu.state.is_checked(id))
219    }
220
221    /// Checks or unchecks an item, in whichever menu holds it.
222    pub fn set_checked(&mut self, id: Id, checked: bool) {
223        for menu in &mut self.menus {
224            if menu.state.item(id).is_some() {
225                menu.state.set_checked(id, checked);
226            }
227        }
228    }
229
230    /// Enables or disables an item, in whichever menu holds it.
231    pub fn set_disabled(&mut self, id: Id, disabled: bool) {
232        for menu in &mut self.menus {
233            if menu.state.item(id).is_some() {
234                menu.state.set_disabled(id, disabled);
235            }
236        }
237    }
238
239    /// Applies an event and returns what was chosen, if anything. Choosing
240    /// an item closes the menu and leaves the bar.
241    pub fn update(&mut self, event: Event<Id>) -> Option<Output<Id>> {
242        match event {
243            Event::Toggle(_) | Event::Hover(_) => self.keyboard = false,
244            Event::Focus | Event::Next | Event::Previous | Event::Open | Event::OpenMenu(_) => {
245                self.keyboard = true;
246            }
247            Event::Menu(..) | Event::Close | Event::Dismiss => {}
248        }
249        match event {
250            Event::Toggle(index) if self.open() == Some(index) => self.dismiss(),
251            Event::Toggle(index) => self.switch(index, dropdown_menu::Event::Open),
252            Event::Hover(index) => {
253                if self.open().is_some_and(|open| open != index) {
254                    self.switch(index, dropdown_menu::Event::Open);
255                }
256            }
257            Event::Menu(_, dropdown_menu::Event::Close) => self.close(),
258            Event::Menu(index, event) => return self.forward(index, event),
259            Event::Focus if self.is_open() || self.is_focused() => self.dismiss(),
260            Event::Focus => self.focused = (!self.menus.is_empty()).then_some(0),
261            Event::Next => self.step(true),
262            Event::Previous => self.step(false),
263            Event::Open => {
264                if let Some(index) = self.focused {
265                    self.switch(index, dropdown_menu::Event::First);
266                }
267            }
268            Event::OpenMenu(index) => self.switch(index, dropdown_menu::Event::First),
269            Event::Close => self.close(),
270            Event::Dismiss => self.dismiss(),
271        }
272        None
273    }
274
275    fn forward(&mut self, index: usize, event: dropdown_menu::Event<Id>) -> Option<Output<Id>> {
276        let menu = self.menus.get_mut(index)?;
277        let output = menu.state.update(event);
278        if output.is_some() {
279            self.dismiss();
280            return output;
281        }
282        if menu.state.is_open() {
283            self.close_others(index);
284            self.focused = Some(index);
285        }
286        None
287    }
288
289    /// Opens the menu at `index` with `event`, closing any other.
290    fn switch(&mut self, index: usize, event: dropdown_menu::Event<Id>) {
291        let Some(menu) = self.menus.get_mut(index) else {
292            return;
293        };
294        let _ = menu.state.update(dropdown_menu::Event::Close);
295        let _ = menu.state.update(event);
296        self.close_others(index);
297        self.focused = Some(index);
298    }
299
300    fn close_others(&mut self, keep: usize) {
301        for (index, menu) in self.menus.iter_mut().enumerate() {
302            if index != keep {
303                let _ = menu.state.update(dropdown_menu::Event::Close);
304            }
305        }
306    }
307
308    fn close_all(&mut self) {
309        for menu in &mut self.menus {
310            let _ = menu.state.update(dropdown_menu::Event::Close);
311        }
312    }
313
314    fn close(&mut self) {
315        if self.is_open() {
316            self.close_all();
317        } else {
318            self.focused = None;
319        }
320    }
321
322    fn dismiss(&mut self) {
323        self.close_all();
324        self.focused = None;
325    }
326
327    fn step(&mut self, forward: bool) {
328        let len = self.menus.len();
329        let Some(current) = self.open().or(self.focused) else {
330            return;
331        };
332        if len == 0 {
333            return;
334        }
335        let target = if forward {
336            (current + 1) % len
337        } else {
338            (current + len - 1) % len
339        };
340        if self.is_open() {
341            self.switch(target, dropdown_menu::Event::First);
342        } else {
343            self.focused = Some(target);
344        }
345    }
346
347    /// Turns a key press into an event. While a menu is open, the bar's
348    /// keymap goes first (Left and Right open and close submenus before
349    /// they move between menus), then `menu_keymap` and typeahead. While
350    /// closed, the bar claims [`Action::Focus`], its mnemonics with Alt,
351    /// and, only while it has focus, its other actions and bare mnemonics.
352    pub fn key_event(
353        &self,
354        keymap: &Keymap<Action>,
355        menu_keymap: &Keymap<dropdown_menu::Action>,
356        key: &keys::Event,
357    ) -> Option<Event<Id>> {
358        let action = resolve(keymap, key);
359        if let Some(open) = self.open() {
360            if let Some(event) = action.and_then(|action| action.event(self)) {
361                return Some(event);
362            }
363            let menu = self.menus.get(open)?;
364            return menu
365                .state
366                .key_event(menu_keymap, key)
367                .map(|event| Event::Menu(open, event));
368        }
369        if let Some(event) = action.and_then(|action| action.event(self)) {
370            return Some(event);
371        }
372        self.mnemonic(key).map(Event::OpenMenu)
373    }
374
375    /// The menu whose mnemonic a key press types: with Alt at any time, or
376    /// on its own while the bar has focus.
377    fn mnemonic(&self, key: &keys::Event) -> Option<usize> {
378        let alone = key.modifiers.is_empty() || key.modifiers == Modifiers::SHIFT;
379        let wanted = key.modifiers == Modifiers::ALT || (alone && self.is_focused());
380        if !wanted {
381            return None;
382        }
383        let Key::Character(typed) = &key.key else {
384            return None;
385        };
386        let typed = typed.chars().next()?.to_lowercase().next()?;
387        self.menus.iter().position(|menu| menu.key() == Some(typed))
388    }
389
390    /// Whether the deepest highlighted item of the open menu opens a submenu.
391    fn on_submenu(&self) -> bool {
392        let Some(id) = self.highlighted() else {
393            return false;
394        };
395        self.menus.iter().any(|menu| {
396            menu.state
397                .item(id)
398                .is_some_and(|item| !item.disabled && matches!(item.kind, Kind::Submenu(_)))
399        })
400    }
401
402    fn open_depth(&self) -> usize {
403        self.open()
404            .and_then(|open| self.menus.get(open))
405            .map_or(0, |menu| menu.state.depth())
406    }
407}
408
409/// Resolves a key press through a keymap. A bare modifier key, such as
410/// Alt, may arrive with its own modifier held, so that modifier is ignored.
411fn resolve<A: Clone>(keymap: &Keymap<A>, key: &keys::Event) -> Option<A> {
412    let modifiers = match key.key {
413        Key::Named(Named::Alt) => key.modifiers.difference(Modifiers::ALT),
414        Key::Named(Named::Control) => key.modifiers.difference(Modifiers::CTRL),
415        Key::Named(Named::Shift) => key.modifiers.difference(Modifiers::SHIFT),
416        _ => key.modifiers,
417    };
418    keymap.resolve(&key.key, modifiers)
419}
420
421/// What a menubar keyboard shortcut does.
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
423pub enum Action {
424    /// Focuses the bar, or leaves it.
425    Focus,
426    NextMenu,
427    PreviousMenu,
428    /// Opens the focused title's menu.
429    Open,
430    Close,
431}
432
433impl Action {
434    /// The [`Event`] this action sends to `state`, or `None` when it does
435    /// nothing, so the key can go elsewhere: while a menu is open, Open is
436    /// left to the menu and Left and Right work its submenus first; while
437    /// the bar is closed and unfocused, only Focus does anything.
438    pub fn event<Id: Copy + PartialEq>(self, state: &State<Id>) -> Option<Event<Id>> {
439        if let Some(open) = state.open() {
440            return match self {
441                Action::Focus => Some(Event::Dismiss),
442                Action::NextMenu if state.on_submenu() => {
443                    Some(Event::Menu(open, dropdown_menu::Event::OpenSubmenu))
444                }
445                Action::NextMenu => Some(Event::Next),
446                Action::PreviousMenu if state.open_depth() > 1 => {
447                    Some(Event::Menu(open, dropdown_menu::Event::CloseSubmenu))
448                }
449                Action::PreviousMenu => Some(Event::Previous),
450                Action::Close if state.open_depth() > 1 => {
451                    Some(Event::Menu(open, dropdown_menu::Event::CloseSubmenu))
452                }
453                Action::Close => Some(Event::Close),
454                Action::Open => None,
455            };
456        }
457        if !state.is_focused() {
458            return (self == Action::Focus).then_some(Event::Focus);
459        }
460        Some(match self {
461            Action::Focus => Event::Focus,
462            Action::NextMenu => Event::Next,
463            Action::PreviousMenu => Event::Previous,
464            Action::Open => Event::Open,
465            Action::Close => Event::Close,
466        })
467    }
468}
469
470impl keys::Action for Action {
471    const ALL: &'static [Self] = &[
472        Action::Focus,
473        Action::NextMenu,
474        Action::PreviousMenu,
475        Action::Open,
476        Action::Close,
477    ];
478
479    fn defaults() -> Keymap<Self> {
480        default_keymap()
481    }
482
483    fn name(self) -> &'static str {
484        match self {
485            Action::Focus => "Focus",
486            Action::NextMenu => "NextMenu",
487            Action::PreviousMenu => "PreviousMenu",
488            Action::Open => "Open",
489            Action::Close => "Close",
490        }
491    }
492
493    fn description(self) -> &'static str {
494        match self {
495            Action::Focus => "Focuses the first menu title, or leaves the bar and closes its menu.",
496            Action::NextMenu => {
497                "Moves to the next menu, or opens the highlighted submenu inside an open menu."
498            }
499            Action::PreviousMenu => {
500                "Moves to the previous menu, or closes the open submenu inside an open menu."
501            }
502            Action::Open => "Opens the focused title's menu on its first item.",
503            Action::Close => "Closes the open submenu or menu, or leaves the bar.",
504        }
505    }
506}
507
508/// The default menubar shortcuts:
509///
510/// | Keys | Action |
511/// | --- | --- |
512/// | `F10`, `Alt` | [`Action::Focus`] |
513/// | `ArrowRight` | [`Action::NextMenu`] |
514/// | `ArrowLeft` | [`Action::PreviousMenu`] |
515/// | `ArrowDown`, `Enter`, `Space` | [`Action::Open`] |
516/// | `Escape` | [`Action::Close`] |
517///
518/// While a menu is open the bar resolves these itself, before the open
519/// menu's own keymap. While the bar is closed, only Focus claims a key
520/// unless the bar has focus, so the arrows stay free for the rest of the
521/// app. Alt with a menu's mnemonic letter opens that menu at any time.
522/// [`keys::subscription`] delivers a bare `Alt` only when it is released
523/// with nothing pressed in between, so Alt chords never focus the bar.
524pub fn default_keymap() -> Keymap<Action> {
525    Keymap::new()
526        .bind(Chord::named(Named::F10), Action::Focus)
527        .bind(Chord::named(Named::Alt), Action::Focus)
528        .bind(Chord::named(Named::ArrowRight), Action::NextMenu)
529        .bind(Chord::named(Named::ArrowLeft), Action::PreviousMenu)
530        .bind(Chord::named(Named::ArrowDown), Action::Open)
531        .bind(Chord::named(Named::Enter), Action::Open)
532        .bind(Chord::named(Named::Space), Action::Open)
533        .bind(Chord::named(Named::Escape), Action::Close)
534}
535
536/// How a menu title is drawn.
537#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
538pub enum TriggerStatus {
539    #[default]
540    Idle,
541    Hovered,
542    /// Its menu is open, or it has the keyboard focus.
543    Active,
544    Disabled,
545}
546
547impl TriggerStatus {
548    pub const ALL: [TriggerStatus; 4] = [
549        TriggerStatus::Idle,
550        TriggerStatus::Hovered,
551        TriggerStatus::Active,
552        TriggerStatus::Disabled,
553    ];
554}
555
556/// The style of a menu title: transparent at rest, the accent under the
557/// pointer, while open and while it has the keyboard focus.
558pub fn trigger_style(tokens: &Tokens, status: TriggerStatus) -> widget::button::Style {
559    let (background, text_color) = match status {
560        TriggerStatus::Idle => (None, tokens.foreground),
561        TriggerStatus::Hovered | TriggerStatus::Active => {
562            (Some(tokens.accent), tokens.accent_foreground)
563        }
564        TriggerStatus::Disabled => (None, tokens.muted_foreground),
565    };
566    widget::button::Style {
567        background: background.map(Background::Color),
568        text_color,
569        border: Border {
570            color: Color::TRANSPARENT,
571            width: 0.0,
572            radius: radius::SM.into(),
573        },
574        shadow: Shadow::default(),
575        snap: true,
576    }
577}
578
579/// The style of the bar behind the titles.
580pub fn bar_style(tokens: &Tokens) -> container::Style {
581    container::Style {
582        background: Some(Background::Color(tokens.background)),
583        text_color: Some(tokens.foreground),
584        ..container::Style::default()
585    }
586}
587
588fn trigger_status(status: widget::button::Status, active: bool) -> TriggerStatus {
589    match status {
590        widget::button::Status::Disabled => TriggerStatus::Disabled,
591        _ if active => TriggerStatus::Active,
592        widget::button::Status::Hovered | widget::button::Status::Pressed => TriggerStatus::Hovered,
593        widget::button::Status::Active => TriggerStatus::Idle,
594    }
595}
596
597type OnEvent<'a, Id, Message> = Rc<dyn Fn(Event<Id>) -> Message + 'a>;
598
599/// A menubar builder. Convert it into an [`Element`] to render.
600pub struct Menubar<'a, Id, Message> {
601    state: &'a State<Id>,
602    width: f32,
603    keymap: Keymap<Action>,
604    menu_keymap: Keymap<dropdown_menu::Action>,
605    on_event: Option<Box<dyn Fn(Event<Id>) -> Message + 'a>>,
606}
607
608impl<Id: std::fmt::Debug, Message> std::fmt::Debug for Menubar<'_, Id, Message> {
609    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610        f.debug_struct("Menubar")
611            .field("state", self.state)
612            .field("width", &self.width)
613            .field("keymap", &self.keymap)
614            .field("menu_keymap", &self.menu_keymap)
615            .finish_non_exhaustive()
616    }
617}
618
619/// Renders the titles of `state`, with the open menu below its title.
620/// Without [`on_event`](Menubar::on_event) every title renders disabled.
621pub fn menubar<'a, Id, Message>(state: &'a State<Id>) -> Menubar<'a, Id, Message> {
622    Menubar {
623        state,
624        width: dropdown_menu::WIDTH,
625        keymap: default_keymap(),
626        menu_keymap: dropdown_menu::default_keymap(),
627        on_event: None,
628    }
629}
630
631impl<'a, Id, Message> Menubar<'a, Id, Message> {
632    /// Width of each menu and its submenus.
633    pub fn width(mut self, width: f32) -> Self {
634        self.width = width;
635        self
636    }
637
638    /// Replaces the [`default_keymap`] the bar resolves while a menu is open.
639    pub fn keymap(mut self, keymap: Keymap<Action>) -> Self {
640        self.keymap = keymap;
641        self
642    }
643
644    /// Replaces the [dropdown menu keys](dropdown_menu::default_keymap) the
645    /// open menu resolves after the bar's own.
646    pub fn menu_keymap(mut self, keymap: Keymap<dropdown_menu::Action>) -> Self {
647        self.menu_keymap = keymap;
648        self
649    }
650
651    pub fn on_event(mut self, on_event: impl Fn(Event<Id>) -> Message + 'a) -> Self {
652        self.on_event = Some(Box::new(on_event));
653        self
654    }
655}
656
657impl<'a, Id, Message> From<Menubar<'a, Id, Message>> for Element<'a, Message>
658where
659    Id: Copy + PartialEq + 'a,
660    Message: Clone + 'a,
661{
662    fn from(menubar: Menubar<'a, Id, Message>) -> Self {
663        let Menubar {
664            state,
665            width,
666            keymap,
667            menu_keymap,
668            on_event,
669        } = menubar;
670        let on_event: Option<OnEvent<'a, Id, Message>> = on_event.map(Rc::from);
671        let keymaps = Rc::new((keymap, menu_keymap));
672
673        let titles = state.menus.iter().enumerate().map(|(index, menu)| {
674            title(state, index, menu, on_event.clone(), keymaps.clone(), width)
675        });
676
677        column![
678            container(row(titles).spacing(2))
679                .padding([space::XS, space::XS])
680                .width(Length::Fill)
681                .style(|theme| bar_style(&Tokens::of(theme))),
682            crate::application::edge(),
683        ]
684        .width(Length::Fill)
685        .into()
686    }
687}
688
689/// One title with its menu floating below it while open.
690fn title<'a, Id, Message>(
691    state: &'a State<Id>,
692    index: usize,
693    menu: &'a Menu<Id>,
694    on_event: Option<OnEvent<'a, Id, Message>>,
695    keymaps: Rc<(Keymap<Action>, Keymap<dropdown_menu::Action>)>,
696    width: f32,
697) -> Element<'a, Message>
698where
699    Id: Copy + PartialEq + 'a,
700    Message: Clone + 'a,
701{
702    let open = menu.state.is_open();
703    let active = open || (!state.is_open() && state.focused == Some(index));
704    let label = title_label(menu, state.shows_mnemonics());
705    let trigger = widget::button(label)
706        .padding(TRIGGER_PADDING)
707        .style(move |theme, status| {
708            trigger_style(&Tokens::of(theme), trigger_status(status, active))
709        })
710        .on_press_maybe(
711            on_event
712                .as_ref()
713                .map(|on_event| on_event(Event::Toggle(index))),
714        );
715
716    let mut anchor = mouse_area(trigger);
717    if let Some(on_event) = on_event.as_ref().filter(|_| state.is_open() && !open) {
718        anchor = anchor.on_enter(on_event(Event::Hover(index)));
719    }
720
721    let on_menu = on_event
722        .clone()
723        .map(|on_event| move |event| on_event(Event::Menu(index, event)));
724    let panel = open.then(|| {
725        dropdown_menu::panel(
726            &menu.state,
727            on_menu
728                .as_ref()
729                .map(|f| f as &dyn Fn(dropdown_menu::Event<Id>) -> Message),
730            width,
731        )
732    });
733    let floating = anchored(anchor)
734        .content(panel)
735        .placement(Placement::new(Side::Bottom, Align::Start))
736        .dismiss_keys([]);
737    let Some(on_event) = on_event.filter(|_| open) else {
738        return floating.into();
739    };
740    floating
741        .on_dismiss(on_event(Event::Dismiss))
742        .on_key(move |key| {
743            let (keymap, menu_keymap) = &*keymaps;
744            state.key_event(keymap, menu_keymap, key).map(&*on_event)
745        })
746        .into()
747}
748
749/// The title text, with its mnemonic underlined while the bar has focus.
750fn title_label<'a, Message: 'a, Id>(menu: &Menu<Id>, underline: bool) -> Element<'a, Message> {
751    let plain = || {
752        text(menu.title.clone())
753            .size(text_size::SM)
754            .line_height(LineHeight::Absolute(LINE_HEIGHT.into()))
755            .wrapping(text::Wrapping::None)
756            .into()
757    };
758    if !underline {
759        return plain();
760    }
761    let Some((before, letter, after)) = split_mnemonic(&menu.title, menu.key()) else {
762        return plain();
763    };
764    let spans: Vec<Span<'a, ()>> = vec![span(before), span(letter).underline(true), span(after)];
765    rich_text(spans)
766        .size(text_size::SM)
767        .line_height(LineHeight::Absolute(LINE_HEIGHT.into()))
768        .wrapping(text::Wrapping::None)
769        .into()
770}
771
772/// Splits a title around the first letter matching its mnemonic.
773fn split_mnemonic(title: &str, key: Option<char>) -> Option<(String, String, String)> {
774    let key = key?;
775    let (start, letter) = title
776        .char_indices()
777        .find(|(_, c)| c.to_lowercase().next() == Some(key))?;
778    let end = start + letter.len_utf8();
779    Some((
780        title.get(..start)?.to_owned(),
781        title.get(start..end)?.to_owned(),
782        title.get(end..)?.to_owned(),
783    ))
784}
785
786#[cfg(test)]
787mod tests {
788    use super::*;
789    use crate::overlay::dropdown_menu::{checkbox_item, item, separator, submenu};
790    use crate::theme::{dark, light};
791
792    const NEW: u8 = 1;
793    const QUIT: u8 = 2;
794    const UNDO: u8 = 3;
795    const RECENT: u8 = 4;
796    const LAST: u8 = 5;
797    const GRID: u8 = 6;
798    const ABOUT: u8 = 7;
799
800    fn state() -> State<u8> {
801        State::new([
802            menu(
803                "File",
804                [
805                    item(NEW, "New").shortcut("Ctrl+N"),
806                    submenu(RECENT, "Open recent", [item(LAST, "notes.md")]),
807                    separator(),
808                    item(QUIT, "Quit"),
809                ],
810            ),
811            menu("Edit", [item(UNDO, "Undo")]),
812            menu("View", [checkbox_item(GRID, "Show grid", true)]),
813            menu("Help", [item(ABOUT, "About")]).mnemonic('p'),
814        ])
815    }
816
817    fn key(key: Key, modifiers: Modifiers) -> keys::Event {
818        keys::Event { key, modifiers }
819    }
820
821    fn named(named: Named) -> keys::Event {
822        key(Key::Named(named), Modifiers::empty())
823    }
824
825    fn resolve_key(state: &State<u8>, event: &keys::Event) -> Option<Event<u8>> {
826        state.key_event(&default_keymap(), &dropdown_menu::default_keymap(), event)
827    }
828
829    #[test]
830    fn starts_closed_and_unfocused() {
831        let state = state();
832        assert_eq!(state.open(), None);
833        assert!(!state.is_open() && !state.is_focused());
834        assert_eq!(state.menus().len(), 4);
835        assert_eq!(state.menus()[0].title(), "File");
836    }
837
838    #[test]
839    fn mnemonics_default_to_the_first_letter() {
840        let state = state();
841        let keys: Vec<_> = state.menus().iter().map(Menu::key).collect();
842        assert_eq!(keys, [Some('f'), Some('e'), Some('v'), Some('p')]);
843        let empty: Menu<u8> = menu("", []);
844        assert_eq!(empty.key(), None);
845    }
846
847    #[test]
848    fn toggle_opens_and_closes_a_menu() {
849        let mut state = state();
850        let _ = state.update(Event::Toggle(1));
851        assert_eq!(state.open(), Some(1));
852        assert_eq!(state.focused(), Some(1));
853        assert_eq!(
854            state.highlighted(),
855            None,
856            "the pointer opens without a highlight"
857        );
858        let _ = state.update(Event::Toggle(1));
859        assert!(!state.is_open() && !state.is_focused());
860    }
861
862    #[test]
863    fn toggling_another_title_switches_menus() {
864        let mut state = state();
865        let _ = state.update(Event::Toggle(0));
866        let _ = state.update(Event::Toggle(2));
867        assert_eq!(state.open(), Some(2));
868        assert!(!state.menus()[0].state().is_open());
869    }
870
871    #[test]
872    fn hovering_switches_only_while_a_menu_is_open() {
873        let mut state = state();
874        let _ = state.update(Event::Hover(1));
875        assert!(!state.is_open(), "hover alone opens nothing");
876
877        let _ = state.update(Event::Toggle(0));
878        let _ = state.update(Event::Hover(1));
879        assert_eq!(state.open(), Some(1));
880        let _ = state.update(Event::Hover(1));
881        assert_eq!(state.open(), Some(1));
882        let _ = state.update(Event::Hover(9));
883        assert_eq!(state.open(), Some(1), "unknown titles are ignored");
884    }
885
886    #[test]
887    fn choosing_an_item_returns_it_and_leaves_the_bar() {
888        let mut state = state();
889        let _ = state.update(Event::Toggle(0));
890        assert_eq!(
891            state.update(Event::Menu(0, dropdown_menu::Event::Activate(NEW))),
892            Some(Output::Activated(NEW))
893        );
894        assert!(!state.is_open() && !state.is_focused());
895
896        let _ = state.update(Event::Toggle(2));
897        assert_eq!(
898            state.update(Event::Menu(2, dropdown_menu::Event::Activate(GRID))),
899            Some(Output::Toggled(GRID, false))
900        );
901        assert!(!state.is_checked(GRID));
902    }
903
904    #[test]
905    fn menu_events_for_unknown_menus_do_nothing() {
906        let mut state = state();
907        assert_eq!(
908            state.update(Event::Menu(9, dropdown_menu::Event::Open)),
909            None
910        );
911        assert!(!state.is_open());
912    }
913
914    #[test]
915    fn a_menu_opened_through_its_own_events_closes_the_others() {
916        let mut state = state();
917        let _ = state.update(Event::Toggle(0));
918        let _ = state.update(Event::Menu(1, dropdown_menu::Event::Next));
919        assert_eq!(state.open(), Some(1));
920        assert_eq!(state.focused(), Some(1));
921        assert!(!state.menus()[0].state().is_open());
922    }
923
924    #[test]
925    fn focus_toggles_the_bar() {
926        let mut state = state();
927        let _ = state.update(Event::Focus);
928        assert_eq!(state.focused(), Some(0));
929        assert!(!state.is_open());
930        let _ = state.update(Event::Focus);
931        assert!(!state.is_focused());
932
933        let mut empty: State<u8> = State::new([]);
934        let _ = empty.update(Event::Focus);
935        assert!(!empty.is_focused());
936        let _ = empty.update(Event::Next);
937        assert!(!empty.is_focused());
938    }
939
940    #[test]
941    fn mnemonics_show_only_while_the_keyboard_drives_the_bar() {
942        let mut state = state();
943        assert!(!state.shows_mnemonics());
944        let _ = state.update(Event::Focus);
945        assert!(state.shows_mnemonics());
946        let _ = state.update(Event::Open);
947        assert!(state.shows_mnemonics());
948        let _ = state.update(Event::Hover(2));
949        assert!(!state.shows_mnemonics(), "the pointer takes over");
950        let _ = state.update(Event::Dismiss);
951        let _ = state.update(Event::Toggle(0));
952        assert!(!state.shows_mnemonics());
953    }
954
955    #[test]
956    fn next_and_previous_move_the_focus_and_wrap() {
957        let mut state = state();
958        let _ = state.update(Event::Next);
959        assert!(!state.is_focused(), "an unfocused bar does not move");
960        let _ = state.update(Event::Focus);
961        let _ = state.update(Event::Previous);
962        assert_eq!(state.focused(), Some(3));
963        let _ = state.update(Event::Next);
964        assert_eq!(state.focused(), Some(0));
965        assert!(!state.is_open());
966    }
967
968    #[test]
969    fn next_with_a_menu_open_opens_the_next_menu_on_its_first_item() {
970        let mut state = state();
971        let _ = state.update(Event::Toggle(0));
972        let _ = state.update(Event::Next);
973        assert_eq!(state.open(), Some(1));
974        assert_eq!(state.highlighted(), Some(UNDO));
975        let _ = state.update(Event::Previous);
976        let _ = state.update(Event::Previous);
977        assert_eq!(state.open(), Some(3));
978        assert_eq!(state.highlighted(), Some(ABOUT));
979    }
980
981    #[test]
982    fn open_opens_the_focused_menu_on_its_first_item() {
983        let mut state = state();
984        let _ = state.update(Event::Open);
985        assert!(!state.is_open(), "nothing is focused");
986        let _ = state.update(Event::Focus);
987        let _ = state.update(Event::Next);
988        let _ = state.update(Event::Open);
989        assert_eq!(state.open(), Some(1));
990        assert_eq!(state.highlighted(), Some(UNDO));
991    }
992
993    #[test]
994    fn open_menu_opens_by_position() {
995        let mut state = state();
996        let _ = state.update(Event::OpenMenu(2));
997        assert_eq!(state.open(), Some(2));
998        assert_eq!(state.highlighted(), Some(GRID));
999        let _ = state.update(Event::OpenMenu(7));
1000        assert_eq!(state.open(), Some(2));
1001    }
1002
1003    #[test]
1004    fn close_returns_to_the_title_then_leaves() {
1005        let mut state = state();
1006        let _ = state.update(Event::OpenMenu(1));
1007        let _ = state.update(Event::Close);
1008        assert!(!state.is_open());
1009        assert_eq!(state.focused(), Some(1));
1010        let _ = state.update(Event::Close);
1011        assert!(!state.is_focused());
1012
1013        let _ = state.update(Event::OpenMenu(1));
1014        let _ = state.update(Event::Menu(1, dropdown_menu::Event::Close));
1015        assert_eq!(state.focused(), Some(1), "a menu's own Close acts the same");
1016    }
1017
1018    #[test]
1019    fn dismiss_closes_everything() {
1020        let mut state = state();
1021        let _ = state.update(Event::OpenMenu(1));
1022        let _ = state.update(Event::Dismiss);
1023        assert!(!state.is_open() && !state.is_focused());
1024    }
1025
1026    #[test]
1027    fn checked_and_disabled_items_are_found_in_any_menu() {
1028        let mut state = state();
1029        assert!(state.is_checked(GRID));
1030        state.set_checked(GRID, false);
1031        assert!(!state.is_checked(GRID));
1032        state.set_disabled(UNDO, true);
1033        assert!(
1034            state.menus()[1]
1035                .state()
1036                .item(UNDO)
1037                .is_some_and(|item| item.disabled)
1038        );
1039        let _ = state.update(Event::OpenMenu(1));
1040        assert_eq!(state.highlighted(), None);
1041    }
1042
1043    #[test]
1044    fn a_closed_unfocused_bar_claims_only_focus_and_alt_mnemonics() {
1045        let state = state();
1046        for named in [
1047            Named::ArrowDown,
1048            Named::ArrowLeft,
1049            Named::ArrowRight,
1050            Named::Escape,
1051        ] {
1052            assert_eq!(resolve_key(&state, &self::named(named)), None, "{named:?}");
1053        }
1054        assert_eq!(
1055            resolve_key(&state, &self::named(Named::F10)),
1056            Some(Event::Focus)
1057        );
1058        assert_eq!(
1059            resolve_key(&state, &key(Key::Named(Named::Alt), Modifiers::ALT)),
1060            Some(Event::Focus),
1061            "Alt arrives with its own modifier held"
1062        );
1063        assert_eq!(
1064            resolve_key(&state, &key(Key::Named(Named::Alt), Modifiers::empty())),
1065            Some(Event::Focus)
1066        );
1067        assert_eq!(
1068            resolve_key(&state, &key(Key::Character("e".into()), Modifiers::ALT)),
1069            Some(Event::OpenMenu(1))
1070        );
1071        assert_eq!(
1072            resolve_key(&state, &key(Key::Character("P".into()), Modifiers::ALT)),
1073            Some(Event::OpenMenu(3))
1074        );
1075        assert_eq!(
1076            resolve_key(&state, &key(Key::Character("e".into()), Modifiers::empty())),
1077            None,
1078            "bare letters need a focused bar"
1079        );
1080        assert_eq!(
1081            resolve_key(&state, &key(Key::Character("z".into()), Modifiers::ALT)),
1082            None,
1083            "letters without a menu stay free"
1084        );
1085    }
1086
1087    #[test]
1088    fn a_focused_bar_moves_opens_and_takes_bare_mnemonics() {
1089        let mut state = state();
1090        let _ = state.update(Event::Focus);
1091        assert_eq!(
1092            resolve_key(&state, &named(Named::ArrowRight)),
1093            Some(Event::Next)
1094        );
1095        assert_eq!(
1096            resolve_key(&state, &named(Named::ArrowLeft)),
1097            Some(Event::Previous)
1098        );
1099        assert_eq!(
1100            resolve_key(&state, &named(Named::ArrowDown)),
1101            Some(Event::Open)
1102        );
1103        assert_eq!(resolve_key(&state, &named(Named::Enter)), Some(Event::Open));
1104        assert_eq!(
1105            resolve_key(&state, &named(Named::Escape)),
1106            Some(Event::Close)
1107        );
1108        assert_eq!(resolve_key(&state, &named(Named::F10)), Some(Event::Focus));
1109        assert_eq!(
1110            resolve_key(&state, &key(Key::Character("v".into()), Modifiers::empty())),
1111            Some(Event::OpenMenu(2))
1112        );
1113        assert_eq!(resolve_key(&state, &named(Named::Tab)), None);
1114    }
1115
1116    #[test]
1117    fn an_open_menu_sends_left_and_right_to_submenus_first() {
1118        let mut state = state();
1119        let _ = state.update(Event::OpenMenu(0));
1120        assert_eq!(state.highlighted(), Some(NEW));
1121        assert_eq!(
1122            resolve_key(&state, &named(Named::ArrowRight)),
1123            Some(Event::Next)
1124        );
1125        assert_eq!(
1126            resolve_key(&state, &named(Named::ArrowLeft)),
1127            Some(Event::Previous)
1128        );
1129
1130        let _ = state.update(Event::Menu(0, dropdown_menu::Event::Next));
1131        assert_eq!(state.highlighted(), Some(RECENT));
1132        let right = resolve_key(&state, &named(Named::ArrowRight));
1133        assert_eq!(
1134            right,
1135            Some(Event::Menu(0, dropdown_menu::Event::OpenSubmenu))
1136        );
1137        let _ = state.update(Event::Menu(0, dropdown_menu::Event::OpenSubmenu));
1138        assert_eq!(state.highlighted(), Some(LAST));
1139        assert_eq!(
1140            resolve_key(&state, &named(Named::ArrowLeft)),
1141            Some(Event::Menu(0, dropdown_menu::Event::CloseSubmenu))
1142        );
1143        assert_eq!(
1144            resolve_key(&state, &named(Named::Escape)),
1145            Some(Event::Menu(0, dropdown_menu::Event::CloseSubmenu))
1146        );
1147    }
1148
1149    #[test]
1150    fn an_open_menu_leaves_the_rest_to_the_menu_keymap() {
1151        let mut state = state();
1152        let _ = state.update(Event::OpenMenu(0));
1153        assert_eq!(
1154            resolve_key(&state, &named(Named::ArrowDown)),
1155            Some(Event::Menu(0, dropdown_menu::Event::Next))
1156        );
1157        assert_eq!(
1158            resolve_key(&state, &named(Named::Enter)),
1159            Some(Event::Menu(0, dropdown_menu::Event::ActivateHighlighted))
1160        );
1161        assert_eq!(
1162            resolve_key(&state, &key(Key::Character("q".into()), Modifiers::empty())),
1163            Some(Event::Menu(0, dropdown_menu::Event::Typeahead('q')))
1164        );
1165        assert_eq!(
1166            resolve_key(&state, &named(Named::Escape)),
1167            Some(Event::Close)
1168        );
1169        assert_eq!(
1170            resolve_key(&state, &named(Named::F10)),
1171            Some(Event::Dismiss)
1172        );
1173    }
1174
1175    fn press(keymap: &Keymap<Action>, chord: &str) -> Option<Action> {
1176        let chord: Chord = chord.parse().unwrap();
1177        keymap.resolve(chord.key(), chord.modifiers())
1178    }
1179
1180    #[test]
1181    fn default_keymap_covers_every_action() {
1182        let keymap = default_keymap();
1183        assert_eq!(press(&keymap, "F10"), Some(Action::Focus));
1184        assert_eq!(press(&keymap, "ArrowRight"), Some(Action::NextMenu));
1185        assert_eq!(press(&keymap, "ArrowLeft"), Some(Action::PreviousMenu));
1186        assert_eq!(press(&keymap, "ArrowDown"), Some(Action::Open));
1187        assert_eq!(press(&keymap, "Enter"), Some(Action::Open));
1188        assert_eq!(press(&keymap, "Space"), Some(Action::Open));
1189        assert_eq!(press(&keymap, "Escape"), Some(Action::Close));
1190        assert_eq!(
1191            keymap.resolve(&Key::Named(Named::Alt), Modifiers::empty()),
1192            Some(Action::Focus)
1193        );
1194        for action in <Action as keys::Action>::ALL {
1195            assert!(!keymap.chords(action).is_empty(), "{action:?}");
1196        }
1197        assert_eq!(Keymap::<Action>::defaults(), keymap);
1198    }
1199
1200    #[test]
1201    fn keymap_overrides_and_unbinding() {
1202        let keymap = default_keymap()
1203            .unbind(&Chord::named(Named::Alt))
1204            .bind("Ctrl+M".parse().unwrap(), Action::Focus);
1205        let state = state();
1206        let alt = key(Key::Named(Named::Alt), Modifiers::ALT);
1207        let menus = dropdown_menu::default_keymap();
1208        assert_eq!(state.key_event(&keymap, &menus, &alt), None);
1209        let ctrl_m = key(Key::Character("m".into()), Modifiers::CTRL);
1210        assert_eq!(
1211            state.key_event(&keymap, &menus, &ctrl_m),
1212            Some(Event::Focus)
1213        );
1214        assert_eq!(
1215            state.key_event(&Keymap::new(), &menus, &named(Named::F10)),
1216            None
1217        );
1218    }
1219
1220    #[test]
1221    fn a_closed_unfocused_bar_maps_only_focus() {
1222        let state = state();
1223        assert_eq!(Action::Focus.event(&state), Some(Event::Focus));
1224        for &action in <Action as keys::Action>::ALL {
1225            if action != Action::Focus {
1226                assert_eq!(action.event(&state), None, "{action:?}");
1227            }
1228        }
1229    }
1230
1231    #[test]
1232    fn an_open_bar_leaves_open_to_the_menu() {
1233        let mut state = state();
1234        let _ = state.update(Event::OpenMenu(1));
1235        assert_eq!(Action::Open.event(&state), None);
1236        assert_eq!(Action::Focus.event(&state), Some(Event::Dismiss));
1237        assert_eq!(Action::Close.event(&state), Some(Event::Close));
1238    }
1239
1240    #[test]
1241    fn every_action_has_a_name_and_a_sentence() {
1242        for &action in <Action as keys::Action>::ALL {
1243            assert!(!keys::Action::name(action).is_empty());
1244            assert!(keys::Action::description(action).ends_with('.'));
1245        }
1246    }
1247
1248    #[test]
1249    fn builder_defaults_and_overrides() {
1250        let state = state();
1251        let bar: Menubar<'_, u8, ()> = menubar(&state);
1252        assert_eq!(bar.width, dropdown_menu::WIDTH);
1253        assert_eq!(bar.keymap, default_keymap());
1254        assert_eq!(bar.menu_keymap, dropdown_menu::default_keymap());
1255        assert!(bar.on_event.is_none());
1256
1257        let bar: Menubar<'_, u8, ()> = menubar(&state)
1258            .width(300.0)
1259            .keymap(Keymap::new())
1260            .menu_keymap(Keymap::new())
1261            .on_event(|_| ());
1262        assert_eq!(bar.width, 300.0);
1263        assert!(bar.keymap.is_empty() && bar.menu_keymap.is_empty());
1264        assert!(bar.on_event.is_some());
1265    }
1266
1267    #[test]
1268    fn titles_use_the_accent_while_hovered_or_active() {
1269        for theme in [light(), dark()] {
1270            let tokens = Tokens::of(&theme);
1271            let idle = trigger_style(&tokens, TriggerStatus::Idle);
1272            assert_eq!(idle.background, None);
1273            assert_eq!(idle.text_color, tokens.foreground);
1274            for status in [TriggerStatus::Hovered, TriggerStatus::Active] {
1275                let style = trigger_style(&tokens, status);
1276                assert_eq!(style.background, Some(Background::Color(tokens.accent)));
1277                assert_eq!(style.text_color, tokens.accent_foreground);
1278                assert_eq!(style.border.radius, radius::SM.into());
1279            }
1280            let disabled = trigger_style(&tokens, TriggerStatus::Disabled);
1281            assert_eq!(disabled.text_color, tokens.muted_foreground);
1282            assert_eq!(disabled.background, None);
1283            assert_eq!(
1284                bar_style(&tokens).background,
1285                Some(Background::Color(tokens.background))
1286            );
1287        }
1288    }
1289
1290    #[test]
1291    fn an_active_title_stays_active_under_the_pointer() {
1292        use widget::button::Status;
1293        assert_eq!(trigger_status(Status::Hovered, true), TriggerStatus::Active);
1294        assert_eq!(
1295            trigger_status(Status::Hovered, false),
1296            TriggerStatus::Hovered
1297        );
1298        assert_eq!(
1299            trigger_status(Status::Pressed, false),
1300            TriggerStatus::Hovered
1301        );
1302        assert_eq!(trigger_status(Status::Active, false), TriggerStatus::Idle);
1303        assert_eq!(
1304            trigger_status(Status::Disabled, true),
1305            TriggerStatus::Disabled
1306        );
1307    }
1308
1309    #[test]
1310    fn mnemonics_split_the_title_around_the_first_match() {
1311        assert_eq!(
1312            split_mnemonic("Help", Some('p')),
1313            Some(("Hel".into(), "p".into(), String::new()))
1314        );
1315        assert_eq!(
1316            split_mnemonic("File", Some('f')),
1317            Some((String::new(), "F".into(), "ile".into()))
1318        );
1319        assert_eq!(split_mnemonic("File", Some('x')), None);
1320        assert_eq!(split_mnemonic("File", None), None);
1321    }
1322}