25 Commits
Author SHA1 Message Date
Joby aa039c7291 I just needed to do something so I felt useful today
- Eliminated duplicate platform stubs: Created shared macro for macOS/Linux implementations
- Extracted cursor restoration logic: Shared helper function for Windows API calls
- Refactored cursor application: Helper function for copy/apply/error handling pattern
- Reduced ~70 lines of duplicate code while maintaining all functionality
2025-06-09 20:09:24 -05:00
jcweaver997 73634440cf added fire circle cursor 2025-05-31 16:08:00 -05:00
jcweaver997 eced2a561c added basic animation 2025-05-31 15:36:20 -05:00
jcweaver997 61157a2402 bundle cursors into cursor_rush.dat, started animator 2025-05-31 00:38:29 -05:00
Joby 67ee3dc8d1 Update README with current versions and accurate info 2025-05-30 23:26:41 -05:00
Joby 7016e96673 Refactor large functions into smaller components 2025-05-30 23:20:39 -05:00
Joby 09374eecb3 Replace mutex unwraps with proper error handling 2025-05-30 23:10:43 -05:00
Joby 4f71789710 Clean up debug logging spam
- Remove ~101 useless debug print statements from production code
- Remove verbose resource debugging and repetitive status messages
- Significantly reduce console noise while maintaining error visibility
2025-05-30 22:59:25 -05:00
Joby a9976cbbe5 Fix unsafe thread-safety docs for cursor handles 2025-05-30 22:38:50 -05:00
Joby bba65f2f97 refactor: eliminate code duplication in cursor operations and cleanup 2025-05-30 22:29:24 -05:00
Joby c1b8160f4a refactor: eliminate code duplication in cursor operations and cleanup
- Extract cursor application logic into apply_cursor_by_index() helper
- Extract cleanup logic into perform_app_cleanup() helper
- Update select_cursor(), next_cursor(), and system tray handlers to use helpers
- Reduce ~55 lines of duplicated code while maintaining functionality
- Mark step 1 of code cleanup TODO as completed
2025-05-30 22:24:33 -05:00
Joby 16fc8949b3 Fixed window dragging for main menu 2025-05-30 21:20:33 -05:00
jcweaver997 b31aefe896 fix steam integration 2025-05-30 19:28:34 -05:00
Joby f60f058cf9 Merge frontend-integration into master, making it the new standard 2025-05-17 21:11:47 -05:00
Joby 9f733ed39b Integrate Tauri frontend with categorization system and Steam integration 2025-05-17 21:07:31 -05:00
Joby 0133b61944 updated readme with run directions 2025-05-17 17:25:50 -05:00
Hemeleh 20592954f7 tauri integration 2025-05-16 17:44:04 +02:00
Hemeleh 06b2f0ffaf tauri integration 2025-05-16 17:38:33 +02:00
Joby 2e71cd8b9e Updated README 2025-05-11 18:52:52 -05:00
Joby a92ef1e07c Fix cursor swapping issues and minimize cursor flashing 2025-05-11 18:48:04 -05:00
Joby b3d85106a8 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.
2025-05-11 18:37:23 -05:00
Joby 34b0d700a6 Replace busy-wait hotkey listener with efficient event-driven approach 2025-05-11 18:21:50 -05:00
Joby 0e6841bd8a Clean up dead code and fix warnings while preserving Steam drop functionality and cross-platform skeleton code 2025-05-11 18:11:58 -05:00
Joby 9f6f3442dc unclean implimentation of windows cursor set as image 2025-05-10 23:42:25 -05:00
Joby a8d8d2ea0b Fixed close hotkey not working so it works now 2025-05-09 22:10:03 -05:00
170 changed files with 8039 additions and 1997 deletions
+1
View File
@@ -1,3 +1,4 @@
/target /target
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
CODE_CLEANUP_TODO.md
+3
View File
@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}
+3 -26
View File
@@ -1,27 +1,4 @@
[package]
name = "cursor_rush"
version = "0.1.0"
edition = "2021"
[dependencies] [workspace]
iced = { version = "0.13.1", default-features = true, features = ["image", "advanced"] } # GUI, bundles wgpu + winit members = ["frontend/src-tauri"]
tray-icon = "0.20.1" # cross‑platform system tray resolver = "3"
image = "0.24.7" # Image processing
walkdir = "2.4.0" # Directory traversal
global-hotkey = "0.4.2" # Global hotkey registration
lazy_static = "1.4.0" # Thread-safe static initialization
steamworks = { version = "0.11.0", features = ["raw-bindings"] }
anyhow = "1.0.98"
once_cell = "1.18.0" # Thread-safe lazy initialized global variables
# Platform-specific dependencies
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["winuser", "wingdi", "consoleapi"] } # Windows API
ctor = "0.2.4" # Constructor/destructor attributes
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7" # Objective-C bindings
cocoa = "0.24.1" # macOS Cocoa bindings
[target.'cfg(target_os = "linux")'.dependencies]
x11 = { version = "2.21.0", features = ["xlib"] } # X11 bindings
+146 -45
View File
@@ -6,78 +6,179 @@ A cross-platform application for customizing your cursor with gameified logic.
- Replace the system cursor with custom PNG images - Replace the system cursor with custom PNG images
- Minimize to system tray for background operation - Minimize to system tray for background operation
- Cross-platform support (Windows, macOS, Linux) - Windows support (macOS and Linux support in development)
- Steam integration - Optimized rendering with low CPU usage
- System tray integration with context menu
- Global hotkey support
- Steam integration for item drops
- Tauri-based frontend with React
## Project Structure ## Project Structure
``` ```
src/ frontend/
├── app/ # Application core logic ├── src/ # React frontend code
│ ├── mod.rs # Main app module exports │ ├── App.jsx # Main React component
│ ├── state.rs # Application state management │ ├── components/ # UI components
│ ├── messages.rs # Message definitions │ └── cursors/ # Cursor image files
│ └── cursor.rs # Cursor-related functionality ├── src-tauri/ # Rust backend code
├── ui/ # User interface components │ ├── src/ # Rust source code
│ ├── mod.rs # UI module exports │ │ ├── lib.rs # Main library code
│ ├── view.rs # View rendering logic │ │ ├── main.rs # Application entry point
│ └── tray.rs # System tray functionality │ │ ├── integrations/ # External integrations
├── platform/ # Platform-specific implementations │ │ │ ├── mod.rs # Integrations module exports
│ ├── mod.rs # Platform module exports │ │ │ └── steam.rs # Steam integration
│ ├── windows/ # Windows-specific code │ │ └── platform/ # Platform-specific code
│ │ ├── mod.rs # Windows module exports │ │ ├── mod.rs # Platform module exports
│ │ ├── types.rs # Windows type definitions │ │ ├── types.rs # Common type definitions
│ │ ├── state.rs # Windows state management │ │ ├── windows/ # Windows-specific code
│ │ ├── cursor.rs # Windows cursor handling │ │ ├── macos/ # macOS-specific code
│ │ ├── window.rs # Windows window management │ │ └── linux/ # Linux-specific code
│ │ └── hook.rs # Windows mouse hook handling │ └── Cargo.toml # Rust dependencies
│ ├── macos.rs # macOS-specific code └── package.json # npm dependencies
│ ├── linux.rs # Linux-specific code
│ └── unsupported.rs # Fallback for unsupported platforms
├── integrations/ # External integrations
│ ├── mod.rs # Integrations module exports
│ └── steam.rs # Steam integration
├── utils/ # Utility functions and helpers
│ ├── mod.rs # Utils module exports
│ ├── config.rs # Configuration management
│ └── logging.rs # Logging utilities
└── main.rs # Application entry point
``` ```
## Building ## Building
### Prerequisites ### Prerequisites
- Rust 1.70.0 or later - Rust 1.56.0 or later (tested with 1.86.0)
- Cargo - Cargo
- Node.js 16.0.0 or later (tested with 20.11.0)
- npm 7.0.0 or later (tested with 10.2.4)
- Platform-specific dependencies:
- Windows: Visual Studio Build Tools (fully supported)
- macOS: Development in progress
- Linux: Development in progress
### Dependencies
The application uses the following major dependencies:
- Tauri 1.8.3 - Cross-platform framework
- React 19.1.0 - Frontend framework
- image 0.24 - Image processing
- winapi 0.3.9 - Windows API bindings
- once_cell 1.19.0 - Lazy initialization
- steamworks 0.11.0 - Steam integration
- parking_lot 0.12.3 - High-performance synchronization
### Build Commands ### Build Commands
```bash ```bash
# Debug build # Navigate to the frontend directory
cargo build cd frontend
# Release build # Install npm dependencies
cargo build --release npm install
# Run in development mode (starts both frontend and backend)
npm run tauri dev
# Build for production
npm run tauri build
# Alternative: Run frontend and backend separately for debugging
# Terminal 1: npm run dev (starts Vite dev server)
# Terminal 2: npm run tauri dev --no-dev-server (starts Tauri without dev server)
``` ```
## Usage ## Usage
1. Place your cursor PNG files in the `assets` directory 1. Make sure you have Node.js and npm installed
2. Run the application 2. Navigate to the frontend directory: `cd frontend`
3. Select a cursor from the list 3. Install dependencies if you haven't already: `npm install`
4. Press Ctrl+Shift+F12 to exit the application 4. Run the application in development mode: `npm run tauri dev`
5. The application will start with a React frontend and Rust backend
6. Use the UI to browse and select cursors
7. The application will replace your system cursor with the selected image
8. To exit, use the "Quit" button in the application or from the system tray
### Custom Cursors
The application comes with sample cursors:
- `default.png` - A simple default cursor
- `icon.png` - An icon cursor
- `red.png` - A red-colored cursor
To add your own cursors:
1. Create PNG or CUR images with transparent backgrounds
2. Place them in the `frontend/src/cursors` directory
3. Restart the application to load the new cursors
4. The cursor name in the UI will be derived from the filename
**Note**: The application automatically scans multiple directories for cursor files:
- `frontend/src/cursors/` (recommended location)
- `frontend/src-tauri/src/cursors/`
- `frontend/src-tauri/cursors/`
## Hotkeys ## Hotkeys
- Ctrl+Shift+F12: Exit the application - Ctrl+Shift+F12: Exit the application (restores original system cursor)
- If this hotkey fails, the application will try to use Ctrl+Alt+F12 or F12 as fallbacks
## System Tray ## System Tray
- Left-click: Show the application window - Left-click: Show the application window
- Right-click: Open the context menu - Right-click: Open the context menu with the following options:
- Next cursor: Switch to the next cursor - Next cursor: Switch to the next cursor in the list
- Show window: Show the application window - Show window: Show the application window
- Quit: Exit the application - Quit: Exit the application and restore the original cursor
## Performance
- The application uses optimized rendering techniques to minimize CPU usage
- On Windows, it uses the Windows API to efficiently replace system cursors
- The application uses event-driven cursor updates instead of polling when possible
- Cursor images are cached to improve performance when switching between cursors
## Known Issues & Status
### Recently Fixed Issues
The following issues have been addressed in recent updates:
- Task Manager compatibility: The application now properly handles elevated applications
- System tray menu stability: Fixed issues with the tray menu disappearing when mousing over it
### Current Known Issues
- Some system tray menu interactions may still be inconsistent on certain Windows versions
- Z-ordering issues where custom cursor may hide behind system tray menus due to Windows UI prioritization
- Performance may be affected when Task Manager or other elevated applications are open due to Windows security restrictions
## Steam Integration
The application includes integration with the Steam API (AppID: 1430270):
- Shows as an active game in Steam for time tracking purposes
- Steam integration initializes automatically when the application starts
- Item drop functionality is implemented but not yet active
- Steam must be running for the integration to work properly
- The application will appear in your Steam library as "running" while active
## Troubleshooting
### Build Issues
- **Missing Visual C++ Redistributable**: Install Visual Studio Build Tools or Visual C++ Redistributable packages
- **Node.js library issues**: Reinstall Node.js (version 16.0.0 or later) and ensure all dependencies are properly installed
- **Rust compilation errors**: Ensure Rust 1.56.0 or later is installed and up to date
### Runtime Issues
- **Application won't start**: Check that all dependencies are installed and Steam is running (if using Steam integration)
- **Cursor not changing**: Verify cursor files exist in the correct directory and have proper file permissions
- **System tray issues**: Try restarting the application or check Windows notification area settings
### Development Issues
- **Tauri dev server not starting**: Ensure port 5173 is available or specify a different port
- **Hot reload not working**: Check that the Vite dev server is running properly in the background
## Contributing
Contributions to Cursor Rush are welcome! Here are some ways you can contribute:
- Report bugs and issues
- Suggest new features or improvements
- Submit pull requests with bug fixes or new features
- Help with documentation
- Contribute to macOS and Linux platform support
- Improve the Tauri integration
## License
This project is currently in development and does not yet have a specified license. A license will be added in a future update.
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+12
View File
@@ -0,0 +1,12 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
+33
View File
@@ -0,0 +1,33 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+3758
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@tauri-apps/api": "^1.6.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
"@tauri-apps/cli": "^1.6.3",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1",
"eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"vite": "^6.3.5"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+3
View File
@@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "cursor-rush"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.5.2", features = [] }
zip = { version = "4.0.0", features = ["zstd"] }
[dependencies]
tauri = { version = "1.6.0", features = [ "window-start-dragging", "shell-open", "system-tray", "window-minimize", "window-hide", "window-show", "window-close"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
image = "0.24"
# Ensure "consoleapi" is in this features list
winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", "errhandlingapi", "consoleapi"] }
once_cell = "1.19.0"
steamworks = "0.11.0"
parking_lot = "0.12.3"
rand = "0.9.1"
tokio = { version = "1.45.1", features = ["macros", "parking_lot", "rt-multi-thread", "time"] }
zip = { version = "4.0.0", features = ["zstd"] }
anyhow = "1.0.98"
futures = "0.3.31"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
+61
View File
@@ -0,0 +1,61 @@
use std::{env, fs::{self, read_dir}, io::Write, path::PathBuf};
use zip::{write::SimpleFileOptions, ZipWriter};
fn main() -> Result<(), Box<dyn std::error::Error>> {
tauri_build::build();
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let profile_target_dir = out_dir
.ancestors()
.nth(3)
.ok_or("Failed to determine profile target directory from OUT_DIR")?;
println!(
"cargo:info=Build script running for target OS: {}",
env::var("CARGO_CFG_TARGET_OS").unwrap_or_default()
);
println!(
"cargo:info=Final libraries will be copied to: {}",
profile_target_dir.display()
);
// --- Define libraries and their source locations based on OS ---
let target_os = env::var("CARGO_CFG_TARGET_OS")?;
let source_subdir = if target_os == "windows" {
"lib/win64"
} else if target_os == "linux" {
"lib/linux64"
} else if target_os == "macos" {
"lib/osx"
} else {
return Err("Unsupported target".into());
};
for f in read_dir(source_subdir)? {
let f = f?;
println!("cargo:rerun-if-changed={}", f.path().display());
fs::copy(f.path(), profile_target_dir.join(f.file_name()))?;
}
// Build data file
println!("cargo:rerun-if-changed=src/cursors");
println!("cargo:rerun-if-changed=src/cursor_def.json");
let mut zip = ZipWriter::new(fs::OpenOptions::new().write(true).create(true).open(profile_target_dir.join("cursor_rush.dat"))?);
zip.start_file("cursor_def.json", SimpleFileOptions::default())?;
zip.write_all(&fs::read("src/cursor_def.json")?)?;
for f in read_dir("src/cursors")? {
let f = f?;
zip.start_file(f.file_name().to_string_lossy(), SimpleFileOptions::default())?;
zip.write_all(&fs::read(f.path())?)?;
}
zip.finish()?;
Ok(())
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+130
View File
@@ -0,0 +1,130 @@
use anyhow::Result;
use futures::FutureExt;
use image::{ImageFormat, RgbaImage};
use std::{env, fs, sync::Arc, time::Duration};
use tokio::time::interval;
use tokio::{select, time::Interval};
use zip::ZipArchive;
use crate::{
platform::{restore_default_cursor, set_system_cursor},
Cursor, CURSOR_CHANGED, CURSOR_CMD,
};
#[derive(Debug, Default)]
struct AnimationState {
cursor_index: Option<usize>,
frame_timer: Option<Interval>,
frame_index: usize,
loaded_image: Vec<RgbaImage>,
loaded_particles: Vec<Vec<RgbaImage>>,
}
#[tokio::main]
pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
let mut cmds = CURSOR_CMD.subscribe();
let mut selected: Option<usize> = None;
let mut animation_state = AnimationState::default();
let mut animation_interval = interval(Duration::from_secs_f32(1.0 / 30.0));
loop {
select! {
cmd = cmds.recv() => {
let cmd = cmd?;
println!("GOT CURSOR CMD {:?}", cmd);
match cmd {
crate::CursorCmd::Select(i) => {
if i < cursors.len() {
selected = Some(i);
}
}
crate::CursorCmd::Next => {
selected = Some(selected.map(|x| (x + 1) % cursors.len()).unwrap_or(0))
}
crate::CursorCmd::Reapply => {
},
crate::CursorCmd::Restore => {
},
}
CURSOR_CHANGED.send(selected.unwrap_or(0)).ok();
}
_ = animation_interval.tick() => {}
}
update_cursor(&mut animation_state, selected, &cursors).await?;
}
}
fn load(path: &str) -> Option<RgbaImage> {
let mut zip = ZipArchive::new(
fs::OpenOptions::new()
.read(true)
.open(env::current_exe().ok()?.parent()?.join("cursor_rush.dat"))
.ok()?,
)
.ok()?;
let mut def = zip.by_name(path).ok()?;
let mut bytes = Vec::new();
std::io::copy(&mut def, &mut bytes).ok();
Some(
image::load_from_memory_with_format(&bytes, ImageFormat::from_path(path).ok()?)
.ok()?
.to_rgba8(),
)
}
fn load_frames(frames: &Vec<String>) -> Vec<RgbaImage> {
frames.iter().map(|x| load(x)).flatten().collect()
}
async fn update_cursor(
animation_state: &mut AnimationState,
selected: Option<usize>,
cursors: &Vec<Cursor>,
) -> Result<()> {
if animation_state.cursor_index != selected {
*animation_state = AnimationState::default();
animation_state.cursor_index = selected;
if let Some(selected) = selected {
if cursors[selected].animation_speed > 0.0 {
animation_state.frame_timer = Some(interval(Duration::from_secs_f32(
1.0 / cursors[selected].animation_speed,
)))
}
animation_state.loaded_image = load_frames(&cursors[selected].frames);
animation_state.loaded_particles = cursors[selected]
.particles
.iter()
.map(|x| load_frames(&x.frames))
.collect();
if let Some(i) = animation_state.loaded_image.get(0) {
set_system_cursor(i)?;
}
} else {
restore_default_cursor()?;
}
} else {
if let Some(frame_timer) = &mut animation_state.frame_timer {
if frame_timer.tick().now_or_never().is_some() && animation_state.loaded_image.len() > 0
{
animation_state.frame_index += 1;
animation_state.frame_index %= animation_state.loaded_image.len();
if let Some(i) = animation_state
.loaded_image
.get(animation_state.frame_index)
{
set_system_cursor(i)?;
}
}
}
}
Ok(())
}
+96
View File
@@ -0,0 +1,96 @@
[
{
"name": "white",
"frames": [
"icon.png"
],
"animation_speed": 0.0,
"particles": []
},
{
"name": "red",
"frames": [
"red.png"
],
"animation_speed": 0.0,
"particles": []
},
{
"name": "flashing",
"frames": [
"red.png",
"icon.png"
],
"animation_speed": 30.0,
"particles": []
},
{
"name": "fire circle",
"source": "https://opengameart.org/content/fire-circle-fx",
"frames": [
"fire_circle_0000.png",
"fire_circle_0001.png",
"fire_circle_0002.png",
"fire_circle_0003.png",
"fire_circle_0004.png",
"fire_circle_0005.png",
"fire_circle_0006.png",
"fire_circle_0007.png",
"fire_circle_0008.png",
"fire_circle_0009.png",
"fire_circle_0010.png",
"fire_circle_0011.png",
"fire_circle_0012.png",
"fire_circle_0013.png",
"fire_circle_0014.png",
"fire_circle_0015.png",
"fire_circle_0016.png",
"fire_circle_0017.png",
"fire_circle_0018.png",
"fire_circle_0019.png",
"fire_circle_0020.png",
"fire_circle_0021.png",
"fire_circle_0022.png",
"fire_circle_0023.png",
"fire_circle_0024.png",
"fire_circle_0025.png",
"fire_circle_0026.png",
"fire_circle_0027.png",
"fire_circle_0028.png",
"fire_circle_0029.png",
"fire_circle_0030.png",
"fire_circle_0031.png",
"fire_circle_0032.png",
"fire_circle_0033.png",
"fire_circle_0034.png",
"fire_circle_0035.png",
"fire_circle_0036.png",
"fire_circle_0037.png",
"fire_circle_0038.png",
"fire_circle_0039.png",
"fire_circle_0040.png",
"fire_circle_0041.png",
"fire_circle_0042.png",
"fire_circle_0043.png",
"fire_circle_0044.png",
"fire_circle_0045.png",
"fire_circle_0046.png",
"fire_circle_0047.png",
"fire_circle_0048.png",
"fire_circle_0049.png",
"fire_circle_0050.png",
"fire_circle_0051.png",
"fire_circle_0052.png",
"fire_circle_0053.png",
"fire_circle_0054.png",
"fire_circle_0055.png",
"fire_circle_0056.png",
"fire_circle_0057.png",
"fire_circle_0058.png",
"fire_circle_0059.png",
"fire_circle_0060.png"
],
"animation_speed": 30.0,
"particles": []
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Some files were not shown because too many files have changed in this diff Show More