Add feature batch 2, subscription/recurring sync, smooth charts, and app icon
This commit is contained in:
147
outlay-gtk/src/tray.rs
Normal file
147
outlay-gtk/src/tray.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use ksni::menu::StandardItem;
|
||||
use ksni::{Category, ToolTip, Tray, TrayMethods};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub enum TrayCommand {
|
||||
Show,
|
||||
QuickAdd,
|
||||
LogExpense,
|
||||
LogIncome,
|
||||
Quit,
|
||||
}
|
||||
|
||||
struct OutlayTray {
|
||||
sender: mpsc::Sender<TrayCommand>,
|
||||
icon_theme_path: String,
|
||||
}
|
||||
|
||||
impl Tray for OutlayTray {
|
||||
fn id(&self) -> String {
|
||||
"outlay".into()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
"Outlay".into()
|
||||
}
|
||||
|
||||
fn icon_name(&self) -> String {
|
||||
"io.github.outlay".into()
|
||||
}
|
||||
|
||||
fn icon_theme_path(&self) -> String {
|
||||
self.icon_theme_path.clone()
|
||||
}
|
||||
|
||||
fn category(&self) -> Category {
|
||||
Category::ApplicationStatus
|
||||
}
|
||||
|
||||
fn tool_tip(&self) -> ToolTip {
|
||||
ToolTip {
|
||||
title: "Outlay - Personal Finance".into(),
|
||||
description: String::new(),
|
||||
icon_name: String::new(),
|
||||
icon_pixmap: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn activate(&mut self, _x: i32, _y: i32) {
|
||||
self.sender.send(TrayCommand::Show).ok();
|
||||
}
|
||||
|
||||
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
|
||||
vec![
|
||||
StandardItem {
|
||||
label: "Show Outlay".into(),
|
||||
activate: Box::new(|tray: &mut Self| {
|
||||
tray.sender.send(TrayCommand::Show).ok();
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
ksni::MenuItem::Separator,
|
||||
StandardItem {
|
||||
label: "Quick Add".into(),
|
||||
activate: Box::new(|tray: &mut Self| {
|
||||
tray.sender.send(TrayCommand::QuickAdd).ok();
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
StandardItem {
|
||||
label: "Log Expense".into(),
|
||||
activate: Box::new(|tray: &mut Self| {
|
||||
tray.sender.send(TrayCommand::LogExpense).ok();
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
StandardItem {
|
||||
label: "Log Income".into(),
|
||||
activate: Box::new(|tray: &mut Self| {
|
||||
tray.sender.send(TrayCommand::LogIncome).ok();
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
ksni::MenuItem::Separator,
|
||||
StandardItem {
|
||||
label: "Quit".into(),
|
||||
activate: Box::new(|tray: &mut Self| {
|
||||
tray.sender.send(TrayCommand::Quit).ok();
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn find_icon_theme_path() -> String {
|
||||
let exe_path = std::env::current_exe().unwrap_or_default();
|
||||
let exe_dir = exe_path.parent().unwrap_or(std::path::Path::new("."));
|
||||
|
||||
let candidates = [
|
||||
exe_dir.join("../../outlay-gtk/data/icons"),
|
||||
exe_dir.join("../share/icons"),
|
||||
PathBuf::from("/usr/share/icons"),
|
||||
];
|
||||
|
||||
for candidate in &candidates {
|
||||
if candidate.exists() {
|
||||
if let Ok(resolved) = candidate.canonicalize() {
|
||||
return resolved.to_string_lossy().into_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn spawn_tray(sender: mpsc::Sender<TrayCommand>) {
|
||||
let icon_theme_path = find_icon_theme_path();
|
||||
|
||||
let tray = OutlayTray {
|
||||
sender,
|
||||
icon_theme_path,
|
||||
};
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create tokio runtime for tray");
|
||||
|
||||
rt.block_on(async {
|
||||
match tray.spawn().await {
|
||||
Ok(_handle) => {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[tray] Failed to register: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user