diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ec5a781..7cc8f63 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -598,28 +598,59 @@ fn project_icon(project: String) -> Result, String> { Ok(Some(format!("data:image/png;base64,{}", STANDARD.encode(png)))) } -/// Projekt-Icon fürs Tray-Menü: sips konvertiert (auch ICNS) und skaliert auf -/// 36 px; muda rendert Menü-Icons auf macOS mit 18 pt. -fn menu_icon(paths: &Paths, project: &str) -> Result>, String> { - let Some(icon) = read_project_config_in(paths, project)?.terminal.icon else { - return Ok(None); - }; - let src = resolve_icon_path(paths, project, &icon); - let tmp = std::env::temp_dir().join(format!("ai-control-tray-icon-{project}.png")); - let out = Command::new("sips") - .args(["-s", "format", "png", "-z", "36", "36"]) - .arg(&src) - .arg("--out") - .arg(&tmp) - .output() - .map_err(|e| format!("sips: {e}"))?; - if !out.status.success() { - return Err(format!("sips: {}", String::from_utf8_lossy(&out.stderr))); +/// Menü-Icon fürs Tray: ganz links der Status-Punkt (grün = läuft), daneben +/// das Projekt-Icon (sips konvertiert, auch ICNS). Ein RGBA-Bild 56×36 px; +/// muda rendert Menü-Icons mit 18 pt Höhe und proportionaler Breite. +fn menu_icon( + paths: &Paths, + project: &str, + running: bool, +) -> Result, String> { + const H: usize = 36; + const DOT_W: usize = 20; + const W: usize = DOT_W + H; + let mut canvas = vec![0u8; W * H * 4]; + if running { + // macOS-Systemgrün, weicher Rand über Alpha. + let (cx, cy, r) = (10.0f32, 18.0f32, 7.0f32); + for y in 0..H { + for x in 0..DOT_W { + let d = ((x as f32 + 0.5 - cx).powi(2) + (y as f32 + 0.5 - cy).powi(2)).sqrt(); + let a = (r - d + 0.5).clamp(0.0, 1.0); + if a > 0.0 { + let i = (y * W + x) * 4; + canvas[i..i + 4].copy_from_slice(&[52, 199, 89, (a * 255.0) as u8]); + } + } + } } - let bytes = fs::read(&tmp).map_err(|e| format!("{}: {e}", tmp.display()))?; - let _ = fs::remove_file(&tmp); - let img = tauri::image::Image::from_bytes(&bytes).map_err(|e| e.to_string())?; - Ok(Some(img)) + if let Some(icon) = read_project_config_in(paths, project)?.terminal.icon { + let src = resolve_icon_path(paths, project, &icon); + let tmp = std::env::temp_dir().join(format!("ai-control-tray-icon-{project}.png")); + let out = Command::new("sips") + .args(["-s", "format", "png", "-z", "36", "36"]) + .arg(&src) + .arg("--out") + .arg(&tmp) + .output() + .map_err(|e| format!("sips: {e}"))?; + if !out.status.success() { + return Err(format!("sips: {}", String::from_utf8_lossy(&out.stderr))); + } + let bytes = fs::read(&tmp).map_err(|e| format!("{}: {e}", tmp.display()))?; + let _ = fs::remove_file(&tmp); + let img = tauri::image::Image::from_bytes(&bytes).map_err(|e| e.to_string())?; + let (iw, ih) = (img.width() as usize, img.height() as usize); + let rgba = img.rgba(); + for y in 0..ih.min(H) { + for x in 0..iw.min(H) { + let src_i = (y * iw + x) * 4; + let dst_i = (y * W + DOT_W + x) * 4; + canvas[dst_i..dst_i + 4].copy_from_slice(&rgba[src_i..src_i + 4]); + } + } + } + Ok(tauri::image::Image::new_owned(canvas, W as u32, H as u32)) } /// Terminal-Einstellungen eines Projekts, für den Terminal-Prozess. @@ -1511,8 +1542,8 @@ pub fn run() { } } -/// Tray-Menü: pro Projekt ein Eintrag mit Icon; das Label nennt die Aktion — -/// „starten" öffnet das Terminal, „zeigen" holt das laufende nach vorn. +/// Tray-Menü: pro Projekt ein Eintrag — Status-Punkt, Projekt-Icon, Name. +/// Klick startet das Projekt bzw. holt das laufende Terminal nach vorn. fn tray_menu( app: &tauri::AppHandle, ) -> Result, Box> { @@ -1524,18 +1555,13 @@ fn tray_menu( let mut projects = list_projects_in(&paths)?; projects.sort_by(|a, b| a.name.cmp(&b.name)); for p in projects { - let label = if p.running { - format!("{} zeigen", p.name) - } else { - format!("{} starten", p.name) - }; - let icon = menu_icon(&paths, &p.name)?; + let icon = menu_icon(&paths, &p.name, p.running)?; menu.append(&IconMenuItem::with_id( app, format!("project:{}", p.name), - label, + &p.name, true, - icon, + Some(icon), None::<&str>, )?)?; }