Reorganize codebase into modular structure
This commit is contained in:
+194
@@ -0,0 +1,194 @@
|
||||
// System tray functionality
|
||||
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
use ::image::open as image_open;
|
||||
use tray_icon::{
|
||||
TrayIconBuilder,
|
||||
TrayIcon,
|
||||
TrayIconEvent,
|
||||
MouseButton,
|
||||
menu::{Menu, MenuEvent},
|
||||
};
|
||||
|
||||
use crate::app::{Message, CursorInfo, TRAY_SENDER, SharedState};
|
||||
|
||||
// Define menu item IDs
|
||||
const NEXT_CURSOR_ID: &str = "next_cursor";
|
||||
const SHOW_WINDOW_ID: &str = "show_window";
|
||||
const QUIT_ID: &str = "quit";
|
||||
|
||||
// Set up the system tray icon
|
||||
pub fn setup_tray_icon(
|
||||
cursors: &[CursorInfo],
|
||||
shared_state: Arc<Mutex<SharedState>>,
|
||||
) -> Option<TrayIcon> { // Return Option<TrayIcon>
|
||||
if cursors.is_empty() {
|
||||
return None; // Return None if no cursors
|
||||
}
|
||||
|
||||
// Create a menu
|
||||
let menu = Menu::with_items(&[
|
||||
&tray_icon::menu::MenuItem::with_id(NEXT_CURSOR_ID, "Next cursor", true, None),
|
||||
&tray_icon::menu::MenuItem::with_id(SHOW_WINDOW_ID, "Show window", true, None),
|
||||
&tray_icon::menu::MenuItem::with_id(QUIT_ID, "Quit", true, None),
|
||||
]).ok()?; // Use ok()? to convert Result to Option
|
||||
|
||||
|
||||
// Use the first PNG as the tray icon
|
||||
let icon_path = &cursors[0].path;
|
||||
let img = image_open(icon_path).ok()?; // Use ok()?
|
||||
let rgba_img = img.to_rgba8();
|
||||
let (width, height) = rgba_img.dimensions();
|
||||
let icon = tray_icon::Icon::from_rgba(rgba_img.into_raw(), width, height).ok()?; // Use ok()?
|
||||
|
||||
// Build the tray icon
|
||||
println!("Building tray icon...");
|
||||
let tray_result = TrayIconBuilder::new()
|
||||
.with_tooltip("Cursor Rush")
|
||||
.with_icon(icon)
|
||||
.with_menu(Box::new(menu))
|
||||
.with_menu_on_left_click(false) // Disable menu on left click - only show on right click
|
||||
.build();
|
||||
|
||||
println!("Tray icon configuration: menu_on_left_click = false");
|
||||
|
||||
// Store the tray icon itself
|
||||
let tray = match tray_result {
|
||||
Ok(tray_instance) => {
|
||||
println!("Tray icon built successfully");
|
||||
tray_instance // Assign the TrayIcon object
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to build tray icon: {}", e);
|
||||
return None; // Return None on error
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for tray menu events in a separate thread
|
||||
let state_clone = shared_state.clone();
|
||||
println!("Spawning tray menu event listener thread...");
|
||||
thread::spawn(move || {
|
||||
let menu_channel = MenuEvent::receiver();
|
||||
println!("Tray menu event listener waiting for events...");
|
||||
|
||||
while let Ok(event) = menu_channel.recv() {
|
||||
println!("Tray menu event received: ID = {:?}", event.id);
|
||||
let msg_to_send = match event.id.as_ref() {
|
||||
NEXT_CURSOR_ID => {
|
||||
println!("Tray: Next Cursor requested");
|
||||
match state_clone.lock() {
|
||||
Ok(mut state) => {
|
||||
if !state.cursors.is_empty() {
|
||||
state.current_index = (state.current_index + 1) % state.cursors.len();
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
println!("Tray: Updating cursor image: {}", cursor.name);
|
||||
crate::app::apply_cursor(&cursor.path);
|
||||
// No need to call render_cursor anymore as the mouse hook handles rendering
|
||||
state.current_cursor_image = image_open(&cursor.path).ok().map(|img| img.to_rgba8());
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("Tray: Failed to lock shared state for next cursor: {}", e),
|
||||
}
|
||||
// Send TrayEvent just to trigger UI update if needed
|
||||
Some(Message::TrayEvent)
|
||||
},
|
||||
SHOW_WINDOW_ID => {
|
||||
println!("Tray: Show Window requested");
|
||||
// Send ShowWindow message to main thread
|
||||
Some(Message::ShowWindow)
|
||||
},
|
||||
QUIT_ID => {
|
||||
println!("Tray: Quit requested");
|
||||
// Send Quit message to main thread
|
||||
Some(Message::Quit)
|
||||
},
|
||||
_ => {
|
||||
println!("Tray: Unknown menu event ID: {:?}", event.id);
|
||||
None // Don't send a message for unknown IDs
|
||||
}
|
||||
};
|
||||
|
||||
// Send the determined message to the main UI thread using the global sender
|
||||
if let Some(msg) = msg_to_send {
|
||||
// Get the global sender
|
||||
if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() {
|
||||
if let Err(e) = tx.unbounded_send(msg.clone()) { // Use clone if msg is needed after send
|
||||
eprintln!("Tray: Failed to send {:?} message: {}", msg, e);
|
||||
break; // Stop thread if channel broken
|
||||
}
|
||||
// If Quit message was sent, break the loop to exit the listener thread
|
||||
if matches!(msg, Message::Quit) {
|
||||
println!("Tray: Quit message sent, exiting listener thread");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
eprintln!("Tray: Global sender not initialized yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Tray menu event listener thread finished");
|
||||
});
|
||||
|
||||
// Set up a direct event handler for tray icon events
|
||||
// This is the preferred way to handle tray icon clicks
|
||||
println!("Setting up tray icon click event handler...");
|
||||
|
||||
// Use set_event_handler to directly handle tray icon events
|
||||
TrayIconEvent::set_event_handler(Some(move |event| {
|
||||
// Only process important events, filter out mouse movements
|
||||
match event {
|
||||
// Handle click events
|
||||
TrayIconEvent::Click { button, .. } => {
|
||||
println!("Tray: Click detected with button: {:?}", button);
|
||||
// Left click should show the window
|
||||
if button == MouseButton::Left {
|
||||
println!("Tray: Left click detected, sending ShowWindow message");
|
||||
// Get the global sender
|
||||
if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() {
|
||||
if let Err(e) = tx.unbounded_send(Message::ShowWindow) {
|
||||
eprintln!("Tray: Failed to send ShowWindow message via handler: {}", e);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Tray: Global sender not initialized yet for click handler");
|
||||
}
|
||||
}
|
||||
},
|
||||
// Handle double-click events
|
||||
TrayIconEvent::DoubleClick { button, .. } => {
|
||||
println!("Tray: Double click detected with button: {:?}", button);
|
||||
// Double left click should also show the window
|
||||
if button == MouseButton::Left {
|
||||
println!("Tray: Double left click detected, sending ShowWindow message");
|
||||
// Get the global sender
|
||||
if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() {
|
||||
if let Err(e) = tx.unbounded_send(Message::ShowWindow) {
|
||||
eprintln!("Tray: Failed to send ShowWindow message via handler on double click: {}", e);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Tray: Global sender not initialized yet for double click handler");
|
||||
}
|
||||
}
|
||||
},
|
||||
// Silently ignore mouse movement events
|
||||
TrayIconEvent::Move { .. } => {
|
||||
// Don't log anything for mouse movements
|
||||
},
|
||||
// Silently ignore mouse enter/leave events
|
||||
TrayIconEvent::Enter { .. } | TrayIconEvent::Leave { .. } => {
|
||||
// Don't log anything for mouse enter/leave
|
||||
},
|
||||
// Log other events for debugging (but not mouse-related ones)
|
||||
_ => {
|
||||
println!("Tray: Event received: {:?}", event);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
println!("Tray setup complete using set_event_handler for clicks."); // Updated log
|
||||
Some(tray) // Return the TrayIcon object
|
||||
}
|
||||
Reference in New Issue
Block a user