unistore-tray 0.1.0

System tray capability for UniStore - cross-platform tray icon, menu, and notifications
Documentation
//! 【事件定义】- TrayEvent 和 MenuItemId
//!
//! 职责:
//! - 定义托盘事件类型
//! - 定义菜单项标识

use std::fmt;

/// 菜单项唯一标识
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MenuItemId(String);

impl MenuItemId {
    /// 创建新的菜单项 ID
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }

    /// 获取 ID 字符串引用
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// 消费并返回内部字符串
    pub fn into_string(self) -> String {
        self.0
    }
}

impl<S: Into<String>> From<S> for MenuItemId {
    fn from(s: S) -> Self {
        Self(s.into())
    }
}

impl fmt::Display for MenuItemId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// 托盘事件
#[derive(Debug, Clone)]
pub enum TrayEvent {
    /// 左键单击
    Click,

    /// 左键双击(仅 Windows 支持)
    DoubleClick,

    /// 右键点击
    RightClick,

    /// 菜单项被点击
    MenuItemClicked(MenuItemId),

    /// 托盘图标已就绪
    Ready,

    /// 托盘被销毁
    Destroyed,
}

impl TrayEvent {
    /// 是否为菜单事件
    pub fn is_menu_event(&self) -> bool {
        matches!(self, TrayEvent::MenuItemClicked(_))
    }

    /// 是否为点击事件
    pub fn is_click_event(&self) -> bool {
        matches!(
            self,
            TrayEvent::Click | TrayEvent::DoubleClick | TrayEvent::RightClick
        )
    }

    /// 获取菜单项 ID(如果是菜单事件)
    pub fn menu_item_id(&self) -> Option<&MenuItemId> {
        match self {
            TrayEvent::MenuItemClicked(id) => Some(id),
            _ => None,
        }
    }
}

impl fmt::Display for TrayEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TrayEvent::Click => write!(f, "Click"),
            TrayEvent::DoubleClick => write!(f, "DoubleClick"),
            TrayEvent::RightClick => write!(f, "RightClick"),
            TrayEvent::MenuItemClicked(id) => write!(f, "MenuItemClicked({})", id),
            TrayEvent::Ready => write!(f, "Ready"),
            TrayEvent::Destroyed => write!(f, "Destroyed"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_menu_item_id() {
        let id = MenuItemId::new("test_id");
        assert_eq!(id.as_str(), "test_id");
        assert_eq!(id.to_string(), "test_id");
    }

    #[test]
    fn test_menu_item_id_from() {
        let id1: MenuItemId = "from_str".into();
        let id2: MenuItemId = String::from("from_string").into();
        
        assert_eq!(id1.as_str(), "from_str");
        assert_eq!(id2.as_str(), "from_string");
    }

    #[test]
    fn test_event_is_menu() {
        assert!(TrayEvent::MenuItemClicked("id".into()).is_menu_event());
        assert!(!TrayEvent::Click.is_menu_event());
        assert!(!TrayEvent::Ready.is_menu_event());
    }

    #[test]
    fn test_event_is_click() {
        assert!(TrayEvent::Click.is_click_event());
        assert!(TrayEvent::DoubleClick.is_click_event());
        assert!(TrayEvent::RightClick.is_click_event());
        assert!(!TrayEvent::Ready.is_click_event());
        assert!(!TrayEvent::MenuItemClicked("id".into()).is_click_event());
    }

    #[test]
    fn test_event_menu_item_id() {
        let event = TrayEvent::MenuItemClicked("test".into());
        assert_eq!(event.menu_item_id().map(|id| id.as_str()), Some("test"));

        let event = TrayEvent::Click;
        assert!(event.menu_item_id().is_none());
    }

    #[test]
    fn test_event_display() {
        assert_eq!(TrayEvent::Click.to_string(), "Click");
        assert_eq!(TrayEvent::Ready.to_string(), "Ready");
        assert_eq!(
            TrayEvent::MenuItemClicked("exit".into()).to_string(),
            "MenuItemClicked(exit)"
        );
    }
}