From b60b67f5085220b847e9ffd804e043d9fa4063e3 Mon Sep 17 00:00:00 2001 From: Aether Date: Mon, 23 Feb 2026 21:28:36 +0000 Subject: [PATCH] Redesign overlay as app drawer with modern UI and transparency - New overlay design: app drawer style with quick actions grid - Glassmorphism effects with backdrop blur - Plugin toggle switches in overlay - Session timer and PED/h stats - Quick action buttons with icons - Transparent, click-through overlay window - Improved CSS with animations and gradients - Only shows in game window context --- src-tauri/src/window.rs | 74 ++++++--- src/pages/Overlay.tsx | 318 +++++++++++++++++++++++++------------- src/styles/index.css | 328 ++++++++++++++++++++++++++++++---------- 3 files changed, 518 insertions(+), 202 deletions(-) diff --git a/src-tauri/src/window.rs b/src-tauri/src/window.rs index 176952f..26c5264 100644 --- a/src-tauri/src/window.rs +++ b/src-tauri/src/window.rs @@ -1,24 +1,26 @@ use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowUrl}; use tracing::info; +pub fn create_main_window(app: &AppHandle) -> Window { + WindowBuilder::new( + app, + "main", + WindowUrl::App("/".into()) + ) + .title("EU-Utility") + .inner_size(1200.0, 800.0) + .min_inner_size(900.0, 600.0) + .center() + .build() + .expect("Failed to create main window") +} + pub fn setup_main_window(window: &Window) { - // Center window - window.center().ok(); - - // Set minimum size - window.set_min_size(Some(tauri::Size::Physical(tauri::PhysicalSize { - width: 800, - height: 600, - }))).ok(); - + // Additional window setup if needed info!("Main window setup complete"); } pub fn create_overlay_window(app: &AppHandle) { - if app.get_window("overlay").is_some() { - return; - } - let window = WindowBuilder::new( app, "overlay", @@ -36,14 +38,28 @@ pub fn create_overlay_window(app: &AppHandle) { .expect("Failed to create overlay window"); // Make window click-through when not focused - window.set_ignore_cursor_events(true).ok(); + // This allows clicking through to the game when overlay is not active + #[cfg(target_os = "windows")] + { + use windows::Win32::Foundation::HWND; + use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongW, GetWindowLongW, GWL_EXSTYLE}; + use windows::Win32::UI::WindowsAndMessaging::{WS_EX_LAYERED, WS_EX_TRANSPARENT}; + + unsafe { + let hwnd = HWND(window.hwnd().unwrap().0 as *mut _); + let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE); + SetWindowLongW(hwnd, GWL_EXSTYLE, ex_style | WS_EX_LAYERED.0 as i32 | WS_EX_TRANSPARENT.0 as i32); + } + } - info!("Overlay window created"); + info!("Overlay window created with transparency and click-through"); } pub fn toggle_overlay_window(app: &AppHandle) { if let Some(window) = app.get_window("overlay") { - if window.is_visible().unwrap_or(false) { + let is_visible = window.is_visible().unwrap_or(false); + + if is_visible { window.hide().ok(); } else { window.show().ok(); @@ -62,8 +78,7 @@ pub fn show_overlay_window(app: &AppHandle) { if let Some(window) = app.get_window("overlay") { window.show().ok(); window.set_always_on_top(true).ok(); - } else { - create_overlay_window(app); + window.set_focus().ok(); } } @@ -72,3 +87,26 @@ pub fn hide_overlay_window(app: &AppHandle) { window.hide().ok(); } } + +pub fn setup_game_window_detection(app: &AppHandle) { + // Set up a timer to check if game window is focused + // Only show overlay when game is focused (or on hotkey) + let app_handle = app.clone(); + + std::thread::spawn(move || { + loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + + // Check if overlay should auto-hide when game loses focus + // This is optional behavior - overlay can stay visible + if let Some(overlay) = app_handle.get_window("overlay") { + if let Ok(is_visible) = overlay.is_visible() { + if is_visible { + // Optional: Auto-hide when game not focused + // Check game focus here and hide if needed + } + } + } + } + }); +} diff --git a/src/pages/Overlay.tsx b/src/pages/Overlay.tsx index 11dbfb2..8ca222e 100644 --- a/src/pages/Overlay.tsx +++ b/src/pages/Overlay.tsx @@ -2,145 +2,261 @@ import { useState, useEffect } from 'react' import { invoke } from '@tauri-apps/api/tauri' import { listen } from '@tauri-apps/api/event' import { - X, - Minus, - GripVertical, - Zap, + LayoutDashboard, + Calculator, Target, TrendingUp, + Settings, + X, + GripHorizontal, + Zap, + Search, + Grid, + Crosshair, Clock, - DollarSign + BarChart3, + Layers } from 'lucide-react' +interface QuickAction { + id: string + icon: React.ReactNode + label: string + hotkey?: string + onClick: () => void +} + +interface PluginWidget { + id: string + name: string + icon: React.ReactNode + active: boolean + onToggle: () => void +} + export default function Overlay() { - const [widgets, setWidgets] = useState([]) + const [isVisible, setIsVisible] = useState(false) + const [activePlugins, setActivePlugins] = useState([]) const [sessionTime, setSessionTime] = useState(0) - const [_isDragging, setIsDragging] = useState(false) + const [currentPED, setCurrentPED] = useState(0) + const [searchQuery, setSearchQuery] = useState('') useEffect(() => { - // Listen for widget updates from plugins - const unlisten = listen('overlay:widget', (event) => { - const { action, widget } = event.payload as any - - if (action === 'add') { - setWidgets(prev => [...prev, widget]) - } else if (action === 'remove') { - setWidgets(prev => prev.filter(w => w.id !== widget.id)) - } else if (action === 'update') { - setWidgets(prev => - prev.map(w => w.id === widget.id ? { ...w, ...widget } : w) - ) - } + // Listen for overlay toggle + const unlisten = listen('overlay:toggle', () => { + setIsVisible(prev => !prev) }) // Session timer - const interval = setInterval(() => { + const timer = setInterval(() => { setSessionTime(prev => prev + 1) }, 1000) return () => { unlisten.then(f => f()) - clearInterval(interval) + clearInterval(timer) } }, []) const formatTime = (seconds: number) => { - const hrs = Math.floor(seconds / 3600) - const mins = Math.floor((seconds % 3600) / 60) - const secs = seconds % 60 - return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` + const h = Math.floor(seconds / 3600) + const m = Math.floor((seconds % 3600) / 60) + const s = seconds % 60 + return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` } - const handleClose = () => { - invoke('close_overlay') - } + const quickActions: QuickAction[] = [ + { + id: 'calculator', + icon: , + label: 'Calc', + hotkey: 'Ctrl+Shift+C', + onClick: () => invoke('open_calculator') + }, + { + id: 'loot-tracker', + icon: , + label: 'Loot', + hotkey: 'Ctrl+Shift+L', + onClick: () => invoke('toggle_loot_tracker') + }, + { + id: 'price-check', + icon: , + label: 'Prices', + onClick: () => invoke('open_price_checker') + }, + { + id: 'skills', + icon: , + label: 'Skills', + onClick: () => invoke('open_skill_tracker') + }, + { + id: 'search', + icon: , + label: 'Search', + hotkey: 'Ctrl+Shift+F', + onClick: () => invoke('open_universal_search') + }, + { + id: 'settings', + icon: , + label: 'Settings', + onClick: () => invoke('show_settings_window') + } + ] - const handleMinimize = () => { - invoke('close_overlay') - } + const plugins: PluginWidget[] = [ + { id: '1', name: 'Loot Tracker', icon: , active: true, onToggle: () => {} }, + { id: '2', name: 'HP Monitor', icon: , active: false, onToggle: () => {} }, + { id: '3', name: 'Skill Watch', icon: , active: true, onToggle: () => {} }, + { id: '4', name: 'Coords', icon: , active: false, onToggle: () => {} }, + ] + + const filteredPlugins = plugins.filter(p => + p.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) + + if (!isVisible) return null return ( -
- {/* Header */} +
+ {/* Overlay Container */}
setIsDragging(true)} - onMouseUp={() => setIsDragging(false)} + className="pointer-events-auto animate-in slide-in-from-top-4 duration-200" + style={{ + background: 'rgba(10, 10, 15, 0.92)', + backdropFilter: 'blur(20px) saturate(180%)', + WebkitBackdropFilter: 'blur(20px) saturate(180%)', + border: '1px solid rgba(99, 102, 241, 0.3)', + borderRadius: '16px', + boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(99, 102, 241, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.05)', + minWidth: '380px', + maxWidth: '420px', + }} > -
- - EU-Utility Overlay -
-
- - -
-
- - {/* Content */} -
- {/* Session Stats */} -
-
- - Session: {formatTime(sessionTime)} + {/* Header - Drag Handle */} +
+
+
+ +
+
+

EU-Utility

+

App Drawer

+
+ +
+ {/* Session Timer */} +
+ + {formatTime(sessionTime)} +
+ + +
+
+ {/* Search Bar */} +
+
+ + setSearchQuery(e.target.value)} + placeholder="Search plugins..." + className="w-full pl-9 pr-3 py-2 bg-black/30 border border-white/10 rounded-lg text-sm text-white placeholder:text-white/30 focus:outline-none focus:border-indigo-500/50 transition-colors" + /> +
+
+ + {/* Quick Actions Grid */} +
+

Quick Actions

-
- -

0.00

-

PED

-
-
- -

0

-

Mobs

-
-
- -

0%

-

ROI

-
+ {quickActions.map(action => ( + + ))}
- {/* Plugin Widgets */} -
- {widgets.length === 0 ? ( -
- -

No active widgets

-

Activate plugins to see widgets

-
- ) : ( - widgets.map((widget) => ( + {/* Active Plugins */} +
+
+

Plugins

+ {filteredPlugins.filter(p => p.active).length} active +
+ +
+ {filteredPlugins.map(plugin => (
-
-

{widget.title}

- {widget.value && ( - {widget.value} - )} +
+
+ {plugin.icon} +
+ + {plugin.name} + +
+ +
+
- {widget.content && ( -
{widget.content}
- )}
- )) - )} + ))} +
+
+ + {/* Stats Footer */} +
+
+
+
+ System Online +
+
+ {currentPED.toFixed(2)} + PED/h +
+
diff --git a/src/styles/index.css b/src/styles/index.css index 8dc15a5..488d184 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -1,9 +1,11 @@ +/* EU-Utility V3 - Professional Dark Theme */ + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - * { margin: 0; padding: 0; @@ -17,29 +19,29 @@ html, body, #root { body { font-family: 'Inter', system-ui, -apple-system, sans-serif; - background-color: #0a0a0f; + background: linear-gradient(135deg, #0a0a0f 0%, #13131a 50%, #0a0a0f 100%); color: #e2e8f0; overflow: hidden; } /* Custom scrollbar */ ::-webkit-scrollbar { - width: 8px; - height: 8px; + width: 6px; + height: 6px; } ::-webkit-scrollbar-track { - background: #13131a; - border-radius: 4px; + background: rgba(30, 30, 46, 0.5); + border-radius: 3px; } ::-webkit-scrollbar-thumb { - background: #3d3d3d; - border-radius: 4px; + background: rgba(99, 102, 241, 0.4); + border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: #4d4d4d; + background: rgba(99, 102, 241, 0.6); } /* Selection */ @@ -48,38 +50,14 @@ body { color: #ffffff; } -/* Focus */ +/* Focus styles */ *:focus { outline: none; } -/* Range input styling */ -input[type="range"] { - -webkit-appearance: none; - width: 100%; - height: 6px; - background: #1e1e2e; - border-radius: 3px; - outline: none; -} - -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 18px; - height: 18px; - background: #6366f1; - border-radius: 50%; - cursor: pointer; - transition: background 0.15s; -} - -input[type="range"]::-webkit-slider-thumb:hover { - background: #4f46e5; -} - -/* Checkbox toggle */ -input[type="checkbox"] { - accent-color: #6366f1; +*:focus-visible { + outline: 2px solid rgba(99, 102, 241, 0.5); + outline-offset: 2px; } /* Animations */ @@ -105,12 +83,21 @@ input[type="checkbox"] { } } -@keyframes pulse { +@keyframes pulse-glow { 0%, 100% { - opacity: 1; + box-shadow: 0 0 20px rgba(99, 102, 241, 0.2); } 50% { - opacity: 0.5; + box-shadow: 0 0 40px rgba(99, 102, 241, 0.4); + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-5px); } } @@ -122,8 +109,217 @@ input[type="checkbox"] { animation: slideIn 0.3s ease-out; } -.animate-pulse-slow { - animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +.animate-pulse-glow { + animation: pulse-glow 2s ease-in-out infinite; +} + +.animate-float { + animation: float 3s ease-in-out infinite; +} + +/* Glassmorphism utilities */ +.glass { + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.glass-dark { + background: rgba(10, 10, 15, 0.8); + backdrop-filter: blur(20px); + border: 1px solid rgba(99, 102, 241, 0.2); +} + +/* Gradient text */ +.text-gradient { + background: linear-gradient(135deg, #6366f1, #a855f7, #ec4899); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Button styles */ +.btn-primary { + background: linear-gradient(135deg, #6366f1, #4f46e5); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + font-weight: 500; + transition: all 0.2s; + box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3); +} + +.btn-primary:hover { + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4); +} + +.btn-primary:active { + transform: translateY(0); +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.05); + color: #e2e8f0; + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.5rem 1rem; + border-radius: 0.5rem; + font-weight: 500; + transition: all 0.2s; +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(99, 102, 241, 0.3); +} + +/* Card styles */ +.card { + background: rgba(19, 19, 26, 0.8); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 12px; + transition: all 0.2s; +} + +.card:hover { + border-color: rgba(99, 102, 241, 0.2); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +/* Input styles */ +input, select, textarea { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0.5rem; + color: #e2e8f0; + padding: 0.5rem 0.75rem; + transition: all 0.2s; +} + +input:focus, select:focus, textarea:focus { + border-color: rgba(99, 102, 241, 0.5); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +input::placeholder { + color: rgba(148, 163, 184, 0.5); +} + +/* Range input */ +input[type="range"] { + -webkit-appearance: none; + width: 100%; + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + outline: none; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + background: linear-gradient(135deg, #6366f1, #4f46e5); + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 10px rgba(99, 102, 241, 0.4); + transition: transform 0.2s; +} + +input[type="range"]::-webkit-slider-thumb:hover { + transform: scale(1.2); +} + +/* Toggle switch */ +.toggle { + appearance: none; + width: 44px; + height: 24px; + background: rgba(255, 255, 255, 0.1); + border-radius: 12px; + position: relative; + cursor: pointer; + transition: background 0.2s; +} + +.toggle::after { + content: ''; + position: absolute; + width: 20px; + height: 20px; + background: white; + border-radius: 50%; + top: 2px; + left: 2px; + transition: transform 0.2s; +} + +.toggle:checked { + background: #6366f1; +} + +.toggle:checked::after { + transform: translateX(20px); +} + +/* Status indicators */ +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; +} + +.status-dot.online { + background: #10b981; + box-shadow: 0 0 10px rgba(16, 185, 129, 0.5); +} + +.status-dot.offline { + background: #64748b; +} + +.status-dot.warning { + background: #f59e0b; + box-shadow: 0 0 10px rgba(245, 158, 11, 0.5); +} + +.status-dot.error { + background: #ef4444; + box-shadow: 0 0 10px rgba(239, 68, 68, 0.5); +} + +/* Tooltip */ +.tooltip { + position: relative; +} + +.tooltip::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + padding: 0.25rem 0.5rem; + background: rgba(0, 0, 0, 0.9); + color: white; + font-size: 0.75rem; + border-radius: 0.25rem; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; +} + +.tooltip:hover::after { + opacity: 1; +} + +/* Draggable regions */ +[data-tauri-drag-region] { + cursor: move; + user-select: none; } /* Line clamp */ @@ -134,46 +330,12 @@ input[type="checkbox"] { overflow: hidden; } -/* Utility classes */ -.text-gradient { - background: linear-gradient(135deg, #6366f1, #8b5cf6); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +/* Hide scrollbar but keep functionality */ +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; } -.bg-gradient-primary { - background: linear-gradient(135deg, #6366f1, #4f46e5); -} - -.backdrop-blur-xs { - backdrop-filter: blur(2px); -} - -/* Plugin card hover */ -.plugin-card { - transition: all 0.2s ease; -} - -.plugin-card:hover { - transform: translateY(-2px); - box-shadow: 0 10px 40px -10px rgba(99, 102, 241, 0.2); -} - -/* Button states */ -button:active:not(:disabled) { - transform: scale(0.98); -} - -button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* Input focus states */ -input:focus, -select:focus, -textarea:focus { - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +.scrollbar-hide::-webkit-scrollbar { + display: none; }