11 changed files with 421 additions and 139 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}
+49 -54
View File
@@ -11,47 +11,31 @@ A cross-platform application for customizing your cursor with gameified logic.
- System tray integration with context menu
- Global hotkey support
- Steam integration for item drops
- Tauri-based frontend with React
## Project Structure
```
src/
├── app/ # Application core logic
│ ├── mod.rs # Main app module exports
│ ├── state.rs # Application state management
│ ├── messages.rs # Message definitions
│ └── cursor.rs # Cursor-related functionality
├── ui/ # User interface components
│ ├── mod.rs # UI module exports
│ ├── view.rs # View rendering logic
│ └── tray.rs # System tray functionality
├── platform/ # Platform-specific implementations
│ ├── mod.rs # Platform module exports
│ ├── windows/ # Windows-specific code
│ │ ├── mod.rs # Windows module exports
│ │ ├── types.rs # Windows type definitions
│ │ ├── state.rs # Windows state management
│ │ └── cursor.rs # Windows cursor handling
│ ├── macos/ # macOS-specific code (in development)
│ │ ├── mod.rs # macOS module exports
│ │ ├── state.rs # macOS state management
│ │ └── 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
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
@@ -60,6 +44,8 @@ src/
- Rust 1.70.0 or later
- Cargo
- Node.js 16.0.0 or later
- npm 7.0.0 or later
- Platform-specific dependencies:
- Windows: None (fully supported)
- macOS: Development in progress
@@ -68,41 +54,49 @@ src/
### Dependencies
The application uses the following major dependencies:
- iced 0.13.1 - GUI framework
- tray-icon 0.20.1 - System tray functionality
- Tauri 1.6.0 - Cross-platform framework
- React - Frontend framework
- image 0.24.7 - Image processing
- global-hotkey 0.4.2 - Global hotkey registration
- steamworks 0.11.0 - Steam integration
- winapi 0.3.9 - Windows API bindings
- once_cell 1.19.0 - Lazy initialization
### Build Commands
```bash
# Debug build
cargo build
# Navigate to the frontend directory
cd frontend
# Release build
cargo build --release
# Install npm dependencies
npm install
# Run in development mode
npm run tauri dev
# Build for production
npm run tauri build
```
## Usage
1. Place your cursor PNG files in the `assets` directory (PNG format only)
2. Run the application using `cargo run` or the compiled executable
3. The application will load all PNG files from the assets directory
4. Select a cursor from the list in the UI
5. The application will replace your system cursor with the selected image
6. When closing the window, the application minimizes to the system tray
7. To fully exit, use the system tray menu or press the exit hotkey
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 two sample cursors in the `assets` directory:
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 images with transparent backgrounds
2. Place them in the `assets` directory
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
@@ -147,6 +141,7 @@ Contributions to Cursor Rush are welcome! Here are some ways you can contribute:
- Submit pull requests with bug fixes or new features
- Help with documentation
- Contribute to macOS and Linux platform support
- Improve the Tauri integration
## License
+3 -2
View File
@@ -14,13 +14,14 @@ rust-version = "1.56"
tauri-build = { version = "1.5.2", features = [] }
[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 = { 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"] }
winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", "errhandlingapi", "consoleapi"] }
once_cell = "1.19.0"
steamworks = "0.10.0"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem
# 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 serde::{Serialize, Deserialize};
use tauri::{Manager, AppHandle};
use tauri::{Manager, AppHandle, CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayMenuItem, SystemTrayEvent, WindowEvent};
use std::path::{Path, PathBuf};
use std::env;
mod platform;
mod integrations;
// Define cursor structure
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -40,26 +41,26 @@ fn get_current_cursor_index(state: tauri::State<AppStateWrapper>) -> usize {
#[tauri::command]
fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<(), String> {
println!("=== RESOURCE DEBUG [select_cursor] ===");
let mut app_state = state.0.lock().unwrap();
if index < app_state.cursors.len() {
app_state.current_index = index;
let cursor = &app_state.cursors[index];
println!("Cursor selected: {}", cursor.name);
println!("Original path: {}", cursor.path);
// Get the resource directory
let resource_dir = app_handle.path_resolver().resource_dir();
println!("Resource dir: {:?}", resource_dir);
// List contents of resource dir
if let Some(res_dir) = &resource_dir {
println!("Listing contents of resource dir: {}", res_dir.display());
if let Ok(entries) = std::fs::read_dir(res_dir) {
for (i, entry) in entries.filter_map(Result::ok).enumerate() {
println!(" {}: {}", i, entry.path().display());
// List subdirectories
if entry.path().is_dir() {
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");
}
}
let filename = Path::new(&cursor.path)
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?;
println!("Extracted filename: {}", filename);
// Try using the actual file path directly
println!("Using direct file path: {}", cursor.path);
// Verify the file exists
if Path::new(&cursor.path).exists() {
println!("File exists, applying cursor...");
if let Err(e) = platform::set_system_cursor(&cursor.path) {
println!("Error applying cursor: {}", e);
return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e));
}
// Emit an event to notify the frontend
let _ = app_handle.emit_all("cursor_changed", index); // Ignore error
println!("Successfully applied cursor using actual file path");
return Ok(());
} else {
@@ -111,11 +112,11 @@ fn select_cursor(index: usize, state: tauri::State<AppStateWrapper>, app_handle:
#[tauri::command]
fn next_cursor(state: tauri::State<AppStateWrapper>, app_handle: AppHandle) -> Result<usize, String> {
println!("=== RESOURCE DEBUG [next_cursor] ===");
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];
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
println!("Extracted filename: {}", filename);
// Try using the actual file path directly
println!("Using direct file path: {}", cursor.path);
// Verify the file exists
if Path::new(&cursor.path).exists() {
println!("File exists, applying cursor...");
if let Err(e) = platform::set_system_cursor(&cursor.path) {
println!("Error applying cursor: {}", e);
return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e));
}
// Emit an event to notify the frontend
let _ = app_handle.emit_all("cursor_changed", app_state.current_index); // Ignore error
println!("Successfully applied cursor using actual file path");
return Ok(app_state.current_index);
} else {
@@ -161,22 +162,40 @@ fn quit_app(app_handle: AppHandle) {
Ok(_) => println!("Default cursor restored"),
Err(e) => eprintln!("Failed to restore default cursor: {}", e),
}
// Exit the application
std::thread::spawn(move || {
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
fn load_cursor_files() -> Vec<Cursor> {
println!("=== RESOURCE DEBUG [load_cursor_files] ===");
let mut cursors = Vec::new();
// Determine the path to src/cursors relative to src-tauri (CARGO_MANIFEST_DIR)
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
println!("CARGO_MANIFEST_DIR: {}", manifest_dir.display());
// Check multiple possible locations
let possible_cursor_dirs = vec![
manifest_dir.join("../src/cursors"),
@@ -184,26 +203,26 @@ fn load_cursor_files() -> Vec<Cursor> {
manifest_dir.join("cursors"),
manifest_dir.clone(), // Check manifest dir itself
];
println!("CHECKING FOR PNG/CUR FILES IN MULTIPLE DIRECTORIES:");
for (i, cursors_dir) in possible_cursor_dirs.iter().enumerate() {
println!("Checking dir {}: {}", i+1, cursors_dir.display());
println!(" Directory exists: {}", cursors_dir.exists());
if cursors_dir.exists() {
println!(" Looking for cursor files in this directory...");
if let Ok(entries) = std::fs::read_dir(cursors_dir) {
for entry in entries.filter_map(Result::ok) {
let path = entry.path();
println!(" Found: {}", path.display());
if path.is_file() && path.extension().map_or(false, |ext| ext == "png" || ext == "cur") {
let name = path.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("Unknown")
.to_string();
println!(" ADDING CURSOR: {} at path {}", name, path.display());
cursors.push(Cursor {
name,
@@ -212,14 +231,14 @@ fn load_cursor_files() -> Vec<Cursor> {
}
}
}
if !cursors.is_empty() {
println!(" Found {} cursor files in this directory.", cursors.len());
break;
}
}
}
// If we found no cursors, use mock data with paths relative to what resolve_resource expects
if cursors.is_empty() {
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 {
println!(" {}", dir.display());
}
// Mock data paths should reflect their eventual resource path structure
cursors.push(Cursor {
name: "Default".to_string(),
path: "src/cursors/default.png".to_string(), // Filename will be 'default.png'
});
cursors.push(Cursor {
name: "Icon".to_string(),
path: "src/cursors/icon.png".to_string(), // Filename will be 'icon.png'
});
cursors.push(Cursor {
name: "Red".to_string(),
path: "src/cursors/red.png".to_string(), // Filename will be 'red.png'
});
println!("Created {} mock cursor entries", cursors.len());
}
println!("Loaded {} cursor files", cursors.len());
// Sort cursors by name for consistent ordering
cursors.sort_by(|a, b| a.name.cmp(&b.name));
cursors
@@ -258,34 +277,153 @@ fn load_cursor_files() -> Vec<Cursor> {
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
println!("Starting Cursor Rush application");
// Initialize platform-specific cursor handling
if let Err(e) = platform::initialize() {
eprintln!("Failed to initialize platform: {}", e);
}
// Load cursors from assets directory
let cursors = load_cursor_files();
// Create app state
let app_state = AppState {
cursors,
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
tauri::Builder::default()
.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![
get_cursors,
get_current_cursor_index,
select_cursor,
next_cursor,
quit_app
quit_app,
minimize_to_tray
])
.setup(|_app| {
// Register for events or perform additional setup
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(())
})
// This matches exactly how it was before our changes
+14 -2
View File
@@ -14,7 +14,11 @@
"all": false,
"window": {
"all": false,
"startDragging": true
"startDragging": true,
"minimize": true,
"hide": true,
"show": true,
"close": true
},
"shell": {
"all": false,
@@ -27,6 +31,12 @@
]
}
},
"systemTray": {
"iconPath": "icons/icon.png",
"iconAsTemplate": false,
"menuOnLeftClick": false,
"title": "Cursor Rush"
},
"bundle": {
"active": true,
"identifier": "com.cursor-rush.dev",
@@ -47,13 +57,15 @@
},
"windows": [
{
"label": "main",
"title": "Cursor Rush",
"width": 310,
"height": 600,
"resizable": true,
"fullscreen": 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 { appWindow } from "@tauri-apps/api/window";
import "./App.css";
@@ -17,8 +17,37 @@ function App() {
const [activePage, setActivePage] = useState("cursors");
const [cursors, setCursors] = useState([]);
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(() => {
const loadCursors = async () => {
try {
@@ -26,7 +55,7 @@ function App() {
const loadedCursors = await invoke("get_cursors");
console.log("Loaded cursors:", loadedCursors);
setCursors(loadedCursors || []);
// Get current cursor index
const currentIndex = await invoke("get_current_cursor_index");
setCurrentCursorIndex(currentIndex);
@@ -47,6 +76,53 @@ function App() {
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
const selectCursor = async (index) => {
try {
@@ -60,7 +136,7 @@ function App() {
const nextCursor = async () => {
if (cursors.length === 0) return;
try {
console.log('Switching to 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
const quitApp = async () => {
try {
@@ -96,22 +162,25 @@ function App() {
return (
<div className="modal-container">
<div className="modal">
<Header
activePage={activePage}
<Header
activePage={activePage}
setActivePage={setActivePage}
minimizeToTray={minimizeToTray}
quitApp={quitApp}
settings={settings}
/>
<Content
<Content
activePage={activePage}
cursors={cursors}
currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor}
nextCursor={nextCursor}
settings={settings}
setSettings={setSettings}
/>
<Footer
<Footer
version="0.1.0"
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 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
const renderContent = () => {
switch (activePage) {
case 'inventory':
return <Inventory
return <Inventory
cursors={cursors}
currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor}
/>;
case 'cursors':
return (
<Cursors
<Cursors
cursors={cursors}
currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor}
@@ -29,9 +29,9 @@ function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCu
case 'crates':
return <Crates />;
case 'settings':
return <Settings />;
return <Settings settings={settings} setSettings={setSettings} />;
default:
return <Cursors
return <Cursors
cursors={cursors}
currentCursorIndex={currentCursorIndex}
selectCursor={selectCursor}
@@ -39,7 +39,7 @@ function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCu
/>;
}
};
return (
<div className="app-content">
{renderContent()}
+25 -10
View File
@@ -1,12 +1,12 @@
import React from 'react';
import './Header.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faGripVertical,
faBoxOpen,
faMousePointer,
faBoxes,
faCog,
import {
faGripVertical,
faBoxOpen,
faMousePointer,
faBoxes,
faCog,
faBoxArchive,
faBox,
faWarehouse,
@@ -16,7 +16,7 @@ import {
faBagShopping
} 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
const navItems = [
{ id: 'inventory', icon: faBagShopping, tooltip: 'Inventory' },
@@ -24,12 +24,23 @@ function Header({ activePage, setActivePage, minimizeToTray, quitApp }) {
{ id: 'crates', icon: faBoxOpen, tooltip: 'Crates' },
{ 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 (
<header className="app-header" data-tauri-drag-region>
<div className="tabs-container">
{navItems.map((item) => (
<div
<div
key={item.id}
className={`tab ${activePage === item.id ? 'active' : ''}`}
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>
<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>
</div>
@@ -1,7 +1,15 @@
import React from 'react';
import './Settings.css';
function Settings() {
function Settings({ settings, setSettings }) {
// Handler for checkbox changes
const handleSettingChange = (settingName) => {
setSettings({
...settings,
[settingName]: !settings[settingName]
});
};
return (
<div className="settings-container">
<h2 className="content-title">Settings</h2>
@@ -10,23 +18,35 @@ function Settings() {
<h3>Application Settings</h3>
<div className="setting-item">
<label>
<input type="checkbox" />
<input
type="checkbox"
checked={settings.startWithWindows}
onChange={() => handleSettingChange('startWithWindows')}
/>
Start with Windows
</label>
</div>
<div className="setting-item">
<label>
<input type="checkbox" checked />
<input
type="checkbox"
checked={settings.minimizeToTrayOnClose}
onChange={() => handleSettingChange('minimizeToTrayOnClose')}
/>
Minimize to tray on close
</label>
</div>
</div>
<div className="settings-group">
<h3>Cursor Settings</h3>
<div className="setting-item">
<label>
<input type="checkbox" checked />
<input
type="checkbox"
checked={settings.autoApplyCursorOnSelection}
onChange={() => handleSettingChange('autoApplyCursorOnSelection')}
/>
Auto-apply cursor on selection
</label>
</div>