Integrate Tauri frontend with categorization system and Steam integration

This commit is contained in:
2025-05-17 21:07:31 -05:00
parent 0133b61944
commit 9f733ed39b
11 changed files with 392 additions and 143 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}
+20 -58
View File
@@ -16,43 +16,26 @@ A cross-platform application for customizing your cursor with gameified logic.
## 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
│ ├── macos/ # macOS-specific code (in development) │ │ └── linux/ # Linux-specific code
│ │ ├── mod.rs # macOS module exports │ └── Cargo.toml # Rust dependencies
│ │ ├── state.rs # macOS state management └── package.json # npm dependencies
│ │ └── cursor.rs # macOS cursor handling
│ ├── linux/ # Linux-specific code (in development)
│ │ ├── mod.rs # Linux module exports
│ │ ├── state.rs # Linux state management
│ │ └── cursor.rs # Linux cursor handling
│ └── unsupported/ # Fallback for unsupported platforms
│ ├── mod.rs # Unsupported module exports
│ ├── state.rs # Unsupported state management
│ └── cursor.rs # Unsupported cursor handling
├── 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
@@ -149,27 +132,6 @@ The application includes integration with the Steam API for future gameified fea
- Item drop functionality is implemented but not yet active - Item drop functionality is implemented but not yet active
- Steam must be running for this functionality to work - Steam must be running for this functionality to work
## Project Structure (Tauri Version)
```
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
│ │ └── platform/ # Platform-specific code
│ │ ├── mod.rs # Platform module exports
│ │ ├── windows/ # Windows-specific code
│ │ ├── macos/ # macOS-specific code
│ │ └── linux/ # Linux-specific code
│ └── Cargo.toml # Rust dependencies
└── package.json # npm dependencies
```
## Contributing ## Contributing
Contributions to Cursor Rush are welcome! Here are some ways you can contribute: Contributions to Cursor Rush are welcome! Here are some ways you can contribute:
+3 -2
View File
@@ -14,13 +14,14 @@ rust-version = "1.56"
tauri-build = { version = "1.5.2", features = [] } tauri-build = { version = "1.5.2", features = [] }
[dependencies] [dependencies]
tauri = { version = "1.6.0", features = [ "window-start-dragging", "shell-open"] } 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_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
image = "0.24" image = "0.24"
# Ensure "consoleapi" is in this features list # Ensure "consoleapi" is in this features list
winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", "errhandlingapi", "consoleapi"] } winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", "errhandlingapi", "consoleapi"] }
once_cell = "1.19.0" once_cell = "1.19.0"
steamworks = "0.10.0"
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem # this feature is used for production builds or when `devPath` points to the filesystem
# DO NOT REMOVE!! # DO NOT REMOVE!!
@@ -0,0 +1,3 @@
// External integrations module
pub mod steam;
@@ -0,0 +1,26 @@
// Steam integration
use steamworks::{AppId, Client, SingleClient};
#[allow(dead_code)]
pub struct Steam {
client: Client,
single: SingleClient,
}
impl Steam {
pub fn new() -> Self {
let (client, single) = Client::init_app(AppId(1430270)).unwrap();
Self { client, single }
}
#[allow(dead_code)]
pub fn drop_item(&self) {
// Placeholder for item drop functionality
// The actual implementation would use the Steam API to trigger an item drop
println!("Steam item drop triggered (placeholder)");
// In a real implementation with the correct API version, we would use:
// self.client.get_inventory().trigger_item_drop(item_def_id);
}
}
+178 -40
View File
@@ -1,10 +1,11 @@
use std::sync::Mutex; use std::sync::Mutex;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use tauri::{Manager, AppHandle}; use tauri::{Manager, AppHandle, CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayMenuItem, SystemTrayEvent, WindowEvent};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::env; use std::env;
mod platform; mod platform;
mod integrations;
// Define cursor structure // Define cursor structure
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -40,26 +41,26 @@ fn get_current_cursor_index(state: tauri::State<AppStateWrapper>) -> usize {
#[tauri::command] #[tauri::command]
fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<(), String> { fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<(), String> {
println!("=== RESOURCE DEBUG [select_cursor] ==="); println!("=== RESOURCE DEBUG [select_cursor] ===");
let mut app_state = state.0.lock().unwrap(); let mut app_state = state.0.lock().unwrap();
if index < app_state.cursors.len() { if index < app_state.cursors.len() {
app_state.current_index = index; app_state.current_index = index;
let cursor = &app_state.cursors[index]; let cursor = &app_state.cursors[index];
println!("Cursor selected: {}", cursor.name); println!("Cursor selected: {}", cursor.name);
println!("Original path: {}", cursor.path); println!("Original path: {}", cursor.path);
// Get the resource directory // Get the resource directory
let resource_dir = app_handle.path_resolver().resource_dir(); let resource_dir = app_handle.path_resolver().resource_dir();
println!("Resource dir: {:?}", resource_dir); println!("Resource dir: {:?}", resource_dir);
// List contents of resource dir // List contents of resource dir
if let Some(res_dir) = &resource_dir { if let Some(res_dir) = &resource_dir {
println!("Listing contents of resource dir: {}", res_dir.display()); println!("Listing contents of resource dir: {}", res_dir.display());
if let Ok(entries) = std::fs::read_dir(res_dir) { if let Ok(entries) = std::fs::read_dir(res_dir) {
for (i, entry) in entries.filter_map(Result::ok).enumerate() { for (i, entry) in entries.filter_map(Result::ok).enumerate() {
println!(" {}: {}", i, entry.path().display()); println!(" {}: {}", i, entry.path().display());
// List subdirectories // List subdirectories
if entry.path().is_dir() { if entry.path().is_dir() {
if let Ok(subentries) = std::fs::read_dir(entry.path()) { if let Ok(subentries) = std::fs::read_dir(entry.path()) {
@@ -73,29 +74,29 @@ fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle:
println!("Failed to read resource directory"); println!("Failed to read resource directory");
} }
} }
let filename = Path::new(&cursor.path) let filename = Path::new(&cursor.path)
.file_name() .file_name()
.and_then(|name| name.to_str()) .and_then(|name| name.to_str())
.ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?; .ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?;
println!("Extracted filename: {}", filename); println!("Extracted filename: {}", filename);
// Try using the actual file path directly // Try using the actual file path directly
println!("Using direct file path: {}", cursor.path); println!("Using direct file path: {}", cursor.path);
// Verify the file exists // Verify the file exists
if Path::new(&cursor.path).exists() { if Path::new(&cursor.path).exists() {
println!("File exists, applying cursor..."); println!("File exists, applying cursor...");
if let Err(e) = platform::set_system_cursor(&cursor.path) { if let Err(e) = platform::set_system_cursor(&cursor.path) {
println!("Error applying cursor: {}", e); println!("Error applying cursor: {}", e);
return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e)); return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e));
} }
// Emit an event to notify the frontend // Emit an event to notify the frontend
let _ = app_handle.emit_all("cursor_changed", index); // Ignore error let _ = app_handle.emit_all("cursor_changed", index); // Ignore error
println!("Successfully applied cursor using actual file path"); println!("Successfully applied cursor using actual file path");
return Ok(()); return Ok(());
} else { } else {
@@ -111,11 +112,11 @@ fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle:
#[tauri::command] #[tauri::command]
fn next_cursor(state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<usize, String> { fn next_cursor(state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<usize, String> {
println!("=== RESOURCE DEBUG [next_cursor] ==="); println!("=== RESOURCE DEBUG [next_cursor] ===");
let mut app_state = state.0.lock().unwrap(); let mut app_state = state.0.lock().unwrap();
if !app_state.cursors.is_empty() { if !app_state.cursors.is_empty() {
app_state.current_index = (app_state.current_index + 1) % app_state.cursors.len(); app_state.current_index = (app_state.current_index + 1) % app_state.cursors.len();
let cursor = &app_state.cursors[app_state.current_index]; let cursor = &app_state.cursors[app_state.current_index];
println!("Next cursor: {} ({})", cursor.name, cursor.path); println!("Next cursor: {} ({})", cursor.name, cursor.path);
@@ -126,22 +127,22 @@ fn next_cursor(state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> R
// Try both resource paths like in select_cursor // Try both resource paths like in select_cursor
println!("Extracted filename: {}", filename); println!("Extracted filename: {}", filename);
// Try using the actual file path directly // Try using the actual file path directly
println!("Using direct file path: {}", cursor.path); println!("Using direct file path: {}", cursor.path);
// Verify the file exists // Verify the file exists
if Path::new(&cursor.path).exists() { if Path::new(&cursor.path).exists() {
println!("File exists, applying cursor..."); println!("File exists, applying cursor...");
if let Err(e) = platform::set_system_cursor(&cursor.path) { if let Err(e) = platform::set_system_cursor(&cursor.path) {
println!("Error applying cursor: {}", e); println!("Error applying cursor: {}", e);
return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e)); return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e));
} }
// Emit an event to notify the frontend // Emit an event to notify the frontend
let _ = app_handle.emit_all("cursor_changed", app_state.current_index); // Ignore error let _ = app_handle.emit_all("cursor_changed", app_state.current_index); // Ignore error
println!("Successfully applied cursor using actual file path"); println!("Successfully applied cursor using actual file path");
return Ok(app_state.current_index); return Ok(app_state.current_index);
} else { } else {
@@ -161,22 +162,40 @@ fn quit_app(app_handle: AppHandle) {
Ok(_) => println!("Default cursor restored"), Ok(_) => println!("Default cursor restored"),
Err(e) => eprintln!("Failed to restore default cursor: {}", e), Err(e) => eprintln!("Failed to restore default cursor: {}", e),
} }
// Exit the application // Exit the application
std::thread::spawn(move || { std::thread::spawn(move || {
app_handle.exit(0); app_handle.exit(0);
}); });
} }
// Command to minimize the window to tray
#[tauri::command]
fn minimize_to_tray(app_handle: AppHandle) -> Result<(), String> {
println!("Minimizing to tray");
// Get the main window
let window = app_handle.get_window("main")
.ok_or_else(|| "Failed to get main window".to_string())?;
// Hide the window
window.hide().map_err(|e| format!("Failed to hide window: {}", e))?;
// The tray should already be visible as configured in tauri.conf.json
println!("Window hidden, application should now be accessible from the system tray");
Ok(())
}
// Function to load cursor files from src/cursors directory // Function to load cursor files from src/cursors directory
fn load_cursor_files() -> Vec<Cursor> { fn load_cursor_files() -> Vec<Cursor> {
println!("=== RESOURCE DEBUG [load_cursor_files] ==="); println!("=== RESOURCE DEBUG [load_cursor_files] ===");
let mut cursors = Vec::new(); let mut cursors = Vec::new();
// Determine the path to src/cursors relative to src-tauri (CARGO_MANIFEST_DIR) // Determine the path to src/cursors relative to src-tauri (CARGO_MANIFEST_DIR)
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
println!("CARGO_MANIFEST_DIR: {}", manifest_dir.display()); println!("CARGO_MANIFEST_DIR: {}", manifest_dir.display());
// Check multiple possible locations // Check multiple possible locations
let possible_cursor_dirs = vec![ let possible_cursor_dirs = vec![
manifest_dir.join("../src/cursors"), manifest_dir.join("../src/cursors"),
@@ -184,26 +203,26 @@ fn load_cursor_files() -> Vec<Cursor> {
manifest_dir.join("cursors"), manifest_dir.join("cursors"),
manifest_dir.clone(), // Check manifest dir itself manifest_dir.clone(), // Check manifest dir itself
]; ];
println!("CHECKING FOR PNG/CUR FILES IN MULTIPLE DIRECTORIES:"); println!("CHECKING FOR PNG/CUR FILES IN MULTIPLE DIRECTORIES:");
for (i, cursors_dir) in possible_cursor_dirs.iter().enumerate() { for (i, cursors_dir) in possible_cursor_dirs.iter().enumerate() {
println!("Checking dir {}: {}", i+1, cursors_dir.display()); println!("Checking dir {}: {}", i+1, cursors_dir.display());
println!(" Directory exists: {}", cursors_dir.exists()); println!(" Directory exists: {}", cursors_dir.exists());
if cursors_dir.exists() { if cursors_dir.exists() {
println!(" Looking for cursor files in this directory..."); println!(" Looking for cursor files in this directory...");
if let Ok(entries) = std::fs::read_dir(cursors_dir) { if let Ok(entries) = std::fs::read_dir(cursors_dir) {
for entry in entries.filter_map(Result::ok) { for entry in entries.filter_map(Result::ok) {
let path = entry.path(); let path = entry.path();
println!(" Found: {}", path.display()); println!(" Found: {}", path.display());
if path.is_file() && path.extension().map_or(false, |ext| ext == "png" || ext == "cur") { if path.is_file() && path.extension().map_or(false, |ext| ext == "png" || ext == "cur") {
let name = path.file_stem() let name = path.file_stem()
.and_then(|s| s.to_str()) .and_then(|s| s.to_str())
.unwrap_or("Unknown") .unwrap_or("Unknown")
.to_string(); .to_string();
println!(" ADDING CURSOR: {} at path {}", name, path.display()); println!(" ADDING CURSOR: {} at path {}", name, path.display());
cursors.push(Cursor { cursors.push(Cursor {
name, name,
@@ -212,14 +231,14 @@ fn load_cursor_files() -> Vec<Cursor> {
} }
} }
} }
if !cursors.is_empty() { if !cursors.is_empty() {
println!(" Found {} cursor files in this directory.", cursors.len()); println!(" Found {} cursor files in this directory.", cursors.len());
break; break;
} }
} }
} }
// If we found no cursors, use mock data with paths relative to what resolve_resource expects // If we found no cursors, use mock data with paths relative to what resolve_resource expects
if cursors.is_empty() { if cursors.is_empty() {
println!("No cursors found in any directory. Using mock data instead."); println!("No cursors found in any directory. Using mock data instead.");
@@ -228,28 +247,28 @@ fn load_cursor_files() -> Vec<Cursor> {
for dir in &possible_cursor_dirs { for dir in &possible_cursor_dirs {
println!(" {}", dir.display()); println!(" {}", dir.display());
} }
// Mock data paths should reflect their eventual resource path structure // Mock data paths should reflect their eventual resource path structure
cursors.push(Cursor { cursors.push(Cursor {
name: "Default".to_string(), name: "Default".to_string(),
path: "src/cursors/default.png".to_string(), // Filename will be 'default.png' path: "src/cursors/default.png".to_string(), // Filename will be 'default.png'
}); });
cursors.push(Cursor { cursors.push(Cursor {
name: "Icon".to_string(), name: "Icon".to_string(),
path: "src/cursors/icon.png".to_string(), // Filename will be 'icon.png' path: "src/cursors/icon.png".to_string(), // Filename will be 'icon.png'
}); });
cursors.push(Cursor { cursors.push(Cursor {
name: "Red".to_string(), name: "Red".to_string(),
path: "src/cursors/red.png".to_string(), // Filename will be 'red.png' path: "src/cursors/red.png".to_string(), // Filename will be 'red.png'
}); });
println!("Created {} mock cursor entries", cursors.len()); println!("Created {} mock cursor entries", cursors.len());
} }
println!("Loaded {} cursor files", cursors.len()); println!("Loaded {} cursor files", cursors.len());
// Sort cursors by name for consistent ordering // Sort cursors by name for consistent ordering
cursors.sort_by(|a, b| a.name.cmp(&b.name)); cursors.sort_by(|a, b| a.name.cmp(&b.name));
cursors cursors
@@ -258,34 +277,153 @@ fn load_cursor_files() -> Vec<Cursor> {
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
println!("Starting Cursor Rush application"); println!("Starting Cursor Rush application");
// Initialize platform-specific cursor handling // Initialize platform-specific cursor handling
if let Err(e) = platform::initialize() { if let Err(e) = platform::initialize() {
eprintln!("Failed to initialize platform: {}", e); eprintln!("Failed to initialize platform: {}", e);
} }
// Load cursors from assets directory // Load cursors from assets directory
let cursors = load_cursor_files(); let cursors = load_cursor_files();
// Create app state // Create app state
let app_state = AppState { let app_state = AppState {
cursors, cursors,
current_index: 0, current_index: 0,
}; };
// Create system tray menu
let next_cursor_item = CustomMenuItem::new("next_cursor".to_string(), "Next cursor");
let show_window_item = CustomMenuItem::new("show_window".to_string(), "Show window");
let quit_item = CustomMenuItem::new("quit".to_string(), "Quit");
let tray_menu = SystemTrayMenu::new()
.add_item(next_cursor_item)
.add_item(show_window_item)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(quit_item);
let system_tray = SystemTray::new().with_menu(tray_menu);
// This is one of the simplest ways to initialize Tauri // This is one of the simplest ways to initialize Tauri
tauri::Builder::default() tauri::Builder::default()
.manage(AppStateWrapper(Mutex::new(app_state))) .manage(AppStateWrapper(Mutex::new(app_state)))
.system_tray(system_tray)
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::LeftClick { .. } => {
println!("System tray received a left click");
if let Some(window) = app.get_window("main") {
match window.is_visible() {
Ok(visible) => {
if visible {
println!("Window is already visible, focusing it");
let _ = window.set_focus();
} else {
println!("Window is hidden, showing it");
if let Err(e) = window.show() {
eprintln!("Failed to show window: {}", e);
}
if let Err(e) = window.set_focus() {
eprintln!("Failed to focus window: {}", e);
}
}
},
Err(e) => {
eprintln!("Failed to check window visibility: {}", e);
// Try to show it anyway
if let Err(e) = window.show() {
eprintln!("Failed to show window: {}", e);
}
}
}
} else {
eprintln!("Main window not found");
}
}
SystemTrayEvent::MenuItemClick { id, .. } => {
println!("Menu item clicked: {}", id);
match id.as_str() {
"next_cursor" => {
println!("Next cursor requested from tray");
let state = app.state::<AppStateWrapper>();
let mut app_state = state.0.lock().unwrap();
if !app_state.cursors.is_empty() {
app_state.current_index = (app_state.current_index + 1) % app_state.cursors.len();
let cursor = &app_state.cursors[app_state.current_index];
if Path::new(&cursor.path).exists() {
if let Err(e) = platform::set_system_cursor(&cursor.path) {
eprintln!("Error applying cursor from tray: {}", e);
} else {
// Emit an event to notify the frontend
let _ = app.emit_all("cursor_changed", app_state.current_index);
}
}
}
}
"show_window" => {
println!("Show window requested from tray");
if let Some(window) = app.get_window("main") {
if let Err(e) = window.show() {
eprintln!("Failed to show window: {}", e);
}
if let Err(e) = window.set_focus() {
eprintln!("Failed to focus window: {}", e);
}
} else {
eprintln!("Main window not found");
}
}
"quit" => {
println!("Quit requested from tray");
// Clean up and restore default cursor before exit
match platform::restore_system_cursor() {
Ok(_) => println!("Default cursor restored"),
Err(e) => eprintln!("Failed to restore default cursor: {}", e),
}
app.exit(0);
}
_ => {}
}
}
_ => {}
})
.on_window_event(move |event| {
match event.event() {
WindowEvent::CloseRequested { api, .. } => {
// Prevent the default close action
api.prevent_close();
// Get the window and hide it
let window = event.window();
println!("Window close requested in backend handler, hiding window");
// Hide the window instead of closing it
if let Err(e) = window.hide() {
eprintln!("Failed to hide window: {}", e);
} else {
println!("Window hidden successfully, app is now in system tray");
}
}
_ => {}
}
})
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
get_cursors, get_cursors,
get_current_cursor_index, get_current_cursor_index,
select_cursor, select_cursor,
next_cursor, next_cursor,
quit_app quit_app,
minimize_to_tray
]) ])
.setup(|_app| { .setup(|_app| {
// Register for events or perform additional setup // Register for events or perform additional setup
println!("Tauri application setup complete"); println!("Tauri application setup complete");
// We're not setting up a window close handler here anymore
// The frontend will handle this based on the minimize-to-tray setting
Ok(()) Ok(())
}) })
// This matches exactly how it was before our changes // This matches exactly how it was before our changes
+14 -2
View File
@@ -14,7 +14,11 @@
"all": false, "all": false,
"window": { "window": {
"all": false, "all": false,
"startDragging": true "startDragging": true,
"minimize": true,
"hide": true,
"show": true,
"close": true
}, },
"shell": { "shell": {
"all": false, "all": false,
@@ -27,6 +31,12 @@
] ]
} }
}, },
"systemTray": {
"iconPath": "icons/icon.png",
"iconAsTemplate": false,
"menuOnLeftClick": false,
"title": "Cursor Rush"
},
"bundle": { "bundle": {
"active": true, "active": true,
"identifier": "com.cursor-rush.dev", "identifier": "com.cursor-rush.dev",
@@ -47,13 +57,15 @@
}, },
"windows": [ "windows": [
{ {
"label": "main",
"title": "Cursor Rush", "title": "Cursor Rush",
"width": 310, "width": 310,
"height": 600, "height": 600,
"resizable": true, "resizable": true,
"fullscreen": false, "fullscreen": false,
"decorations": false, "decorations": false,
"transparent": true "transparent": true,
"visible": true
} }
] ]
} }
+89 -20
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useCallback } from "react";
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { appWindow } from "@tauri-apps/api/window"; import { appWindow } from "@tauri-apps/api/window";
import "./App.css"; import "./App.css";
@@ -17,8 +17,37 @@ function App() {
const [activePage, setActivePage] = useState("cursors"); const [activePage, setActivePage] = useState("cursors");
const [cursors, setCursors] = useState([]); const [cursors, setCursors] = useState([]);
const [currentCursorIndex, setCurrentCursorIndex] = useState(0); const [currentCursorIndex, setCurrentCursorIndex] = useState(0);
const [settings, setSettings] = useState({
minimizeToTrayOnClose: true,
startWithWindows: false,
autoApplyCursorOnSelection: true
});
// Load cursors on mount // Function to minimize app to tray
const minimizeToTray = useCallback(async () => {
try {
console.log('Minimizing app to tray');
// First hide the window to ensure it disappears immediately
await appWindow.hide();
console.log('Window hidden, now ensuring tray is visible');
// Then call the backend to ensure the tray is visible
await invoke("minimize_to_tray");
console.log('minimize_to_tray command completed successfully');
} catch (error) {
console.error("Failed to minimize to tray:", error);
// Fallback to window API if the invoke fails
try {
console.log('Trying fallback hide');
await appWindow.hide();
console.log('Fallback hide succeeded');
} catch (fallbackError) {
console.error("Fallback hide also failed:", fallbackError);
}
}
}, []);
// Load cursors on mount and set up event listeners
useEffect(() => { useEffect(() => {
const loadCursors = async () => { const loadCursors = async () => {
try { try {
@@ -26,7 +55,7 @@ function App() {
const loadedCursors = await invoke("get_cursors"); const loadedCursors = await invoke("get_cursors");
console.log("Loaded cursors:", loadedCursors); console.log("Loaded cursors:", loadedCursors);
setCursors(loadedCursors || []); setCursors(loadedCursors || []);
// Get current cursor index // Get current cursor index
const currentIndex = await invoke("get_current_cursor_index"); const currentIndex = await invoke("get_current_cursor_index");
setCurrentCursorIndex(currentIndex); setCurrentCursorIndex(currentIndex);
@@ -47,6 +76,53 @@ function App() {
loadCursors(); loadCursors();
}, []); }, []);
// Set up window close event listener
useEffect(() => {
const setupCloseHandler = async () => {
const unlisten = await appWindow.onCloseRequested(async (event) => {
console.log('Window close requested');
if (settings.minimizeToTrayOnClose) {
console.log('Minimize to tray setting is enabled, preventing close and hiding instead');
// Prevent the window from closing
event.preventDefault();
// Hide the window instead
try {
console.log('Calling minimizeToTray function');
await minimizeToTray();
console.log('Window should now be hidden and app should be in system tray');
} catch (error) {
console.error('Failed to minimize to tray on close:', error);
}
} else {
console.log('Minimize to tray setting is disabled, allowing window to close');
// Let the window close normally, but we need to call quit_app to ensure proper cleanup
try {
await invoke("quit_app");
} catch (error) {
console.error("Failed to quit app:", error);
}
}
});
return unlisten;
};
let unlistenFn;
setupCloseHandler().then(unlisten => {
unlistenFn = unlisten;
console.log('Close handler set up successfully');
}).catch(err => console.error('Failed to set up close handler:', err));
// Clean up event listener on unmount
return () => {
if (unlistenFn) {
unlistenFn();
}
};
}, [settings.minimizeToTrayOnClose, minimizeToTray]);
// Functions to handle cursor operations using Tauri backend // Functions to handle cursor operations using Tauri backend
const selectCursor = async (index) => { const selectCursor = async (index) => {
try { try {
@@ -60,7 +136,7 @@ function App() {
const nextCursor = async () => { const nextCursor = async () => {
if (cursors.length === 0) return; if (cursors.length === 0) return;
try { try {
console.log('Switching to next cursor'); console.log('Switching to next cursor');
const newIndex = await invoke("next_cursor"); const newIndex = await invoke("next_cursor");
@@ -73,16 +149,6 @@ function App() {
} }
}; };
// Function to minimize app to tray
const minimizeToTray = async () => {
try {
console.log('Minimizing app to tray');
await appWindow.hide();
} catch (error) {
console.error("Failed to minimize to tray:", error);
}
};
// Function to exit app // Function to exit app
const quitApp = async () => { const quitApp = async () => {
try { try {
@@ -96,22 +162,25 @@ function App() {
return ( return (
<div className="modal-container"> <div className="modal-container">
<div className="modal"> <div className="modal">
<Header <Header
activePage={activePage} activePage={activePage}
setActivePage={setActivePage} setActivePage={setActivePage}
minimizeToTray={minimizeToTray} minimizeToTray={minimizeToTray}
quitApp={quitApp} quitApp={quitApp}
settings={settings}
/> />
<Content <Content
activePage={activePage} activePage={activePage}
cursors={cursors} cursors={cursors}
currentCursorIndex={currentCursorIndex} currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor} selectCursor={selectCursor}
nextCursor={nextCursor} nextCursor={nextCursor}
settings={settings}
setSettings={setSettings}
/> />
<Footer <Footer
version="0.1.0" version="0.1.0"
status={cursors.length > 0 ? `${cursors.length} cursors loaded` : "No cursors found"} status={cursors.length > 0 ? `${cursors.length} cursors loaded` : "No cursors found"}
/> />
+6 -6
View File
@@ -7,19 +7,19 @@ import Cursors from './content_modules/Cursors';
import Crates from './content_modules/Crates'; import Crates from './content_modules/Crates';
import Settings from './content_modules/Settings'; import Settings from './content_modules/Settings';
function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCursor }) { function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCursor, settings, setSettings }) {
// Render the appropriate content based on active page // Render the appropriate content based on active page
const renderContent = () => { const renderContent = () => {
switch (activePage) { switch (activePage) {
case 'inventory': case 'inventory':
return <Inventory return <Inventory
cursors={cursors} cursors={cursors}
currentCursorIndex={currentCursorIndex} currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor} selectCursor={selectCursor}
/>; />;
case 'cursors': case 'cursors':
return ( return (
<Cursors <Cursors
cursors={cursors} cursors={cursors}
currentCursorIndex={currentCursorIndex} currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor} selectCursor={selectCursor}
@@ -29,9 +29,9 @@ function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCu
case 'crates': case 'crates':
return <Crates />; return <Crates />;
case 'settings': case 'settings':
return <Settings />; return <Settings settings={settings} setSettings={setSettings} />;
default: default:
return <Cursors return <Cursors
cursors={cursors} cursors={cursors}
currentCursorIndex={currentCursorIndex} currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor} selectCursor={selectCursor}
@@ -39,7 +39,7 @@ function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCu
/>; />;
} }
}; };
return ( return (
<div className="app-content"> <div className="app-content">
{renderContent()} {renderContent()}
+25 -10
View File
@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import './Header.css'; import './Header.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
faGripVertical, faGripVertical,
faBoxOpen, faBoxOpen,
faMousePointer, faMousePointer,
faBoxes, faBoxes,
faCog, faCog,
faBoxArchive, faBoxArchive,
faBox, faBox,
faWarehouse, faWarehouse,
@@ -16,7 +16,7 @@ import {
faBagShopping faBagShopping
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
function Header({ activePage, setActivePage, minimizeToTray, quitApp }) { function Header({ activePage, setActivePage, minimizeToTray, quitApp, settings }) {
// Navigation buttons for each page with icons // Navigation buttons for each page with icons
const navItems = [ const navItems = [
{ id: 'inventory', icon: faBagShopping, tooltip: 'Inventory' }, { id: 'inventory', icon: faBagShopping, tooltip: 'Inventory' },
@@ -24,12 +24,23 @@ function Header({ activePage, setActivePage, minimizeToTray, quitApp }) {
{ id: 'crates', icon: faBoxOpen, tooltip: 'Crates' }, { id: 'crates', icon: faBoxOpen, tooltip: 'Crates' },
{ id: 'settings', icon: faCog, tooltip: 'Settings' } { id: 'settings', icon: faCog, tooltip: 'Settings' }
]; ];
// Handle close button click based on settings
const handleCloseClick = () => {
if (settings && settings.minimizeToTrayOnClose) {
console.log('Close button clicked, minimizing to tray based on settings');
minimizeToTray();
} else {
console.log('Close button clicked, quitting application based on settings');
quitApp();
}
};
return ( return (
<header className="app-header" data-tauri-drag-region> <header className="app-header" data-tauri-drag-region>
<div className="tabs-container"> <div className="tabs-container">
{navItems.map((item) => ( {navItems.map((item) => (
<div <div
key={item.id} key={item.id}
className={`tab ${activePage === item.id ? 'active' : ''}`} className={`tab ${activePage === item.id ? 'active' : ''}`}
onClick={() => setActivePage(item.id)} onClick={() => setActivePage(item.id)}
@@ -42,7 +53,11 @@ function Header({ activePage, setActivePage, minimizeToTray, quitApp }) {
<button className="minimize-button" onClick={minimizeToTray} title="Minimize to Tray"> <button className="minimize-button" onClick={minimizeToTray} title="Minimize to Tray">
_ _
</button> </button>
<button className="close-button" onClick={quitApp} title="Quit Application"> <button
className="close-button"
onClick={handleCloseClick}
title={settings && settings.minimizeToTrayOnClose ? "Minimize to Tray" : "Quit Application"}
>
× ×
</button> </button>
</div> </div>
@@ -1,7 +1,15 @@
import React from 'react'; import React from 'react';
import './Settings.css'; import './Settings.css';
function Settings() { function Settings({ settings, setSettings }) {
// Handler for checkbox changes
const handleSettingChange = (settingName) => {
setSettings({
...settings,
[settingName]: !settings[settingName]
});
};
return ( return (
<div className="settings-container"> <div className="settings-container">
<h2 className="content-title">Settings</h2> <h2 className="content-title">Settings</h2>
@@ -10,23 +18,35 @@ function Settings() {
<h3>Application Settings</h3> <h3>Application Settings</h3>
<div className="setting-item"> <div className="setting-item">
<label> <label>
<input type="checkbox" /> <input
type="checkbox"
checked={settings.startWithWindows}
onChange={() => handleSettingChange('startWithWindows')}
/>
Start with Windows Start with Windows
</label> </label>
</div> </div>
<div className="setting-item"> <div className="setting-item">
<label> <label>
<input type="checkbox" checked /> <input
type="checkbox"
checked={settings.minimizeToTrayOnClose}
onChange={() => handleSettingChange('minimizeToTrayOnClose')}
/>
Minimize to tray on close Minimize to tray on close
</label> </label>
</div> </div>
</div> </div>
<div className="settings-group"> <div className="settings-group">
<h3>Cursor Settings</h3> <h3>Cursor Settings</h3>
<div className="setting-item"> <div className="setting-item">
<label> <label>
<input type="checkbox" checked /> <input
type="checkbox"
checked={settings.autoApplyCursorOnSelection}
onChange={() => handleSettingChange('autoApplyCursorOnSelection')}
/>
Auto-apply cursor on selection Auto-apply cursor on selection
</label> </label>
</div> </div>