feat: more native menus with expected reactions

This commit is contained in:
Georg Bauer
2026-07-27 15:09:12 +02:00
parent 8f02f0934b
commit c96675c462
11 changed files with 738 additions and 70 deletions

View File

@@ -1,10 +1,14 @@
use muda::accelerator::{Accelerator, CMD_OR_CTRL, Code, Modifiers};
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use crate::native_edit::EditCommand;
const PREFERENCES: &str = "preferences";
const MODEL_MANAGER: &str = "model-manager";
const NEW_CHAT: &str = "new-chat";
const EXPORT_CHAT: &str = "export-chat";
const TOGGLE_SIDEBAR: &str = "toggle-sidebar";
const HELP: &str = "help";
const UNDO: &str = "undo";
const REDO: &str = "redo";
const CUT: &str = "cut";
@@ -14,20 +18,36 @@ const SELECT_ALL: &str = "select-all";
pub(crate) struct NativeMenu {
_menu: Menu,
new_chat: MenuItem,
export_chat: MenuItem,
sidebar: CheckMenuItem,
undo: MenuItem,
redo: MenuItem,
cut: MenuItem,
copy: MenuItem,
paste: MenuItem,
select_all: MenuItem,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum NativeMenuEvent {
Preferences,
ModelManager,
NewChat,
ExportChat,
ToggleSidebar,
Help,
Edit(EditCommand),
}
pub(crate) fn install() -> Result<NativeMenu, String> {
let menu = Menu::new();
let application = Submenu::new("DS4Server", true);
let file = Submenu::new("File", true);
let edit = Submenu::new("Edit", true);
let view = Submenu::new("View", true);
let window = Submenu::new("Window", true);
let help = Submenu::new("Help", true);
let preferences = MenuItem::with_id(
PREFERENCES,
"Preferences…",
@@ -43,6 +63,37 @@ pub(crate) fn install() -> Result<NativeMenu, String> {
Code::KeyM,
)),
);
let new_chat = MenuItem::with_id(
NEW_CHAT,
"New Chat",
false,
Some(Accelerator::new(Some(Modifiers::SUPER), Code::KeyN)),
);
let export_chat = MenuItem::with_id(
EXPORT_CHAT,
"Export Chat as Markdown…",
false,
Some(Accelerator::new(
Some(Modifiers::SUPER | Modifiers::SHIFT),
Code::KeyS,
)),
);
let sidebar = CheckMenuItem::with_id(
TOGGLE_SIDEBAR,
"Show Sidebar",
true,
true,
Some(Accelerator::new(Some(Modifiers::SUPER), Code::KeyB)),
);
let open_help = MenuItem::with_id(
HELP,
"DS4Server Help",
true,
Some(Accelerator::new(
Some(Modifiers::SUPER | Modifiers::SHIFT),
Code::Slash,
)),
);
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);
@@ -65,6 +116,13 @@ pub(crate) fn install() -> Result<NativeMenu, String> {
&PredefinedMenuItem::quit(None),
])
.map_err(|error| error.to_string())?;
file.append_items(&[
&new_chat,
&export_chat,
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::close_window(None),
])
.map_err(|error| error.to_string())?;
edit.append_items(&[
&undo,
&redo,
@@ -75,21 +133,53 @@ pub(crate) fn install() -> Result<NativeMenu, String> {
&select_all,
])
.map_err(|error| error.to_string())?;
view.append_items(&[&sidebar, &PredefinedMenuItem::separator(), &model_manager])
.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])
help.append(&open_help).map_err(|error| error.to_string())?;
menu.append_items(&[&application, &file, &edit, &view, &window, &help])
.map_err(|error| error.to_string())?;
menu.init_for_nsapp();
window.set_as_windows_menu_for_nsapp();
Ok(NativeMenu { _menu: menu })
Ok(NativeMenu {
_menu: menu,
new_chat,
export_chat,
sidebar,
undo,
redo,
cut,
copy,
paste,
select_all,
})
}
impl NativeMenu {
pub(crate) fn update(
&self,
project_active: bool,
chat_active: bool,
sidebar_visible: bool,
edit: crate::native_edit::EditAvailability,
) {
self.new_chat.set_enabled(project_active);
self.export_chat.set_enabled(chat_active);
self.sidebar.set_checked(sidebar_visible);
self.undo.set_enabled(edit.undo);
self.redo.set_enabled(edit.redo);
self.cut.set_enabled(edit.cut);
self.copy.set_enabled(edit.copy);
self.paste.set_enabled(edit.paste);
self.select_all.set_enabled(edit.select_all);
}
}
pub(crate) fn next_event() -> Option<NativeMenuEvent> {
@@ -97,6 +187,10 @@ pub(crate) fn next_event() -> Option<NativeMenuEvent> {
let event = match event.id.0.as_str() {
PREFERENCES => NativeMenuEvent::Preferences,
MODEL_MANAGER => NativeMenuEvent::ModelManager,
NEW_CHAT => NativeMenuEvent::NewChat,
EXPORT_CHAT => NativeMenuEvent::ExportChat,
TOGGLE_SIDEBAR => NativeMenuEvent::ToggleSidebar,
HELP => NativeMenuEvent::Help,
UNDO => NativeMenuEvent::Edit(EditCommand::Undo),
REDO => NativeMenuEvent::Edit(EditCommand::Redo),
CUT => NativeMenuEvent::Edit(EditCommand::Cut),