Reorganize codebase into modular structure
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
// UI module
|
||||
// Contains UI components and rendering logic
|
||||
|
||||
pub mod view;
|
||||
pub mod tray;
|
||||
|
||||
pub use view::view_app;
|
||||
+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
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// UI view components
|
||||
|
||||
use iced::{
|
||||
widget::{button, text, Column, Row, container, image as iced_image},
|
||||
Element, Length,
|
||||
};
|
||||
|
||||
use crate::app::{CursorRush, Message};
|
||||
|
||||
// View function to render the UI
|
||||
pub fn view_app(state: &CursorRush) -> Element<Message> {
|
||||
let mut content = Column::new()
|
||||
.spacing(16)
|
||||
.push(text("Cursor Rush").size(24));
|
||||
|
||||
// Add a button for each cursor
|
||||
if !state.cursors.is_empty() {
|
||||
let current_cursor = &state.cursors[state.current_index];
|
||||
content = content.push(text(format!("Current cursor: {}", current_cursor.name)));
|
||||
|
||||
// Display the current cursor image as a preview
|
||||
let img_handle = iced_image::Handle::from_path(¤t_cursor.path);
|
||||
let cursor_image = iced_image::Image::new(img_handle)
|
||||
.width(Length::Fixed(64.0))
|
||||
.height(Length::Fixed(64.0));
|
||||
|
||||
content = content.push(cursor_image);
|
||||
|
||||
// Create a row of cursor buttons
|
||||
let mut cursor_row = Row::new().spacing(10);
|
||||
|
||||
for (i, cursor) in state.cursors.iter().enumerate() {
|
||||
// Create a small preview image for each cursor
|
||||
let preview_handle = iced_image::Handle::from_path(&cursor.path);
|
||||
let preview_image = iced_image::Image::new(preview_handle)
|
||||
.width(Length::Fixed(32.0))
|
||||
.height(Length::Fixed(32.0));
|
||||
|
||||
let btn = button(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(preview_image)
|
||||
.push(text(&cursor.name).size(16))
|
||||
)
|
||||
.on_press(Message::SelectCursor(i));
|
||||
|
||||
cursor_row = cursor_row.push(btn);
|
||||
}
|
||||
|
||||
content = content
|
||||
.push(cursor_row)
|
||||
.push(button("Next cursor").on_press(Message::NextCursor))
|
||||
.push(button("Quit").on_press(Message::Quit));
|
||||
|
||||
// Add instructions for the hotkey and close button behavior
|
||||
content = content.push(
|
||||
container(
|
||||
Column::new()
|
||||
.spacing(5)
|
||||
.push(text("Press Ctrl+Shift+F12 to exit the application").size(16))
|
||||
.push(text("Close button (X) minimizes to system tray").size(16))
|
||||
)
|
||||
.padding(10)
|
||||
);
|
||||
} else {
|
||||
content = content.push(text("No cursors found in assets directory"));
|
||||
}
|
||||
|
||||
// Create the main container
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.padding(20)
|
||||
.into()
|
||||
}
|
||||
Reference in New Issue
Block a user