From 5ec15e12f3f083631fad722d6d2c67183118bd69 Mon Sep 17 00:00:00 2001 From: Aether Date: Mon, 23 Feb 2026 21:32:13 +0000 Subject: [PATCH] Redesign overlay to Windows Start Menu style - compact bottom bar with expand - Windows-style search bar with rounded corners - Positioned at bottom center of screen - Logo button to expand/collapse (like Windows Start) - Search input with type-to-search - Expanded view shows Quick Access grid (6 items) - Categorized list (Tools, Plugins, System) - Keyboard shortcuts displayed - Active plugin indicators - Click-through when not focused - Session timer in header - Smooth expand/collapse animations --- src-tauri/src/window.rs | 88 ++++---- src/pages/Overlay.tsx | 431 ++++++++++++++++++++++++---------------- 2 files changed, 309 insertions(+), 210 deletions(-) diff --git a/src-tauri/src/window.rs b/src-tauri/src/window.rs index 26c5264..2e790e0 100644 --- a/src-tauri/src/window.rs +++ b/src-tauri/src/window.rs @@ -1,4 +1,4 @@ -use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowUrl}; +use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowUrl, Position, PhysicalPosition}; use tracing::info; pub fn create_main_window(app: &AppHandle) -> Window { @@ -16,43 +16,39 @@ pub fn create_main_window(app: &AppHandle) -> Window { } pub fn setup_main_window(window: &Window) { - // Additional window setup if needed info!("Main window setup complete"); } pub fn create_overlay_window(app: &AppHandle) { + // Get primary monitor size + let monitor = app.primary_monitor().unwrap(); + let monitor_size = monitor.as_ref().map(|m| m.size()).unwrap_or_else(|| tauri::PhysicalSize::new(1920, 1080)); + + // Position overlay at bottom center like Windows Start Menu + let window_width = 720.0; + let window_height = 600.0; // Expanded height + let x = (monitor_size.width as f64 - window_width) / 2.0; + let y = monitor_size.height as f64 - window_height - 20.0; // 20px from bottom + let window = WindowBuilder::new( app, "overlay", WindowUrl::App("/#/overlay".into()) ) .title("EU-Utility Overlay") - .inner_size(400.0, 600.0) - .position(100.0, 100.0) + .inner_size(window_width, window_height) + .position(x, y) .transparent(true) .decorations(false) .always_on_top(true) .skip_taskbar(true) .visible(false) + // Enable click-through when not focused + .focus() .build() .expect("Failed to create overlay window"); - // Make window click-through when not focused - // 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 with transparency and click-through"); + info!("Overlay window created at bottom center (Windows-style)"); } pub fn toggle_overlay_window(app: &AppHandle) { @@ -61,15 +57,42 @@ pub fn toggle_overlay_window(app: &AppHandle) { if is_visible { window.hide().ok(); + // Re-enable click-through + enable_click_through(&window, true); } else { window.show().ok(); window.set_always_on_top(true).ok(); window.set_focus().ok(); + // Disable click-through when active + enable_click_through(&window, false); } } else { create_overlay_window(app); if let Some(window) = app.get_window("overlay") { window.show().ok(); + enable_click_through(&window, false); + } + } +} + +fn enable_click_through(window: &Window, enable: bool) { + #[cfg(target_os = "windows")] + unsafe { + 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, WS_EX_NOACTIVATE}; + + let hwnd = HWND(window.hwnd().unwrap().0 as *mut _); + let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE); + + if enable { + // Enable click-through + SetWindowLongW(hwnd, GWL_EXSTYLE, + ex_style | WS_EX_LAYERED.0 as i32 | WS_EX_TRANSPARENT.0 as i32); + } else { + // Disable click-through + SetWindowLongW(hwnd, GWL_EXSTYLE, + ex_style & !(WS_EX_TRANSPARENT.0 as i32)); } } } @@ -79,34 +102,13 @@ pub fn show_overlay_window(app: &AppHandle) { window.show().ok(); window.set_always_on_top(true).ok(); window.set_focus().ok(); + enable_click_through(&window, false); } } pub fn hide_overlay_window(app: &AppHandle) { if let Some(window) = app.get_window("overlay") { window.hide().ok(); + enable_click_through(&window, true); } } - -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 8ca222e..1b7e759 100644 --- a/src/pages/Overlay.tsx +++ b/src/pages/Overlay.tsx @@ -1,50 +1,54 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { invoke } from '@tauri-apps/api/tauri' import { listen } from '@tauri-apps/api/event' import { + Search, LayoutDashboard, - Calculator, Target, TrendingUp, + Calculator, Settings, X, - GripHorizontal, + ChevronUp, + GripVertical, Zap, - Search, - Grid, - Crosshair, - Clock, BarChart3, - Layers + Clock, + Layers, + Maximize2, + Minimize2, + Grid, + Command } from 'lucide-react' -interface QuickAction { +interface MenuItem { id: string icon: React.ReactNode label: string + description?: string hotkey?: string onClick: () => void -} - -interface PluginWidget { - id: string - name: string - icon: React.ReactNode - active: boolean - onToggle: () => void + category: 'tools' | 'plugins' | 'system' } export default function Overlay() { const [isVisible, setIsVisible] = useState(false) - const [activePlugins, setActivePlugins] = useState([]) - const [sessionTime, setSessionTime] = useState(0) - const [currentPED, setCurrentPED] = useState(0) + const [isExpanded, setIsExpanded] = useState(false) const [searchQuery, setSearchQuery] = useState('') + const [activePlugins, setActivePlugins] = useState(['loot', 'skills']) + const [sessionTime, setSessionTime] = useState(0) + const inputRef = useRef(null) useEffect(() => { // Listen for overlay toggle const unlisten = listen('overlay:toggle', () => { - setIsVisible(prev => !prev) + setIsVisible(prev => { + const newVisible = !prev + if (newVisible && inputRef.current) { + setTimeout(() => inputRef.current?.focus(), 100) + } + return newVisible + }) }) // Session timer @@ -58,205 +62,298 @@ export default function Overlay() { } }, []) + useEffect(() => { + if (isVisible && !isExpanded && inputRef.current) { + inputRef.current.focus() + } + }, [isVisible]) + const formatTime = (seconds: number) => { 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')}` + return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}` } - const quickActions: QuickAction[] = [ + const menuItems: MenuItem[] = [ { id: 'calculator', icon: , - label: 'Calc', + label: 'Calculator', + description: 'Quick calculations', hotkey: 'Ctrl+Shift+C', - onClick: () => invoke('open_calculator') + onClick: () => invoke('open_calculator'), + category: 'tools' }, { id: 'loot-tracker', icon: , - label: 'Loot', + label: 'Loot Tracker', + description: 'Track hunting sessions', hotkey: 'Ctrl+Shift+L', - onClick: () => invoke('toggle_loot_tracker') + onClick: () => togglePlugin('loot'), + category: 'plugins' + }, + { + id: 'skill-tracker', + icon: , + label: 'Skill Tracker', + description: 'Monitor skill gains', + onClick: () => togglePlugin('skills'), + category: 'plugins' }, { id: 'price-check', icon: , - label: 'Prices', - onClick: () => invoke('open_price_checker') + label: 'Price Check', + description: 'Item market values', + onClick: () => invoke('open_price_checker'), + category: 'tools' }, { - 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: 'dashboard', + icon: , + label: 'Dashboard', + description: 'Main application window', + onClick: () => invoke('show_main_window'), + category: 'system' }, { id: 'settings', icon: , label: 'Settings', - onClick: () => invoke('show_settings_window') - } + description: 'Configure application', + onClick: () => invoke('show_settings_window'), + category: 'system' + }, ] - 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 togglePlugin = (pluginId: string) => { + setActivePlugins(prev => + prev.includes(pluginId) + ? prev.filter(id => id !== pluginId) + : [...prev, pluginId] + ) + } - const filteredPlugins = plugins.filter(p => - p.name.toLowerCase().includes(searchQuery.toLowerCase()) - ) + const filteredItems = searchQuery + ? menuItems.filter(item => + item.label.toLowerCase().includes(searchQuery.toLowerCase()) || + item.description?.toLowerCase().includes(searchQuery.toLowerCase()) + ) + : menuItems + + const groupedItems = filteredItems.reduce((acc, item) => { + if (!acc[item.category]) acc[item.category] = [] + acc[item.category].push(item) + return acc + }, {} as Record) if (!isVisible) return null return ( -
- {/* Overlay Container */} +
+ {/* Windows-Style Overlay Container */}
- {/* Header - Drag Handle */} + {/* Main Search Bar - Windows Style */}
-
-
- -
-
-

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" - /> -
-
+ {/* Search Input */} +
+ setSearchQuery(e.target.value)} + onFocus={() => setIsExpanded(true)} + placeholder="Type to search plugins, tools..." + className="w-full bg-transparent border-none text-white text-base placeholder:text-white/40 focus:outline-none" + style={{ fontSize: '15px' }} + /> +
- {/* Quick Actions Grid */} -
-

Quick Actions

-
- {quickActions.map(action => ( + {/* Right Side Actions */} +
+ {/* Session Timer */} +
+ + {formatTime(sessionTime)} +
+ + {/* Expand/Collapse */} - ))} -
-
- {/* Active Plugins */} -
-
-

Plugins

- {filteredPlugins.filter(p => p.active).length} active -
- -
- {filteredPlugins.map(plugin => ( -
setIsVisible(false)} + className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors" > -
-
- {plugin.icon} + + +
+
+ + {/* Expanded Menu - Windows Start Menu Style */} + {isExpanded && ( +
+ {/* Quick Actions Grid */} +
+
+ Quick Access +
+ {activePlugins.length} active
- - {plugin.name} -
- -
-
+ +
+ {menuItems.slice(0, 6).map((item) => ( + + ))}
- ))} -
-
- {/* Stats Footer */} -
-
-
-
- System Online + {/* Search Results or All Items */} +
+ {Object.entries(groupedItems).map(([category, items]) => ( +
+
+ + {category === 'tools' ? 'Tools' : category === 'plugins' ? 'Plugins' : 'System'} + +
+
+ {items.map((item) => ( + + ))} +
+
+ ))} + + {filteredItems.length === 0 && ( +
+ +

No results found for "{searchQuery}"

+
+ )} +
+ + {/* Footer */} +
+
+
+ EU-Utility V3.0 +
+
+ + Press + Ctrl+Shift+U + to toggle +
+
-
- {currentPED.toFixed(2)} - PED/h -
-
+ )}