use anyhow::{Context, Result};
use crossbeam_channel::Receiver;
use tray_icon::{
menu::{Menu, MenuEvent, MenuId, MenuItem},
TrayIcon, TrayIconBuilder,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TrayCommand {
Quit,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum IndicatorState {
Idle,
Recording,
}
impl IndicatorState {
#[must_use]
pub const fn tooltip(self) -> &'static str {
match self {
Self::Idle => "scrybe — idle",
Self::Recording => "scrybe — recording",
}
}
}
pub struct RecordingIndicator {
tray: TrayIcon,
quit_id: MenuId,
menu_events: Receiver<MenuEvent>,
}
impl RecordingIndicator {
pub fn start(initial: IndicatorState) -> Result<Self> {
let menu = Menu::new();
let quit_item = MenuItem::new("Quit scrybe", true, None);
let quit_id = quit_item.id().clone();
menu.append(&quit_item)
.context("appending quit menu item")?;
let tray = TrayIconBuilder::new()
.with_menu(Box::new(menu))
.with_tooltip(initial.tooltip())
.with_title("scrybe")
.build()
.context("building tray icon")?;
let menu_events = MenuEvent::receiver().clone();
Ok(Self {
tray,
quit_id,
menu_events,
})
}
pub fn set_state(&self, state: IndicatorState) {
let _ = self.tray.set_tooltip(Some(state.tooltip()));
}
pub fn poll(&self) -> Option<TrayCommand> {
while let Ok(event) = self.menu_events.try_recv() {
if event.id == self.quit_id {
return Some(TrayCommand::Quit);
}
}
None
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn test_indicator_state_idle_returns_idle_tooltip() {
assert_eq!(IndicatorState::Idle.tooltip(), "scrybe — idle");
}
#[test]
fn test_indicator_state_recording_returns_recording_tooltip() {
assert_eq!(IndicatorState::Recording.tooltip(), "scrybe — recording");
}
}