From b3d85106a82f6f70ca392862a5e2e477d6210356 Mon Sep 17 00:00:00 2001 From: JobyGitGud Date: Sun, 11 May 2025 18:37:23 -0500 Subject: [PATCH] Implement cursor asset caching for improved performance Key improvements: 1. Add image caching to CursorInfo struct to avoid repeated disk reads and decoding 2. Implement Windows cursor handle caching to minimize GDI resource allocation 3. Update all code paths to use cached resources for better performance 4. Remove deprecated/unused functions for cleaner codebase This change significantly reduces CPU usage and improves cursor switching performance. --- src/app/cursor.rs | 47 ++++++++++++++++++------- src/app/mod.rs | 2 +- src/app/state.rs | 36 ++++++++----------- src/platform/linux/cursor.rs | 13 ++++--- src/platform/linux/mod.rs | 2 +- src/platform/macos/cursor.rs | 13 ++++--- src/platform/macos/mod.rs | 2 +- src/platform/mod.rs | 27 ++++++++++++--- src/platform/unsupported/cursor.rs | 23 +++++++------ src/platform/unsupported/mod.rs | 2 +- src/platform/windows/cursor.rs | 55 ++++++++++++++++++++++++------ src/platform/windows/mod.rs | 4 +-- src/ui/tray.rs | 8 +++-- 13 files changed, 157 insertions(+), 77 deletions(-) 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(); } } },