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:
+35
-12
@@ -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<crate::platform::SyncHCURSOR>,
|
||||
}
|
||||
|
||||
// Function to load all PNG files from the assets directory
|
||||
@@ -36,9 +39,25 @@ pub fn load_cursor_files() -> Vec<CursorInfo> {
|
||||
.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
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -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};
|
||||
|
||||
+14
-22
@@ -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();
|
||||
|
||||
// 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<Message> {
|
||||
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<Message> {
|
||||
|
||||
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<Message> {
|
||||
// Boot function implementation
|
||||
pub fn boot_app() -> (CursorRush, Task<Message>) {
|
||||
// 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<Message>) {
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
+23
-4
@@ -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};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
+5
-3
@@ -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();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user