1use async_trait::async_trait;
8use origin_domain::Result;
9use std::fmt::Debug;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct TrayMenuItem {
15 pub id: String,
17 pub label: String,
18 pub enabled: bool,
20}
21
22impl TrayMenuItem {
23 pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
24 Self {
25 id: id.into(),
26 label: label.into(),
27 enabled: true,
28 }
29 }
30
31 pub fn disabled(mut self) -> Self {
32 self.enabled = false;
33 self
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum TrayBadge {
40 None,
41 Attention,
43 Count(u32),
45}
46
47#[async_trait]
52pub trait TrayService: Debug + Send + Sync + 'static {
53 async fn set_title(&self, title: &str) -> Result<()>;
55
56 async fn set_badge(&self, badge: TrayBadge) -> Result<()>;
58
59 async fn set_menu(&self, items: Vec<TrayMenuItem>) -> Result<()>;
65}
66
67#[derive(Debug, Clone, Copy, Default)]
69pub struct NoopTrayService;
70
71#[async_trait]
72impl TrayService for NoopTrayService {
73 async fn set_title(&self, title: &str) -> Result<()> {
74 tracing::debug!(title, "tray title — dropped (noop service)");
75 Ok(())
76 }
77
78 async fn set_badge(&self, badge: TrayBadge) -> Result<()> {
79 tracing::debug!(?badge, "tray badge — dropped (noop service)");
80 Ok(())
81 }
82
83 async fn set_menu(&self, items: Vec<TrayMenuItem>) -> Result<()> {
84 tracing::debug!(count = items.len(), "tray menu — dropped (noop service)");
85 Ok(())
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[tokio::test]
94 async fn the_noop_service_accepts_everything_without_error() {
95 let tray: &dyn TrayService = &NoopTrayService;
96
97 tray.set_title("demo").await.unwrap();
98 tray.set_badge(TrayBadge::Count(3)).await.unwrap();
99 tray.set_menu(vec![
100 TrayMenuItem::new("show", "Show window"),
101 TrayMenuItem::new("quit", "Quit"),
102 ])
103 .await
104 .unwrap();
105 }
106}