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.
This commit is contained in:
2025-05-11 18:37:23 -05:00
parent 34b0d700a6
commit b3d85106a8
13 changed files with 157 additions and 77 deletions
+33 -10
View File
@@ -12,6 +12,9 @@ use crate::platform;
pub struct CursorInfo { pub struct CursorInfo {
pub name: String, pub name: String,
pub path: PathBuf, pub path: PathBuf,
pub image: Option<::image::RgbaImage>,
#[cfg(windows)]
pub win_cursor: Option<crate::platform::SyncHCURSOR>,
} }
// Function to load all PNG files from the assets directory // Function to load all PNG files from the assets directory
@@ -36,9 +39,25 @@ pub fn load_cursor_files() -> Vec<CursorInfo> {
.to_string(); .to_string();
println!("Adding cursor: {} at path {}", name, path.display()); 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 { cursors.push(CursorInfo {
name, name,
path: path.to_path_buf(), 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 // Apply the cursor from CursorInfo
pub fn apply_cursor(path: &Path) -> bool { pub fn apply_cursor_info(cursor_info: &mut CursorInfo) -> bool {
// Validate the cursor image // Validate the cursor image if not already cached
if validate_cursor_image(path) { if cursor_info.image.is_none() && !validate_cursor_image(&cursor_info.path) {
// Use platform-specific cursor image update (not hiding system cursor again) return false;
if platform::update_cursor_image(path) { }
println!("Successfully updated cursor image");
// 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; return true;
} else { } else {
eprintln!("Failed to update cursor image"); eprintln!("Failed to update cursor image: {}", cursor_info.name);
return false;
} }
}
false
} }
+1 -1
View File
@@ -7,7 +7,7 @@ mod cursor;
pub use state::CursorRush; pub use state::CursorRush;
pub use messages::Message; 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; pub use state::SharedState;
use iced::{Subscription, Task}; use iced::{Subscription, Task};
+14 -22
View File
@@ -22,20 +22,16 @@ pub fn update_app(state: &mut CursorRush, message: Message) -> Task<Message> {
state.current_index = (state.current_index + 1) % state.cursors.len(); state.current_index = (state.current_index + 1) % state.cursors.len();
// Apply the new cursor // Apply the new cursor
if let Some(cursor) = state.cursors.get(state.current_index) { if let Some(cursor) = state.cursors.get_mut(state.current_index) {
apply_cursor(&cursor.path); // Use the cached version with the cursor info
apply_cursor_info(cursor);
// Load the new cursor image
let current_cursor_image = match image::open(&cursor.path) {
Ok(img) => Some(img.to_rgba8()),
Err(_) => None,
};
// Update shared state // Update shared state
if let Some(shared) = &state.shared_state { if let Some(shared) = &state.shared_state {
if let Ok(mut shared_state) = shared.lock() { if let Ok(mut shared_state) = shared.lock() {
shared_state.current_index = state.current_index; 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<Message> {
state.current_index = idx; state.current_index = idx;
// Apply the selected cursor // Apply the selected cursor
if let Some(cursor) = state.cursors.get(state.current_index) { if let Some(cursor) = state.cursors.get_mut(state.current_index) {
apply_cursor(&cursor.path); // Use the cached version with the cursor info
apply_cursor_info(cursor);
// Load the new cursor image
let current_cursor_image = match image_open(&cursor.path) {
Ok(img) => Some(img.to_rgba8()),
Err(_) => None,
};
// Update shared state // Update shared state
if let Some(shared) = &state.shared_state { if let Some(shared) = &state.shared_state {
if let Ok(mut shared_state) = shared.lock() { if let Ok(mut shared_state) = shared.lock() {
shared_state.current_index = state.current_index; 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<Message> {
use crate::platform; use crate::platform;
use crate::ui::tray::setup_tray_icon; 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 // Shared state between the UI and the tray icon
pub struct SharedState { pub struct SharedState {
@@ -313,7 +305,7 @@ pub fn app_subscription(_state: &CursorRush) -> Subscription<Message> {
// Boot function implementation // Boot function implementation
pub fn boot_app() -> (CursorRush, Task<Message>) { pub fn boot_app() -> (CursorRush, Task<Message>) {
// Load cursor files from assets directory // Load cursor files from assets directory
let cursors = load_cursor_files(); let mut cursors = load_cursor_files();
if cursors.is_empty() { if cursors.is_empty() {
eprintln!("No cursor PNG files found in assets directory!"); eprintln!("No cursor PNG files found in assets directory!");
@@ -336,8 +328,8 @@ pub fn boot_app() -> (CursorRush, Task<Message>) {
} }
// Apply the first cursor by default // Apply the first cursor by default
if let Some(cursor) = cursors.first() { if let Some(cursor) = cursors.first_mut() {
apply_cursor(&cursor.path); apply_cursor_info(cursor);
} }
// Create shared state // Create shared state
+6 -3
View File
@@ -1,6 +1,7 @@
// Linux cursor management // Linux cursor management
use std::path::Path; use std::path::Path;
use crate::platform::CursorInfo;
// Hide the system cursor // Hide the system cursor
pub fn hide_system_cursor() -> bool { pub fn hide_system_cursor() -> bool {
@@ -8,12 +9,14 @@ pub fn hide_system_cursor() -> bool {
false false
} }
// Update the cursor image // Update the cursor image using CursorInfo (with caching)
pub fn update_cursor_image(_path: &Path) -> bool { pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool {
println!("Linux cursor image update not yet implemented"); println!("Linux cursor image update with caching not yet implemented");
false false
} }
// Restore the original cursor // Restore the original cursor
pub fn restore_cursor() { pub fn restore_cursor() {
println!("Linux cursor restoration not yet implemented"); println!("Linux cursor restoration not yet implemented");
+1 -1
View File
@@ -4,4 +4,4 @@ mod cursor;
mod state; mod state;
// Re-export the public API // 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};
+6 -3
View File
@@ -1,6 +1,7 @@
// macOS cursor management // macOS cursor management
use std::path::Path; use std::path::Path;
use crate::platform::CursorInfo;
// Hide the system cursor // Hide the system cursor
pub fn hide_system_cursor() -> bool { pub fn hide_system_cursor() -> bool {
@@ -8,12 +9,14 @@ pub fn hide_system_cursor() -> bool {
false false
} }
// Update the cursor image // Update the cursor image using CursorInfo (with caching)
pub fn update_cursor_image(_path: &Path) -> bool { pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool {
println!("macOS cursor image update not yet implemented"); println!("macOS cursor image update with caching not yet implemented");
false false
} }
// Restore the original cursor // Restore the original cursor
pub fn restore_cursor() { pub fn restore_cursor() {
println!("macOS cursor restoration not yet implemented"); println!("macOS cursor restoration not yet implemented");
+1 -1
View File
@@ -4,4 +4,4 @@ mod cursor;
mod state; mod state;
// Re-export the public API // 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};
+23 -4
View File
@@ -16,18 +16,37 @@ mod linux;
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
mod unsupported; 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 --- // --- Public Exports ---
// Export the platform-specific functions // Export the platform-specific functions
#[cfg(windows)] #[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")] #[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")] #[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 // Default implementation for other platforms
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] #[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};
+6 -3
View File
@@ -1,6 +1,7 @@
// Unsupported platform cursor management // Unsupported platform cursor management
use std::path::Path; use std::path::Path;
use crate::platform::CursorInfo;
// Hide the system cursor // Hide the system cursor
pub fn hide_system_cursor() -> bool { pub fn hide_system_cursor() -> bool {
@@ -8,12 +9,14 @@ pub fn hide_system_cursor() -> bool {
false false
} }
// Update the cursor image // Update the cursor image using CursorInfo (with caching)
pub fn update_cursor_image(_path: &Path) -> bool { pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool {
println!("Cursor image update not supported"); println!("Cursor image update with caching not supported");
false false
} }
// Restore the original cursor // Restore the original cursor
pub fn restore_cursor() { pub fn restore_cursor() {
println!("Cursor restoration not supported"); println!("Cursor restoration not supported");
+1 -1
View File
@@ -4,4 +4,4 @@ mod cursor;
mod state; mod state;
// Re-export the public API // 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};
+42 -7
View File
@@ -1,6 +1,5 @@
// Windows platform cursor management // Windows platform cursor management
use std::path::Path;
use std::ptr::null_mut; use std::ptr::null_mut;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use winapi::{ use winapi::{
@@ -290,21 +289,48 @@ pub fn hide_system_cursor() -> bool {
true true
} }
// Update the cursor image - creates and sets a custom cursor for all system cursors // Update the cursor image using CursorInfo (with caching)
pub fn update_cursor_image(path: &Path) -> bool { pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool {
// Load the image // Use cached image if available, otherwise load from disk
let image = match image_open(path) { let image = if let Some(ref cached_image) = cursor_info.image {
Ok(img) => img.to_rgba8(), 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) => { Err(e) => {
eprintln!("Failed to load cursor image: {}", e); eprintln!("Failed to load cursor image: {}", e);
return false; return false;
} }
}
}; };
// Store the image for reference // Store the image in the global state for reference
*CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone()); *CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone());
unsafe { 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 // Clean up old cursor if it exists
let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap(); let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap();
if let Some(cursor_wrapper) = cursor_guard.take() { 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 // Store the cursor for later use
*cursor_guard = Some(cursor_wrapper); *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 // Apply the custom cursor to all system cursors
let success = apply_cursor_to_all_system_cursors(cursor_wrapper.0); let success = apply_cursor_to_all_system_cursors(cursor_wrapper.0);
if !success { if !success {
@@ -332,6 +365,8 @@ pub fn update_cursor_image(path: &Path) -> bool {
} }
} }
// Apply a cursor to all system cursors // Apply a cursor to all system cursors
unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool { unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool {
let mut success = true; let mut success = true;
+2 -2
View File
@@ -1,8 +1,8 @@
// Windows platform implementation module // Windows platform implementation module
mod types; pub mod types;
mod cursor; mod cursor;
mod state; mod state;
// Re-export the public API // 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};
+5 -3
View File
@@ -83,10 +83,12 @@ pub fn setup_tray_icon(
Ok(mut state) => { Ok(mut state) => {
if !state.cursors.is_empty() { if !state.cursors.is_empty() {
state.current_index = (state.current_index + 1) % state.cursors.len(); 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); println!("Tray: Updating cursor image: {}", cursor.name);
crate::app::apply_cursor(&cursor.path); crate::app::apply_cursor_info(cursor);
state.current_cursor_image = image_open(&cursor.path).ok().map(|img| img.to_rgba8()); // Use the cached image directly
state.current_cursor_image = cursor.image.clone();
} }
} }
}, },