use std::sync::atomic::{AtomicBool, Ordering}; use tauri::{ menu::{Menu, MenuItem}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, AppHandle, Manager, Runtime, }; pub fn build_tray(app: &AppHandle) -> tauri::Result<()> { let open = MenuItem::with_id(app, "open", "Open Robin", true, None::<&str>)?; let quit = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; let menu = Menu::with_items(app, &[&open, &quit])?; TrayIconBuilder::with_id("robin-tray") .tooltip("Robin") .icon(app.default_window_icon().cloned().unwrap()) .menu(&menu) .menu_on_left_click(false) .on_menu_event(|app, event| match event.id.as_ref() { "open" => toggle_chat_panel(app), "quit" => app.exit(0), _ => {} }) .on_tray_icon_event(|tray, event| { if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event { toggle_chat_panel(tray.app_handle()); } }) .build(app)?; Ok(()) } pub fn toggle_chat_panel(app: &AppHandle) { if let Some(window) = app.get_webview_window("chat") { if window.is_visible().unwrap_or(false) { let _ = window.hide(); } else { let _ = window.show(); let _ = window.set_focus(); } } } static BADGE_ACTIVE: AtomicBool = AtomicBool::new(false); pub fn badge_on(app: &AppHandle) { if BADGE_ACTIVE.swap(true, Ordering::Relaxed) { return; // already badged } if let Some(tray) = app.tray_by_id("robin-tray") { if let Some(icon) = app.default_window_icon().cloned() { let _ = tray.set_icon(Some(icon)); } let _ = tray.set_tooltip(Some("Robin — something to show you")); } } pub fn badge_off(app: &AppHandle) { BADGE_ACTIVE.store(false, Ordering::Relaxed); if let Some(tray) = app.tray_by_id("robin-tray") { if let Some(icon) = app.default_window_icon().cloned() { let _ = tray.set_icon(Some(icon)); } let _ = tray.set_tooltip(Some("Robin")); } } pub fn clear_badge_and_open(app: &AppHandle) { badge_off(app); toggle_chat_panel(app); }