Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa039c7291 | ||
|
|
73634440cf | ||
|
|
eced2a561c | ||
|
|
61157a2402 | ||
|
|
67ee3dc8d1 | ||
|
|
7016e96673 | ||
|
|
09374eecb3 | ||
|
|
4f71789710 | ||
|
|
a9976cbbe5 | ||
|
|
bba65f2f97 | ||
|
|
c1b8160f4a | ||
|
|
16fc8949b3 | ||
|
|
b31aefe896 | ||
|
|
f60f058cf9 | ||
|
|
9f733ed39b | ||
|
|
0133b61944 | ||
|
|
20592954f7 | ||
|
|
06b2f0ffaf | ||
|
|
2e71cd8b9e | ||
|
|
a92ef1e07c | ||
|
|
b3d85106a8 | ||
|
|
34b0d700a6 | ||
|
|
0e6841bd8a | ||
|
|
9f6f3442dc | ||
|
|
a8d8d2ea0b | ||
|
|
49226cf5c5 | ||
|
|
f464052bda | ||
|
|
cf1c90856f |
@@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
CODE_CLEANUP_TODO.md
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
# Cursor Rush
|
||||||
|
|
||||||
|
A cross-platform application for customizing your cursor with gameified logic.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Replace the system cursor with custom PNG images
|
||||||
|
- Minimize to system tray for background operation
|
||||||
|
- Windows support (macOS and Linux support in development)
|
||||||
|
- 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
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/
|
||||||
|
├── src/ # React frontend code
|
||||||
|
│ ├── App.jsx # Main React component
|
||||||
|
│ ├── components/ # UI components
|
||||||
|
│ └── cursors/ # Cursor image files
|
||||||
|
├── src-tauri/ # Rust backend code
|
||||||
|
│ ├── src/ # Rust source code
|
||||||
|
│ │ ├── lib.rs # Main library code
|
||||||
|
│ │ ├── main.rs # Application entry point
|
||||||
|
│ │ ├── integrations/ # External integrations
|
||||||
|
│ │ │ ├── mod.rs # Integrations module exports
|
||||||
|
│ │ │ └── steam.rs # Steam integration
|
||||||
|
│ │ └── platform/ # Platform-specific code
|
||||||
|
│ │ ├── mod.rs # Platform module exports
|
||||||
|
│ │ ├── types.rs # Common type definitions
|
||||||
|
│ │ ├── windows/ # Windows-specific code
|
||||||
|
│ │ ├── macos/ # macOS-specific code
|
||||||
|
│ │ └── linux/ # Linux-specific code
|
||||||
|
│ └── Cargo.toml # Rust dependencies
|
||||||
|
└── package.json # npm dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Rust 1.56.0 or later (tested with 1.86.0)
|
||||||
|
- 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to the frontend directory
|
||||||
|
cd frontend
|
||||||
|
|
||||||
|
# Install npm dependencies
|
||||||
|
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
|
||||||
|
|
||||||
|
1. Make sure you have Node.js and npm installed
|
||||||
|
2. Navigate to the frontend directory: `cd frontend`
|
||||||
|
3. Install dependencies if you haven't already: `npm install`
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- Left-click: Show the application window
|
||||||
|
- Right-click: Open the context menu with the following options:
|
||||||
|
- Next cursor: Switch to the next cursor in the list
|
||||||
|
- Show window: Show the application window
|
||||||
|
- 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.
|
||||||
@@ -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?
|
||||||
@@ -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.
|
||||||
@@ -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 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -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>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 |
@@ -0,0 +1,3 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
@@ -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"]
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 407 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.8 KiB |