use muda::accelerator::{Accelerator, CMD_OR_CTRL, Code, Modifiers}; use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu}; use crate::native_edit::EditCommand; const PREFERENCES: &str = "preferences"; const MODEL_MANAGER: &str = "model-manager"; const UNDO: &str = "undo"; const REDO: &str = "redo"; const CUT: &str = "cut"; const COPY: &str = "copy"; const PASTE: &str = "paste"; const SELECT_ALL: &str = "select-all"; pub(crate) struct NativeMenu { _menu: Menu, } #[derive(Clone, Copy, Debug)] pub(crate) enum NativeMenuEvent { Preferences, ModelManager, Edit(EditCommand), } pub(crate) fn install() -> Result { let menu = Menu::new(); let application = Submenu::new("DS4Server", true); let edit = Submenu::new("Edit", true); let window = Submenu::new("Window", true); let preferences = MenuItem::with_id( PREFERENCES, "Preferences…", true, Some(Accelerator::new(Some(Modifiers::SUPER), Code::Comma)), ); let model_manager = MenuItem::with_id( MODEL_MANAGER, "Model Manager…", true, Some(Accelerator::new( Some(Modifiers::SUPER | Modifiers::SHIFT), Code::KeyM, )), ); let undo = edit_item(UNDO, "Undo", Code::KeyZ, None); let redo = edit_item(REDO, "Redo", Code::KeyZ, Some(Modifiers::SHIFT)); let cut = edit_item(CUT, "Cut", Code::KeyX, None); let copy = edit_item(COPY, "Copy", Code::KeyC, None); let paste = edit_item(PASTE, "Paste", Code::KeyV, None); let select_all = edit_item(SELECT_ALL, "Select All", Code::KeyA, None); application .append_items(&[ &PredefinedMenuItem::about(None, None), &PredefinedMenuItem::separator(), &preferences, &PredefinedMenuItem::separator(), &PredefinedMenuItem::services(None), &PredefinedMenuItem::separator(), &PredefinedMenuItem::hide(None), &PredefinedMenuItem::hide_others(None), &PredefinedMenuItem::show_all(None), &PredefinedMenuItem::separator(), &PredefinedMenuItem::quit(None), ]) .map_err(|error| error.to_string())?; edit.append_items(&[ &undo, &redo, &PredefinedMenuItem::separator(), &cut, ©, &paste, &select_all, ]) .map_err(|error| error.to_string())?; window .append_items(&[ &model_manager, &PredefinedMenuItem::separator(), &PredefinedMenuItem::minimize(None), &PredefinedMenuItem::maximize(None), &PredefinedMenuItem::close_window(None), &PredefinedMenuItem::bring_all_to_front(None), ]) .map_err(|error| error.to_string())?; menu.append_items(&[&application, &edit, &window]) .map_err(|error| error.to_string())?; menu.init_for_nsapp(); window.set_as_windows_menu_for_nsapp(); Ok(NativeMenu { _menu: menu }) } pub(crate) fn next_event() -> Option { while let Ok(event) = MenuEvent::receiver().try_recv() { let event = match event.id.0.as_str() { PREFERENCES => NativeMenuEvent::Preferences, MODEL_MANAGER => NativeMenuEvent::ModelManager, UNDO => NativeMenuEvent::Edit(EditCommand::Undo), REDO => NativeMenuEvent::Edit(EditCommand::Redo), CUT => NativeMenuEvent::Edit(EditCommand::Cut), COPY => NativeMenuEvent::Edit(EditCommand::Copy), PASTE => NativeMenuEvent::Edit(EditCommand::Paste), SELECT_ALL => NativeMenuEvent::Edit(EditCommand::SelectAll), _ => continue, }; return Some(event); } None } fn edit_item( id: &'static str, label: &'static str, code: Code, extra_modifier: Option, ) -> MenuItem { MenuItem::with_id( id, label, true, Some(Accelerator::new( Some(CMD_OR_CTRL | extra_modifier.unwrap_or_else(Modifiers::empty)), code, )), ) } #[cfg(test)] mod tests { use super::*; #[test] fn edit_items_are_custom_menu_events() { let paste = edit_item(PASTE, "Paste", Code::KeyV, None); assert_eq!(paste.id().0, PASTE); assert!(paste.is_enabled()); } }