diff --git a/src/app/cursor.rs b/src/app/cursor.rs index 29f7811..9e4a08d 100644 --- a/src/app/cursor.rs +++ b/src/app/cursor.rs @@ -12,6 +12,9 @@ use crate::platform; pub struct CursorInfo { pub name: String, pub path: PathBuf, + pub image: Option<::image::RgbaImage>, + #[cfg(windows)] + pub win_cursor: Option, } // Function to load all PNG files from the assets directory @@ -36,9 +39,25 @@ pub fn load_cursor_files() -> Vec { .to_string(); println!("Adding cursor: {} at path {}", name, path.display()); + + // Try to load the image during initialization + let image = match image_open(path) { + Ok(img) => { + println!("Successfully pre-loaded image for cursor: {}", name); + Some(img.to_rgba8()) + }, + Err(e) => { + eprintln!("Failed to pre-load image for cursor {}: {}", name, e); + None + } + }; + cursors.push(CursorInfo { name, path: path.to_path_buf(), + image, + #[cfg(windows)] + win_cursor: None, // Windows cursor will be created on first use }); } } @@ -72,17 +91,21 @@ pub fn validate_cursor_image(path: &Path) -> bool { } } -// Apply the cursor at the given path -pub fn apply_cursor(path: &Path) -> bool { - // Validate the cursor image - if validate_cursor_image(path) { - // Use platform-specific cursor image update (not hiding system cursor again) - if platform::update_cursor_image(path) { - println!("Successfully updated cursor image"); - return true; - } else { - eprintln!("Failed to update cursor image"); - } +// Apply the cursor from CursorInfo +pub fn apply_cursor_info(cursor_info: &mut CursorInfo) -> bool { + // Validate the cursor image if not already cached + if cursor_info.image.is_none() && !validate_cursor_image(&cursor_info.path) { + return false; + } + + // Use platform-specific cursor image update with the cursor info + if platform::update_cursor_info(cursor_info) { + println!("Successfully updated cursor image: {}", cursor_info.name); + return true; + } else { + eprintln!("Failed to update cursor image: {}", cursor_info.name); + return false; } - false } + + diff --git a/src/app/mod.rs b/src/app/mod.rs index f335da2..ab255e7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,7 +7,7 @@ mod cursor; pub use state::CursorRush; pub use messages::Message; -pub use cursor::{CursorInfo, load_cursor_files, apply_cursor}; +pub use cursor::{CursorInfo, load_cursor_files, apply_cursor_info}; pub use state::SharedState; use iced::{Subscription, Task}; diff --git a/src/app/state.rs b/src/app/state.rs index a34b57b..7b87cd9 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -22,20 +22,16 @@ pub fn update_app(state: &mut CursorRush, message: Message) -> Task { state.current_index = (state.current_index + 1) % state.cursors.len(); // Apply the new cursor - if let Some(cursor) = state.cursors.get(state.current_index) { - apply_cursor(&cursor.path); - - // Load the new cursor image - let current_cursor_image = match image::open(&cursor.path) { - Ok(img) => Some(img.to_rgba8()), - Err(_) => None, - }; + if let Some(cursor) = state.cursors.get_mut(state.current_index) { + // Use the cached version with the cursor info + apply_cursor_info(cursor); // Update shared state if let Some(shared) = &state.shared_state { if let Ok(mut shared_state) = shared.lock() { shared_state.current_index = state.current_index; - shared_state.current_cursor_image = current_cursor_image; + // Use the cached image directly + shared_state.current_cursor_image = cursor.image.clone(); } } } @@ -47,20 +43,16 @@ pub fn update_app(state: &mut CursorRush, message: Message) -> Task { state.current_index = idx; // Apply the selected cursor - if let Some(cursor) = state.cursors.get(state.current_index) { - apply_cursor(&cursor.path); - - // Load the new cursor image - let current_cursor_image = match image_open(&cursor.path) { - Ok(img) => Some(img.to_rgba8()), - Err(_) => None, - }; + if let Some(cursor) = state.cursors.get_mut(state.current_index) { + // Use the cached version with the cursor info + apply_cursor_info(cursor); // Update shared state if let Some(shared) = &state.shared_state { if let Ok(mut shared_state) = shared.lock() { shared_state.current_index = state.current_index; - shared_state.current_cursor_image = current_cursor_image; + // Use the cached image directly + shared_state.current_cursor_image = cursor.image.clone(); } } } @@ -212,7 +204,7 @@ pub fn update_app(state: &mut CursorRush, message: Message) -> Task { use crate::platform; use crate::ui::tray::setup_tray_icon; -use crate::app::{Message, CursorInfo, load_cursor_files, apply_cursor, TRAY_SENDER}; +use crate::app::{Message, CursorInfo, load_cursor_files, apply_cursor_info, TRAY_SENDER}; // Shared state between the UI and the tray icon pub struct SharedState { @@ -313,7 +305,7 @@ pub fn app_subscription(_state: &CursorRush) -> Subscription { // Boot function implementation pub fn boot_app() -> (CursorRush, Task) { // Load cursor files from assets directory - let cursors = load_cursor_files(); + let mut cursors = load_cursor_files(); if cursors.is_empty() { eprintln!("No cursor PNG files found in assets directory!"); @@ -336,8 +328,8 @@ pub fn boot_app() -> (CursorRush, Task) { } // Apply the first cursor by default - if let Some(cursor) = cursors.first() { - apply_cursor(&cursor.path); + if let Some(cursor) = cursors.first_mut() { + apply_cursor_info(cursor); } // Create shared state diff --git a/src/platform/linux/cursor.rs b/src/platform/linux/cursor.rs index bf55e0f..54630f5 100644 --- a/src/platform/linux/cursor.rs +++ b/src/platform/linux/cursor.rs @@ -1,25 +1,28 @@ // Linux cursor management use std::path::Path; +use crate::platform::CursorInfo; // Hide the system cursor pub fn hide_system_cursor() -> bool { - println!("Linux system cursor hiding not yet implemented"); + println!("Linux system cursor hiding not yet implemented"); false } -// Update the cursor image -pub fn update_cursor_image(_path: &Path) -> bool { - println!("Linux cursor image update not yet implemented"); +// Update the cursor image using CursorInfo (with caching) +pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { + println!("Linux cursor image update with caching not yet implemented"); false } + + // Restore the original cursor pub fn restore_cursor() { println!("Linux cursor restoration not yet implemented"); } // Render the cursor -pub fn render_cursor() { +pub fn render_cursor() { // No-op } diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index c53fca9..34b272c 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -4,4 +4,4 @@ mod cursor; mod state; // Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_image, restore_cursor, render_cursor}; +pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/macos/cursor.rs b/src/platform/macos/cursor.rs index 3bf5e67..0b1ea28 100644 --- a/src/platform/macos/cursor.rs +++ b/src/platform/macos/cursor.rs @@ -1,25 +1,28 @@ // macOS cursor management use std::path::Path; +use crate::platform::CursorInfo; // Hide the system cursor pub fn hide_system_cursor() -> bool { - println!("macOS system cursor hiding not yet implemented"); + println!("macOS system cursor hiding not yet implemented"); false } -// Update the cursor image -pub fn update_cursor_image(_path: &Path) -> bool { - println!("macOS cursor image update not yet implemented"); +// Update the cursor image using CursorInfo (with caching) +pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { + println!("macOS cursor image update with caching not yet implemented"); false } + + // Restore the original cursor pub fn restore_cursor() { println!("macOS cursor restoration not yet implemented"); } // Render the cursor -pub fn render_cursor() { +pub fn render_cursor() { // No-op } diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 96930b1..d35a84d 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -4,4 +4,4 @@ mod cursor; mod state; // Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_image, restore_cursor, render_cursor}; +pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 873c902..95f1057 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -16,18 +16,37 @@ mod linux; #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] mod unsupported; +// --- Platform-independent types --- + +// Define a cross-platform cursor handle type +#[cfg(windows)] +pub use windows::types::SyncHCURSOR; + +#[cfg(not(windows))] +#[derive(Copy, Clone, Debug)] +pub struct SyncHCURSOR(pub usize); + +#[cfg(not(windows))] +unsafe impl Send for SyncHCURSOR {} + +#[cfg(not(windows))] +unsafe impl Sync for SyncHCURSOR {} + // --- Public Exports --- // Export the platform-specific functions #[cfg(windows)] -pub use windows::{hide_system_cursor, update_cursor_image, restore_cursor}; +pub use windows::{hide_system_cursor, update_cursor_info, restore_cursor}; + +// Re-export CursorInfo for platform-specific implementations +pub use crate::app::CursorInfo; #[cfg(target_os = "macos")] -pub use macos::{hide_system_cursor, update_cursor_image, restore_cursor, render_cursor}; +pub use macos::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; #[cfg(target_os = "linux")] -pub use linux::{hide_system_cursor, update_cursor_image, restore_cursor, render_cursor}; +pub use linux::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; // Default implementation for other platforms #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] -pub use unsupported::{hide_system_cursor, update_cursor_image, restore_cursor, render_cursor}; +pub use unsupported::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/unsupported/cursor.rs b/src/platform/unsupported/cursor.rs index ce537ea..365f401 100644 --- a/src/platform/unsupported/cursor.rs +++ b/src/platform/unsupported/cursor.rs @@ -1,25 +1,28 @@ // Unsupported platform cursor management use std::path::Path; +use crate::platform::CursorInfo; // Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("System cursor hiding not supported"); - false +pub fn hide_system_cursor() -> bool { + println!("System cursor hiding not supported"); + false } -// Update the cursor image -pub fn update_cursor_image(_path: &Path) -> bool { - println!("Cursor image update not supported"); - false +// Update the cursor image using CursorInfo (with caching) +pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { + println!("Cursor image update with caching not supported"); + false } + + // Restore the original cursor -pub fn restore_cursor() { - println!("Cursor restoration not supported"); +pub fn restore_cursor() { + println!("Cursor restoration not supported"); } // Render the cursor -pub fn render_cursor() { +pub fn render_cursor() { // No-op } diff --git a/src/platform/unsupported/mod.rs b/src/platform/unsupported/mod.rs index 3b9f514..70332b5 100644 --- a/src/platform/unsupported/mod.rs +++ b/src/platform/unsupported/mod.rs @@ -4,4 +4,4 @@ mod cursor; mod state; // Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_image, restore_cursor, render_cursor}; +pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/windows/cursor.rs b/src/platform/windows/cursor.rs index 84c1bb3..fa37eb7 100644 --- a/src/platform/windows/cursor.rs +++ b/src/platform/windows/cursor.rs @@ -1,6 +1,5 @@ // Windows platform cursor management -use std::path::Path; use std::ptr::null_mut; use std::sync::atomic::Ordering; use winapi::{ @@ -290,21 +289,48 @@ pub fn hide_system_cursor() -> bool { true } -// Update the cursor image - creates and sets a custom cursor for all system cursors -pub fn update_cursor_image(path: &Path) -> bool { - // Load the image - let image = match image_open(path) { - Ok(img) => img.to_rgba8(), - Err(e) => { - eprintln!("Failed to load cursor image: {}", e); - return false; +// Update the cursor image using CursorInfo (with caching) +pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool { + // Use cached image if available, otherwise load from disk + let image = if let Some(ref cached_image) = cursor_info.image { + println!("Using cached image for cursor: {}", cursor_info.name); + cached_image.clone() + } else { + println!("Loading image from disk for cursor: {}", cursor_info.name); + match image_open(&cursor_info.path) { + Ok(img) => { + let rgba = img.to_rgba8(); + // Cache the image for future use + cursor_info.image = Some(rgba.clone()); + rgba + }, + Err(e) => { + eprintln!("Failed to load cursor image: {}", e); + return false; + } } }; - // Store the image for reference + // Store the image in the global state for reference *CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone()); unsafe { + // Use cached Windows cursor if available + #[cfg(windows)] + if let Some(cached_cursor) = cursor_info.win_cursor { + println!("Using cached Windows cursor handle for: {}", cursor_info.name); + + // Apply the cached cursor to all system cursors + let success = apply_cursor_to_all_system_cursors(cached_cursor.0); + if !success { + eprintln!("Failed to apply cached cursor to all system cursors"); + // Even if we failed to set all system cursors, try to at least set the current cursor + SetCursor(cached_cursor.0); + } + + return true; + } + // Clean up old cursor if it exists let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap(); if let Some(cursor_wrapper) = cursor_guard.take() { @@ -316,6 +342,13 @@ pub fn update_cursor_image(path: &Path) -> bool { // Store the cursor for later use *cursor_guard = Some(cursor_wrapper); + // Cache the Windows cursor handle in the CursorInfo + #[cfg(windows)] + { + println!("Caching Windows cursor handle for: {}", cursor_info.name); + cursor_info.win_cursor = Some(cursor_wrapper); + } + // Apply the custom cursor to all system cursors let success = apply_cursor_to_all_system_cursors(cursor_wrapper.0); if !success { @@ -332,6 +365,8 @@ pub fn update_cursor_image(path: &Path) -> bool { } } + + // Apply a cursor to all system cursors unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool { let mut success = true; diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 2130cc0..534fbc7 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1,8 +1,8 @@ // Windows platform implementation module -mod types; +pub mod types; mod cursor; mod state; // Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_image, restore_cursor}; +pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor}; diff --git a/src/ui/tray.rs b/src/ui/tray.rs index 2707b26..c772b84 100644 --- a/src/ui/tray.rs +++ b/src/ui/tray.rs @@ -83,10 +83,12 @@ pub fn setup_tray_icon( 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) { + let current_index = state.current_index; + if let Some(cursor) = state.cursors.get_mut(current_index) { println!("Tray: Updating cursor image: {}", cursor.name); - crate::app::apply_cursor(&cursor.path); - state.current_cursor_image = image_open(&cursor.path).ok().map(|img| img.to_rgba8()); + crate::app::apply_cursor_info(cursor); + // Use the cached image directly + state.current_cursor_image = cursor.image.clone(); } } },