Files
DS4Server/src/native_menu.rs
2026-07-24 21:12:48 +02:00

141 lines
4.3 KiB
Rust

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<NativeMenu, String> {
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,
&copy,
&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<NativeMenuEvent> {
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<Modifiers>,
) -> 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());
}
}