Files
IronStorage/apps/desktop/src/native_menu.rs

144 lines
5.3 KiB
Rust

//! Safe macOS menu adapter over the shared desktop action registry.
use muda::{
Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu,
accelerator::{Accelerator, Code, Modifiers},
};
use crate::action::{self, ActionContext, MenuGroup, UiAction};
pub struct NativeMenu {
_menu: Menu,
items: Vec<(UiAction, MenuItem)>,
}
impl NativeMenu {
pub fn install(context: ActionContext) -> Result<Self, muda::Error> {
let menu = Menu::new();
let mut items = Vec::new();
for group in MenuGroup::ALL {
let submenu = Submenu::new(group.label(), true);
match group {
MenuGroup::App => {
submenu.append(&PredefinedMenuItem::about(None, None))?;
submenu.append(&PredefinedMenuItem::separator())?;
append_action(&submenu, UiAction::Settings, context, &mut items)?;
submenu.append(&PredefinedMenuItem::separator())?;
submenu.append(&PredefinedMenuItem::services(None))?;
submenu.append(&PredefinedMenuItem::separator())?;
submenu.append(&PredefinedMenuItem::hide(None))?;
submenu.append(&PredefinedMenuItem::hide_others(None))?;
submenu.append(&PredefinedMenuItem::show_all(None))?;
submenu.append(&PredefinedMenuItem::separator())?;
append_action(&submenu, UiAction::Quit, context, &mut items)?;
}
MenuGroup::Window => {
submenu.append(&PredefinedMenuItem::minimize(None))?;
submenu.append(&PredefinedMenuItem::bring_all_to_front(None))?;
}
MenuGroup::Edit => {
submenu.append(&PredefinedMenuItem::undo(None))?;
submenu.append(&PredefinedMenuItem::redo(None))?;
submenu.append(&PredefinedMenuItem::separator())?;
submenu.append(&PredefinedMenuItem::cut(None))?;
submenu.append(&PredefinedMenuItem::copy(None))?;
submenu.append(&PredefinedMenuItem::paste(None))?;
submenu.append(&PredefinedMenuItem::select_all(None))?;
submenu.append(&PredefinedMenuItem::separator())?;
append_action(&submenu, UiAction::Find, context, &mut items)?;
}
_ => append_actions(&submenu, group, context, &mut items)?,
}
menu.append(&submenu)?;
}
menu.init_for_nsapp();
Ok(Self { _menu: menu, items })
}
pub fn sync(&self, context: ActionContext) {
for (action, item) in &self.items {
item.set_enabled(action::enabled(*action, context));
}
}
pub fn poll(&self) -> Option<UiAction> {
let event = MenuEvent::receiver().try_recv().ok()?;
self.items
.iter()
.find_map(|(action, item)| (item.id() == &event.id).then_some(*action))
}
}
fn append_actions(
submenu: &Submenu,
group: MenuGroup,
context: ActionContext,
items: &mut Vec<(UiAction, MenuItem)>,
) -> Result<(), muda::Error> {
for spec in action::actions_in(group).filter(|spec| {
!matches!(
spec.action,
UiAction::About
| UiAction::Undo
| UiAction::Redo
| UiAction::Cut
| UiAction::CopyEditedField
| UiAction::Paste
| UiAction::Minimize
| UiAction::TogglePaneFocus
)
}) {
append_action(submenu, spec.action, context, items)?;
}
Ok(())
}
fn append_action(
submenu: &Submenu,
action: UiAction,
context: ActionContext,
items: &mut Vec<(UiAction, MenuItem)>,
) -> Result<(), muda::Error> {
let spec = action::spec_for(action);
let item = MenuItem::with_id(
format!("ironstorage.{}", action.id()),
spec.label,
action::enabled(action, context),
accelerator(action),
);
submenu.append(&item)?;
items.push((action, item));
Ok(())
}
fn accelerator(action: UiAction) -> Option<Accelerator> {
let command = Modifiers::SUPER;
let (modifiers, code) = match action {
UiAction::Settings => (command, Code::Comma),
UiAction::NewEntry => (command, Code::KeyN),
UiAction::OpenFolder => (command, Code::KeyO),
UiAction::Save => (command, Code::KeyS),
UiAction::CloseWindow => (command, Code::KeyW),
UiAction::Quit => (command, Code::KeyQ),
UiAction::Undo => (command, Code::KeyZ),
UiAction::Redo => (command | Modifiers::SHIFT, Code::KeyZ),
UiAction::Cut => (command, Code::KeyX),
UiAction::Paste => (command, Code::KeyV),
UiAction::Find => (command, Code::KeyF),
UiAction::CommandPalette => (command, Code::KeyK),
UiAction::Refresh => (command, Code::KeyR),
UiAction::Lock => (command, Code::KeyL),
UiAction::Help => (Modifiers::empty(), Code::F1),
UiAction::About
| UiAction::CopyField
| UiAction::CopyEditedField
| UiAction::TogglePaneFocus
| UiAction::OpenEntry
| UiAction::ReloadEntry
| UiAction::EditEntry
| UiAction::ToggleReveal
| UiAction::Minimize => return None,
};
Some(Accelerator::new(Some(modifiers), code))
}