desktop: tray menu panels and pinnable context menus
The tray's right-click menu is drawn by the app: one tree from tray.rs (stable submenu keys), one frameless always-on-top menu-N window per panel rendered by tray-menu.html with the design system, placed inside the monitor's work area. A pure panel book keeps transient and pinned panels apart; pinned panels (pin button, Ctrl+click, middle click) are draggable, step aside from each other and are saved in state.json (menuPins) and restored at start. tray_update pushes the tree to the open panels, which update in place; closed panels are destroyed. The native menu, rendered from the same tree, stays as the fallback and on Linux. Only menu-* windows get the menu_* commands (menu.json). In-app context menus get the same behaviour through a pure menu stack (ui/menu-stack.ts): pin a menu, tear off a submenu, pinned panels survive the next menu, are draggable and re-read their entries from live sources so data changes update them in place. Also drops a stray diff3 marker line from CHANGELOG.md.
This commit is contained in:
parent
445ab1adbc
commit
5236f002f1
35 changed files with 2308 additions and 123 deletions
|
|
@ -49,6 +49,11 @@ fn guard_card(w: &WebviewWindow) -> Res<()> {
|
|||
policy::require_card(w.label(), &url, cfg!(debug_assertions)).map_err(Fail::host)
|
||||
}
|
||||
|
||||
fn guard_menu(w: &WebviewWindow) -> Res<()> {
|
||||
let url = w.url().map_err(|e| Fail::host(e.to_string()))?;
|
||||
policy::require_menu(w.label(), &url, cfg!(debug_assertions)).map_err(Fail::host)
|
||||
}
|
||||
|
||||
fn saved(s: &AppState) -> Res<std::sync::MutexGuard<'_, Saved>> {
|
||||
s.saved.lock().map_err(|_| Fail::host("state poisoned"))
|
||||
}
|
||||
|
|
@ -1665,3 +1670,79 @@ pub async fn ai_fix_undo(window: WebviewWindow, run_id: String) -> Res<Vec<Strin
|
|||
let rec = ai_fix::runs().lock().ok().and_then(|mut m| m.remove(&run_id)).ok_or_else(|| Fail::host("no such AI fix run"))?;
|
||||
tauri::async_runtime::spawn_blocking(move || ai_fix::undo(&rec.0, &rec.1)).await.map_err(|e| Fail::host(e.to_string()))?.map_err(Fail::host)
|
||||
}
|
||||
|
||||
// ---- the tray menu panels (menu_panel.rs; only `menu-*` windows) ----
|
||||
|
||||
/// A panel: what it shows (the whole tree and its path into it).
|
||||
#[tauri::command]
|
||||
pub fn menu_state(window: WebviewWindow) -> Res<Option<crate::menu_panel::PanelInfo>> {
|
||||
guard_menu(&window)?;
|
||||
Ok(crate::menu_panel::panel_state(&window))
|
||||
}
|
||||
|
||||
/// A panel measured itself (CSS px): sized, placed inside the work area and shown.
|
||||
#[tauri::command]
|
||||
pub fn menu_ready(window: WebviewWindow, width: f64, height: f64) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::ready(&window, width, height);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Opens a submenu beside this panel at the row whose top is `top` (CSS px).
|
||||
#[tauri::command]
|
||||
pub fn menu_open_sub(window: WebviewWindow, key: String, top: f64, focus: bool) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::open_sub(&window, &key, top, focus, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ctrl / middle click on a submenu row: opens it as its own pinned panel.
|
||||
#[tauri::command]
|
||||
pub fn menu_pin_sub(window: WebviewWindow, key: String, top: f64) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::open_sub(&window, &key, top, false, true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The pointer moved to a plain row: this panel's submenus close.
|
||||
#[tauri::command]
|
||||
pub fn menu_close_subs(window: WebviewWindow) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::close_subs(&window);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Closes this panel (Left / Esc in a submenu, X on a pinned one).
|
||||
#[tauri::command]
|
||||
pub fn menu_close(window: WebviewWindow, refocus_parent: bool) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::close(&window, refocus_parent);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Esc on the root menu: every transient panel closes.
|
||||
#[tauri::command]
|
||||
pub fn menu_dismiss(window: WebviewWindow) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::dismiss(window.app_handle());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pins or unpins this panel.
|
||||
#[tauri::command]
|
||||
pub fn menu_pin(window: WebviewWindow, pinned: bool) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
crate::menu_panel::pin(&window, pinned);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A pick: the same action as the native tray menu (an id tray.rs knows; anything else is refused).
|
||||
#[tauri::command]
|
||||
pub fn menu_select(window: WebviewWindow, id: String) -> Res<()> {
|
||||
guard_menu(&window)?;
|
||||
if id.len() > 300 || crate::tray::action_of(&id).is_none() {
|
||||
return Err(Fail::host("unknown menu item"));
|
||||
}
|
||||
crate::menu_panel::select(&window, id);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue