What do you need to change to make this run on ONNX + DirectML

import sys
import os
import json
import time
import threading
import logging
import numpy as np
import cv2
import mss
import mouse
import torch
import ctypes
import keyboard
import math
import torch_directml
import webbrowser
from collections import deque
import sort
from sort import Sort
from ultralytics import YOLO
from PyQt5 import QtCore, QtGui, QtWidgets

class LoginDialog(QtWidgets.QDialog):
    def __init__(self, correct_key, parent=None):
        super().__init__(parent)
        self.correct_key = correct_key
        self.result = False

        self.setup_ui()

    def setup_ui(self):
        self.setWindowTitle("Enter Key")
        self.setModal(True)
        self.resize(400, 200)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        # Central container with styling
        self.container = QtWidgets.QWidget(self)
        self.container.setGeometry(10, 10, 380, 180)
        self.container.setStyleSheet(self.container_style())

        layout = QtWidgets.QVBoxLayout(self.container)
        layout.setContentsMargins(20, 20, 20, 20)
        layout.setSpacing(15)

        # Label
        self.label = QtWidgets.QLabel("Enter your license key or password:", self.container)
        self.label.setStyleSheet("color: white; font-size: 14px;")
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        layout.addWidget(self.label)

        # Input
        self.input = QtWidgets.QLineEdit(self.container)
        self.input.setEchoMode(QtWidgets.QLineEdit.Password)
        self.input.setStyleSheet(self.input_style())
        layout.addWidget(self.input)

        # Button
        self.button = QtWidgets.QPushButton("Unlock", self.container)
        self.button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.button.setStyleSheet(self.button_style())
        self.button.clicked.connect(self.check_key)
        layout.addWidget(self.button)

        self.input.setFocus()

    def container_style(self):
        return """
            QWidget {
                background-color: rgba(0, 0, 0, 180);
                border: 2px solid purple;
                border-radius: 15px;
            }
        """

    def input_style(self):
        return """
            QLineEdit {
                background-color: rgba(255, 255, 255, 30);
                color: white;
                border: 1px solid purple;
                border-radius: 8px;
                padding: 5px;
                font-size: 14px;
            }
        """

    def button_style(self):
        return """
            QPushButton {
                background-color: purple;
                color: white;
                border-radius: 8px;
                padding: 8px;
                font-weight: bold;
                font-size: 14px;
            }
            QPushButton:hover {
                background-color: #800080;
            }
        """

    def check_key(self):
        entered = self.input.text().strip()
        if entered == self.correct_key:
            self.result = True
            self.accept()
        else:
            QtWidgets.QMessageBox.warning(self, "Access Denied", "Incorrect license key or password.")

    def keyPressEvent(self, event):
        if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
            self.check_key()
        else:
            super().keyPressEvent(event)

    @staticmethod
    def get_login(correct_key):
        app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
        dialog = LoginDialog(correct_key)
        if dialog.exec_() == QtWidgets.QDialog.Accepted and dialog.result:
            return True
        return False

# Usage
if __name__ == "__main__":
    CORRECT_KEY = "2011UD"
    if not LoginDialog.get_login(CORRECT_KEY):
        sys.exit(0)

# --- SETTINGS AND LOGGING SETUP ---
APPNAME = "UD Premium"
SETTINGS_FILE = "ud_premium_settings.json"
LOG_FILE = "ud_premium.log"
BASE_PATH = os.path.dirname(os.path.abspath(__file__))
SETTINGS_PATH = os.path.join(BASE_PATH, SETTINGS_FILE)
LOG_PATH = os.path.join(BASE_PATH, LOG_FILE)
YOLO_PT_PATH = os.path.join(BASE_PATH, "yolo11n.pt")

if not os.path.exists(LOG_PATH):
    with open(LOG_PATH, "w"): pass

logging.basicConfig(
    filename=LOG_PATH,
    level=logging.INFO,
    format="%(asctime)s %(levelname)s: %(message)s"
)

DEFAULT_SETTINGS = {
    "fov_radius": 200,
    "fov_enabled": True,
    "fov_color": "#B366FF",
    "fov_thickness": 0.1,
    "fov_opacity": 0.0,    # Default see-through for FOV
    "aim_enabled": True,
    "aim_hotkey": "Right Mouse Button",
    "aim_hotkey_code": None,
    "aim_move_speed": 0.8,
    "aim_smoothing": 0.45,
    "aim_sticky_max": 35,
    "aim_sticky_decay": 0.85,
    "aim_sticky_boost": 1.55,
    "aim_prediction_factor": 0.1,
    "aim_prioritize_head": True,
    "aim_max_move": 35,
    "silent_aim_enabled": False,
    "silent_aim_hotkey": "Q",
    "silent_aim_speed": 1.0,
    "esp_enabled": True,
    "esp_box_color": "#B366FF",
    "esp_fill_color": "#7E3FF2",
    "esp_line_color": "#B366FF",
    "esp_box_style": "Box",
    "esp_show_lines": True,
    "esp_font_size": 12,
    "esp_box_thickness": 1,
    "esp_line_thickness": 1,
    "confidence": 0.5,
    "imgsz": 416,
    "detection_fps": 60,
    "ui_theme": "dark",
    "ui_font": "Segoe UI",
    "ui_font_size": 11,
    "window_transparency": 0.87,   # Main window opacity (see-through)
    "trigger_enabled": False,
    "trigger_auto_fire": False,
    "trigger_fire_speed": 150,
    "trigger_tolerance": 2,
    "trigger_hotkey": "Left Mouse Button",
    "trigger_burst": False,
    "trigger_burst_count": 3,
    "trigger_burst_delay": 60,
    "trigger_mouse_button": "left",
    "menu_shortcut": "F8",
    # --- RECOIL SETTINGS ---
    "recoil_enabled": False,
    "recoil_strength": 50,
    "recoil_jitter": 5,
    "recoil_pattern": "Default",
    "recoil_activation_key": "Left Mouse Button",
    "recoil_delay": 12,
    "recoil_humanize": 6,
    "recoil_side": 0,
    "recoil_custom_pattern": [],
}

def load_settings():
    if os.path.exists(SETTINGS_PATH):
        try:
            with open(SETTINGS_PATH, "r") as f:
                data = json.load(f)
            for k, v in DEFAULT_SETTINGS.items():
                if k not in data:
                    data[k] = v
            return data
        except Exception as e:
            logging.error(f"Failed to load settings: {e}")
    return DEFAULT_SETTINGS.copy()

def save_settings(settings):
    try:
        with open(SETTINGS_PATH, "w") as f:
            json.dump(settings, f, indent=2)
    except Exception as e:
        logging.error(f"Failed to save settings: {e}")

def export_settings(settings, parent=None):
    fname, _ = QtWidgets.QFileDialog.getSaveFileName(parent, "Export Settings", "", "JSON Files (*.json)")
    if fname:
        try:
            with open(fname, "w") as f:
                json.dump(settings, f, indent=2)
            QtWidgets.QMessageBox.information(parent, "Exported", "Settings exported successfully.")
        except Exception as e:
            QtWidgets.QMessageBox.warning(parent, "Error", f"Could not export settings:\n{e}")

def import_settings(parent=None):
    fname, _ = QtWidgets.QFileDialog.getOpenFileName(parent, "Import Settings", "", "JSON Files (*.json)")
    if fname:
        try:
            with open(fname, "r") as f:
                new_settings = json.load(f)
            for k, v in DEFAULT_SETTINGS.items():
                if k not in new_settings:
                    new_settings[k] = v
            QtWidgets.QMessageBox.information(parent, "Imported", "Settings imported. Restart app for all changes.")
            return new_settings
        except Exception as e:
            QtWidgets.QMessageBox.warning(parent, "Error", f"Could not import settings:\n{e}")
    return None

# --- UTILS ---
def parse_qcolor(colorstr, fallback="#B366FF"):
    try:
        if isinstance(colorstr, str) and colorstr.startswith("#") and len(colorstr) == 7:
            return QtGui.QColor(colorstr)
        else:
            return QtGui.QColor(fallback)
    except Exception:
        return QtGui.QColor(fallback)

def clamp(val, minv, maxv):
    return max(minv, min(val, maxv))

def move_mouse_relative(dx, dy):
    import ctypes
    INPUT_MOUSE = 0
    MOUSEEVENTF_MOVE = 0x0001
    class MOUSEINPUT(ctypes.Structure):
        _fields_ = [("dx", ctypes.c_long), ("dy", ctypes.c_long), ("mouseData", ctypes.c_ulong),
                    ("dwFlags", ctypes.c_ulong), ("time", ctypes.c_ulong), ("dwExtraInfo", ctypes.POINTER(ctypes.c_ulong))]
    class INPUT(ctypes.Structure):
        _fields_ = [("type", ctypes.c_ulong), ("mi", MOUSEINPUT)]
    extra = ctypes.c_ulong(0)
    mi = MOUSEINPUT(dx, dy, 0, MOUSEEVENTF_MOVE, 0, ctypes.pointer(extra))
    inp = INPUT(INPUT_MOUSE, mi)
    ctypes.windll.user32.SendInput(1, ctypes.byref(inp), ctypes.sizeof(inp))

def mouse_click_down_up(button='left'):
    mouse.press(button=button)
    time.sleep(0.01)
    mouse.release(button=button)

def mouse_press_down(button='left'):
    mouse.press(button=button)

def mouse_release_up(button='left'):
    mouse.release(button=button)

# --- YOLO SINGLETON ---
class YOLOEnsemble:
    _instance = None
    _lock = threading.Lock()

    def __init__(self, models_config):
        self.models = []
        self.names = {}
        self.prediction_cache = {}
        self.cache_lock = threading.Lock()
        self.device = self._setup_directml_device()
        self._load_models(models_config)

    def _setup_directml_device(self):
        """Setup DirectML for maximum AMD GPU performance with fallbacks"""
        try:
            # Try DirectML first
            if torch_directml.is_available():
                device_count = torch_directml.device_count()
                logging.info(f"DirectML available with {device_count} device(s)")
                
                if device_count > 0:
                    # Get the best DirectML device (usually 0)
                    dml_device = torch_directml.device(0)
                    
                    # Maximum DirectML optimizations
                    torch.backends.directml.enabled = True
                    torch.backends.directml.allow_reduced_precision = True
                    
                    logging.info(f"DirectML device initialized: {dml_device}")
                    return dml_device
            
            # Fallback to CUDA if available
            if torch.cuda.is_available():
                cuda_device = torch.device('cuda:0')
                logging.info(f"CUDA device initialized: {cuda_device}")
                return cuda_device
            
            # Final fallback to CPU with warning
            logging.warning("No GPU available, falling back to CPU (performance will be reduced)")
            return torch.device('cpu')
                
        except Exception as e:
            logging.warning(f"GPU setup failed ({e}), falling back to CPU")
            return torch.device('cpu')

    def _load_models(self, models_config):
        """Load models with DirectML optimization for maximum performance"""
        for model_path, imgsz in models_config:
            try:
                # Load YOLO model
                model = YOLO(model_path)
                
                # Force model to DirectML device for 100% GPU utilization
                model.model = model.model.to(self.device)
                
                # Set model to evaluation mode and optimize
                model.model.eval()
                
                # Enable DirectML optimizations
                if hasattr(torch.backends, 'directml') and torch.backends.directml.enabled:
                    # Compile model for maximum DirectML performance
                    try:
                        model.model = torch.jit.optimize_for_inference(model.model)
                        logging.info(f"Model JIT optimized for DirectML: {model_path}")
                    except:
                        logging.info(f"JIT optimization skipped for: {model_path}")
                
                # Zero-latency warmup with DirectML - more aggressive
                dummy = torch.zeros((1, 3, imgsz, imgsz), dtype=torch.float32, device=self.device)
                
                with torch.no_grad():
                    # More aggressive warmup - 15 iterations for maximum DirectML optimization
                    for _ in range(15):
                        try:
                            _ = model.model(dummy)
                        except:
                            # Fallback warmup using predict
                            dummy_np = np.zeros((imgsz, imgsz, 3), dtype=np.uint8)
                            model.predict(
                                source=dummy_np,
                                imgsz=imgsz,
                                device=self.device,
                                verbose=False,
                                stream=False
                            )
                            break
                
                # Clear cache for maximum performance (if DirectML available)
                if hasattr(torch, 'directml') and str(self.device).startswith('privateuseone'):
                    torch.directml.empty_cache()
                elif torch.cuda.is_available() and self.device.type == 'cuda':
                    torch.cuda.empty_cache()
                
                self.models.append((model, imgsz))
                if hasattr(model, "names"):
                    self.names.update(model.names)
                    
                logging.info(f"DirectML model loaded: {model_path} -> {imgsz}px on {self.device}")
                
            except Exception as e:
                logging.error(f"Failed to load model {model_path}: {e}")

    def predict(self, frame, conf_thres=0.3):
        """Ultra-high performance DirectML prediction with zero latency"""
        all_results = []
        original_height, original_width = frame.shape[:2]
        
        # Process all models with DirectML for maximum GPU utilization
        with torch.no_grad():  # Disable gradients for inference speed
            for model, imgsz in self.models:
                try:
                    # Calculate scale factors for coordinate transformation
                    scale_x = original_width / imgsz
                    scale_y = original_height / imgsz
                    
                    # Ultra-fast resize with optimized interpolation
                    resized_frame = cv2.resize(frame, (imgsz, imgsz), interpolation=cv2.INTER_LINEAR)
                    
                    # Maximum performance prediction with DirectML
                    results = model.predict(
                        source=resized_frame,
                        save=False,
                        imgsz=imgsz,
                        conf=conf_thres,
                        iou=0.15,  # More aggressive NMS for speed
                        device=self.device,
                        half=False,  # DirectML works better with float32
                        verbose=False,
                        stream=False,
                        agnostic_nms=True,
                        max_det=40,  # Slightly higher detection count
                        classes=[0],  # Person class only
                        retina_masks=False
                    )[0]
                    
                    # Store transformation data
                    results.scale_x = scale_x
                    results.scale_y = scale_y
                    results.original_width = original_width
                    results.original_height = original_height
                    
                    all_results.append(results)
                    
                except Exception as e:
                    logging.warning(f"DirectML prediction failed: {e}")
                    continue
        
        # No cache clearing to maintain maximum GPU utilization
        # Only clear if memory is getting full
        if hasattr(torch, 'directml') and str(self.device).startswith('privateuseone'):
            pass  # Keep DirectML memory allocated
        elif torch.cuda.is_available() and self.device.type == 'cuda':
            pass  # Keep CUDA memory allocated
        return all_results

    @classmethod
    def get_instance(cls, models_config):
        with cls._lock:
            if cls._instance is None:
                cls._instance = YOLOEnsemble(models_config)
            return cls._instance

def clamp(value, min_val, max_val):
    """Zero-overhead clamp function"""
    return min_val if value < min_val else max_val if value > max_val else value

class DetectionThread(QtCore.QThread):
    detection_signal = QtCore.pyqtSignal(list, list, list, list, list)

    def __init__(self, settings):
        super().__init__()
        self.settings = settings
        self.running = False
        self.frame_lock = threading.Lock()
        
        # BitBlt setup
        self.user32 = ctypes.windll.user32
        self.gdi32 = ctypes.windll.gdi32
        self.dc = None
        self.memdc = None
        self.bitmap = None
        self.bitmapinfo = None
        
        # Ultra-fast tracker with minimal overhead
        self.tracker = Sort(max_age=10, min_hits=1, iou_threshold=0.2)
        self.frame_skip = 0
        self.last_detection_time = 0
        
        # Screen cache
        self.screen_width = 0
        self.screen_height = 0
        self.center_x = 0
        self.center_y = 0        # Load DirectML optimized models
        model_paths = [
            ("models/BestMain.pt", 96),
            ("models/yolo11n.pt", 128)
        ]
        

        self.yolo_ensemble = YOLOEnsemble.get_instance(model_paths)

    def setup_bitblt(self):
        """Initialize BitBlt capture resources"""
        try:
            # Get desktop window handle
            hwnd = self.user32.GetDesktopWindow()
            
            # Get DC and create compatible DC
            self.dc = self.user32.GetDC(hwnd)
            self.memdc = self.gdi32.CreateCompatibleDC(self.dc)
            
            # Create bitmap info structure
            class BITMAPINFOHEADER(ctypes.Structure):
                _fields_ = [
                    ("biSize", ctypes.c_uint32),
                    ("biWidth", ctypes.c_int),
                    ("biHeight", ctypes.c_int),
                    ("biPlanes", ctypes.c_short),
                    ("biBitCount", ctypes.c_short),
                    ("biCompression", ctypes.c_uint32),
                    ("biSizeImage", ctypes.c_uint32),
                    ("biXPelsPerMeter", ctypes.c_int),
                    ("biYPelsPerMeter", ctypes.c_int),
                    ("biClrUsed", ctypes.c_uint32),
                    ("biClrImportant", ctypes.c_uint32)
                ]

            self.bitmapinfo = BITMAPINFOHEADER()
            self.bitmapinfo.biSize = ctypes.sizeof(BITMAPINFOHEADER)
            self.bitmapinfo.biPlanes = 1
            self.bitmapinfo.biBitCount = 32
            self.bitmapinfo.biCompression = 0
            
            return True
            
        except Exception as e:
            logging.error(f"BitBlt setup failed: {e}")
            return False

    def capture_screen_region(self, x, y, width, height):
        """Capture screen region using BitBlt"""
        try:
            # Update bitmap info
            self.bitmapinfo.biWidth = width
            self.bitmapinfo.biHeight = -height  # Negative for top-down
            self.bitmapinfo.biSizeImage = width * height * 4
            
            # Create compatible bitmap
            self.bitmap = self.gdi32.CreateCompatibleBitmap(self.dc, width, height)
            self.gdi32.SelectObject(self.memdc, self.bitmap)
            
            # Perform BitBlt operation
            result = self.gdi32.BitBlt(
                self.memdc, 0, 0, width, height,
                self.dc, x, y,
                0x00CC0020  # SRCCOPY
            )
            
            if not result:
                raise Exception("BitBlt failed")
            
            # Get bitmap bits
            buffer_size = width * height * 4
            buffer = (ctypes.c_byte * buffer_size)()
            
            self.gdi32.GetDIBits(
                self.memdc, self.bitmap, 0, height,
                buffer, ctypes.byref(self.bitmapinfo), 0
            )
            
            # Convert to numpy array
            array = np.frombuffer(buffer, dtype=np.uint8)
            array = array.reshape(height, width, 4)
            
            # Cleanup current bitmap
            self.gdi32.DeleteObject(self.bitmap)
            
            return array
            
        except Exception as e:
            logging.error(f"BitBlt capture failed: {e}")
            return None

    def cleanup_bitblt(self):
        """Clean up BitBlt resources"""
        try:
            if self.bitmap:
                self.gdi32.DeleteObject(self.bitmap)
            if self.memdc:
                self.gdi32.DeleteDC(self.memdc)
            if self.dc:
                self.user32.ReleaseDC(None, self.dc)
        except Exception as e:
            logging.error(f"BitBlt cleanup failed: {e}")

    def fast_nms(self, boxes, scores, iou_threshold=0.25):
        """Ultra-fast NMS implementation with minimal overhead"""
        if len(boxes) == 0:
            return []

        # Convert to numpy arrays for vectorized operations
        boxes = np.asarray(boxes, dtype=np.float32)
        scores = np.asarray(scores, dtype=np.float32)

        # Get sorted indices by score (descending)
        indices = np.argsort(scores)[::-1]
        
        keep = []
        while indices.size > 0:
            # Take the highest scoring box
            current = indices[0]
            keep.append(current)
            
            if indices.size == 1:
                break
            
            # Calculate IoU with vectorized operations
            remaining_indices = indices[1:]
            current_box = boxes[current]
            remaining_boxes = boxes[remaining_indices]
            
            # Fast IoU calculation
            ious = self._fast_iou(current_box, remaining_boxes)
            
            # Keep only boxes with IoU below threshold
            indices = remaining_indices[ious <= iou_threshold]
        
        return keep
    
    def _fast_iou(self, box, boxes):
        """Vectorized IoU calculation with minimal overhead"""
        # Intersection coordinates
        x1 = np.maximum(box[0], boxes[:, 0])
        y1 = np.maximum(box[1], boxes[:, 1])
        x2 = np.minimum(box[2], boxes[:, 2])
        y2 = np.minimum(box[3], boxes[:, 3])
        
        # Intersection area
        intersection = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)
        
        # Box areas
        box_area = (box[2] - box[0]) * (box[3] - box[1])
        boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
        
        # Union and IoU
        union = box_area + boxes_area - intersection
        return intersection / np.maximum(union, 1e-10)

    def transform_coordinates(self, boxes, crop_x, crop_y, scale_x, scale_y):
        """Zero-latency coordinate transformation"""
        transformed_boxes = []
        
        for box in boxes:
            x1, y1, x2, y2 = box
            
            # Scale and transform in one operation
            x1_screen = int(x1 * scale_x + crop_x)
            y1_screen = int(y1 * scale_y + crop_y)
            x2_screen = int(x2 * scale_x + crop_x)
            y2_screen = int(y2 * scale_y + crop_y)
            
            transformed_boxes.append((x1_screen, y1_screen, x2_screen, y2_screen))
            
        return transformed_boxes

    def run(self):
        """Main detection loop with BitBlt capture and DirectML"""
        self.running = True
        
        if not self.yolo_ensemble.models:
            logging.error("No models loaded")
            return
            
        if not self.setup_bitblt():
            logging.error("BitBlt setup failed")
            return

        # Get screen dimensions
        self.screen_width = self.user32.GetSystemMetrics(0)  # SM_CXSCREEN
        self.screen_height = self.user32.GetSystemMetrics(1)  # SM_CYSCREEN
        self.center_x = self.screen_width >> 1
        self.center_y = self.screen_height >> 1
        
        logging.info(f"BitBlt detection started: {self.screen_width}x{self.screen_height}")
        
        last_time = time.perf_counter()
        
        try:
            while self.running:
                current_time = time.perf_counter()
                
                # Calculate FOV and crop region
                fov_radius = clamp(self.settings.get('fov_radius', 150), 50,
                                 min(self.screen_width, self.screen_height) >> 1)
                
                crop_x = max(self.center_x - fov_radius, 0)
                crop_y = max(self.center_y - fov_radius, 0)
                crop_width = min(fov_radius * 2, self.screen_width - crop_x)
                crop_height = min(fov_radius * 2, self.screen_height - crop_y)
                
                if crop_width < 100 or crop_height < 100:
                    continue
                
                # Capture screen region using BitBlt
                frame = self.capture_screen_region(crop_x, crop_y, crop_width, crop_height)
                
                if frame is None or frame.size == 0:
                    continue
                
                # Convert color space
                frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGB)
                
                # Run detection with DirectML
                conf_thres = clamp(self.settings.get('confidence', 0.5), 0.2, 0.95)
                all_results = self.yolo_ensemble.predict(frame, conf_thres=conf_thres)
                
                # Process detections
                detections = []
                for results in all_results:
                    if hasattr(results, 'boxes') and len(results.boxes) > 0:
                        boxes = results.boxes.xyxy.cpu().numpy()
                        confs = results.boxes.conf.cpu().numpy()
                        classes = results.boxes.cls.cpu().numpy()
                        
                        scale_x = getattr(results, 'scale_x', 1.0)
                        scale_y = getattr(results, 'scale_y', 1.0)
                        
                        screen_boxes = self.transform_coordinates(
                            boxes, crop_x, crop_y, scale_x, scale_y
                        )
                        
                        for i, (screen_box, conf, cls) in enumerate(zip(screen_boxes, confs, classes)):
                            if conf < conf_thres:
                                continue
                                
                            x1_screen, y1_screen, x2_screen, y2_screen = screen_box
                            class_id = int(cls)
                            class_name = self.yolo_ensemble.names.get(class_id, "person")
                            
                            if (x1_screen >= x2_screen or y1_screen >= y2_screen or
                                x1_screen < 0 or y1_screen < 0 or
                                x2_screen > self.screen_width or y2_screen > self.screen_height):
                                continue
                                
                            det_center_x = (x1_screen + x2_screen) >> 1
                            det_center_y = (y1_screen + y2_screen) >> 1
                            
                            dx = det_center_x - self.center_x
                            dy = det_center_y - self.center_y
                            dist_squared = dx * dx + dy * dy
                            
                            if dist_squared <= fov_radius * fov_radius:
                                detections.append((
                                    x1_screen, y1_screen, x2_screen, y2_screen,
                                    float(conf), class_name
                                ))

                # Sort by confidence (highest first)
                detections.sort(key=lambda x: x[4], reverse=True)

                # Extract data for processing
                boxes = [(x1, y1, x2, y2) for (x1, y1, x2, y2, _, _) in detections]
                confidences = [conf for (_, _, _, _, conf, _) in detections]
                labels = [label for (_, _, _, _, _, label) in detections]

                # Apply fast NMS
                if boxes:
                    selected_indices = self.fast_nms(boxes, confidences, iou_threshold=0.25)
                    boxes = [boxes[i] for i in selected_indices]
                    labels = [labels[i] for i in selected_indices]
                    confidences = [confidences[i] for i in selected_indices]

                # Prepare tracking data
                dets_for_sort = np.array([
                    [*box, conf] for box, conf in zip(boxes, confidences)
                ]) if boxes else np.empty((0, 5))
                
                # Tracking
                tracked_objects = self.tracker.update(dets_for_sort)

                # Extract tracking results
                tracked_boxes = []
                track_ids = []
                
                for obj in tracked_objects:
                    x1t, y1t, x2t, y2t, track_id = obj
                    
                    x1t, y1t, x2t, y2t = int(x1t), int(y1t), int(x2t), int(y2t)
                    
                    if (x1t < x2t and y1t < y2t and 
                        x1t >= 0 and y1t >= 0 and 
                        x2t <= self.screen_width and y2t <= self.screen_height):
                        
                        tracked_boxes.append((x1t, y1t, x2t, y2t))
                        track_ids.append(int(track_id))

                # Emit detection signal
                self.detection_signal.emit(
                    tracked_boxes, labels, confidences, labels, track_ids
                )
                
                # Performance monitoring
                self.frame_skip += 1
                if self.frame_skip >= 200:
                    self.frame_skip = 0
                    fps = 1.0 / max(0.001, current_time - last_time)
                    logging.info(f"BitBlt Performance - FPS: {fps:.1f}, Detections: {len(tracked_boxes)}")
                
                last_time = current_time
                
        except Exception as e:
            logging.error(f"Detection error: {e}")
        
        finally:
            self.cleanup_bitblt()

    def stop(self):
        """Clean shutdown"""
        self.running = False
        self.cleanup_bitblt()
        logging.info("BitBlt detection stopped")




class AimAssistThread(QtCore.QThread):
    aim_active_signal = QtCore.pyqtSignal(bool)

    def __init__(self, settings, get_targets_func, get_fov_center_func):
        super().__init__()
        self.settings = settings
        self.get_targets = get_targets_func
        self.get_fov_center = get_fov_center_func
        self.running = False
        self.aim_active = False
        self.lock = threading.Lock()
        
        # Tracking settings
        self.locked_track_id = None
        self.target_acquired = False
        
        # Performance settings
        self.aim_fps = 1000  # Increased for faster response
        self.frame_time = 1.0 / self.aim_fps
        self.last_aim_time = 0
        
        # Anti-recoil settings
        self.recoil_control = True
        self.recoil_strength = 1.2  # Adjust as needed
        self.recoil_recovery = 0.95  # Recovery rate
        self.current_recoil = 0
        
        # Aim settings
        self.aim_smoothing = 0.75  # Lower = faster aim
        self.max_speed = 8  # Increased max speed
        self.deadzone = 1.5  # Reduced deadzone
        
        # Toggle controls
        self.toggle_key = 'p'
        self.aim_toggled_on = False
        self.last_toggle_time = 0
        self.toggle_cooldown = 0.15  # Faster toggle response

    def calculate_aim_point(self, x1, y1, x2, y2):
        """Fast aim point calculation with height-based targeting"""
        center_x = (x1 + x2) >> 1
        height = y2 - y1
        # Aim higher for distant targets (smaller height)
        aim_height = 0.2 if height < 100 else 0.3
        center_y = y1 + int(height * aim_height)  # Aim at upper chest/head
        return center_x, center_y

    def apply_anti_recoil(self, dy):
        """Apply dynamic anti-recoil compensation"""
        if not self.recoil_control:
            return dy
            
        if dy > 0:  # Only apply when aiming down
            self.current_recoil = min(self.current_recoil + 0.2, self.recoil_strength)
        else:
            self.current_recoil *= self.recoil_recovery
            
        return dy - (self.current_recoil * 2.0)

    def calculate_movement(self, target_pos, fc_x, fc_y):
        """Optimized movement calculation with anti-recoil"""
        if not target_pos:
            return 0, 0

        dx = target_pos[0] - fc_x
        dy = target_pos[1] - fc_y
        
        # Apply deadzone
        if abs(dx) <= self.deadzone and abs(dy) <= self.deadzone:
            return 0, 0

        # Calculate base movement
        move_x = dx * self.aim_smoothing
        move_y = dy * self.aim_smoothing
        
        # Apply anti-recoil to vertical movement
        move_y = self.apply_anti_recoil(move_y)
        
        # Speed limiting
        move_x = clamp(move_x, -self.max_speed, self.max_speed)
        move_y = clamp(move_y, -self.max_speed, self.max_speed)

        return int(move_x), int(move_y)

    def get_best_target(self, targets, fc_x, fc_y, fov_radius):
        """Fast target selection with priority system"""
        if not targets:
            return None, None
            
        best_target = None
        best_score = float('inf')
        fov_radius_sq = fov_radius * fov_radius
        
        for target in targets:
            if len(target) < 4:
                continue
                
            x1, y1, x2, y2 = target[:4]
            track_id = target[5] if len(target) > 5 else None
            
            # Fast center calculation
            center_x, center_y = self.calculate_aim_point(x1, y1, x2, y2)
            
            # Quick distance check
            dx = center_x - fc_x
            dy = center_y - fc_y
            dist_sq = dx*dx + dy*dy
            
            if dist_sq >= fov_radius_sq:
                continue
                
            # Prioritize current target
            if track_id == self.locked_track_id:
                return (center_x, center_y), target
                
            # Score based on distance and size
            target_size = (x2-x1) * (y2-y1)
            score = dist_sq / (target_size + 1)
            
            if score < best_score:
                best_score = score
                best_target = ((center_x, center_y), target)
                
        return best_target if best_target else (None, None)

    def run(self):
        """High-performance aim assist loop"""
        self.running = True
        sleep_time = 0.0001  # Minimal sleep
        
        while self.running:
            try:
                current_time = time.perf_counter()
                
                if current_time - self.last_aim_time < self.frame_time:
                    time.sleep(sleep_time)
                    continue

                self.last_aim_time = current_time

                # Fast toggle check
                if keyboard.is_pressed(self.toggle_key):
                    if current_time - self.last_toggle_time > self.toggle_cooldown:
                        self.aim_toggled_on = not self.aim_toggled_on
                        self.last_toggle_time = current_time

                aim_active = False

                if self.aim_toggled_on and self.settings.get('aim_enabled', True):
                    with self.lock:
                        targets = self.get_targets()

                    fov_center = self.get_fov_center()
                    
                    if targets and fov_center:
                        fc_x, fc_y = (fov_center.x(), fov_center.y()) if hasattr(fov_center, "x") else fov_center
                        fov_radius = self.settings.get('fov_radius', 200)
                        
                        target_pos, target_data = self.get_best_target(targets, fc_x, fc_y, fov_radius)
                        
                        if target_pos:
                            if not self.target_acquired:
                                self.target_acquired = True
                                if target_data and len(target_data) > 5:
                                    self.locked_track_id = target_data[5]
                            
                            mdx, mdy = self.calculate_movement(target_pos, fc_x, fc_y)
                            
                            if mdx != 0 or mdy != 0:
                                move_mouse_relative(mdx, mdy)
                                aim_active = True
                        else:
                            self.target_acquired = False
                            self.locked_track_id = None
                            self.current_recoil = 0
                
                if aim_active != self.aim_active:
                    self.aim_active = aim_active
                    self.aim_active_signal.emit(self.aim_active)

            except Exception as e:
                logging.error(f"AimAssist error: {e}")
                time.sleep(sleep_time)

    def stop(self):
        """Clean shutdown"""
        self.running = False
        self.target_acquired = False
        self.locked_track_id = None
        self.current_recoil = 0



class TriggerBotThread(QtCore.QThread):
    def __init__(self, settings, get_targets_func, get_fov_center_func):
        super().__init__()
        self.settings = settings
        self.get_targets = get_targets_func
        self.get_fov_center = get_fov_center_func
        self.running = False
        self.is_firing = False
        self.last_fired = 0
        self.last_triggered = False
        self.lock = threading.Lock()
        self.burst_shots_left = 0
        self.last_burst_time = 0
        self.trigger_hotkey = self.settings.get("trigger_hotkey", "Left Mouse Button").lower()

    def run(self):
        self.running = True
        while self.running:
            try:
                if self.settings.get("trigger_enabled", False):
                    fire_speed_ms = int(self.settings.get("trigger_fire_speed", 150))
                    auto_fire = self.settings.get("trigger_auto_fire", False)
                    tolerance = int(self.settings.get("trigger_tolerance", 2))
                    burst_enabled = self.settings.get("trigger_burst", False)
                    burst_count = int(self.settings.get("trigger_burst_count", 3))
                    burst_delay = int(self.settings.get("trigger_burst_delay", 60))
                    button = self.settings.get("trigger_mouse_button", "left")

                    with self.lock:
                        targets = self.get_targets()
                    fov_center = self.get_fov_center()
                    if not fov_center:
                        time.sleep(0.01)
                        continue
                    fc_x, fc_y = (
                        (fov_center.x(), fov_center.y())
                        if hasattr(fov_center, "x") else fov_center
                    )
                    fired_this_loop = False
                    now = time.time()

                    for (x1, y1, x2, y2, *_) in targets:
                        # Check if the FOV center is inside the bounding box with optional tolerance
                        left = x1 - tolerance
                        right = x2 + tolerance
                        top = y1 - tolerance
                        bottom = y2 + tolerance
                        if left <= fc_x <= right and top <= fc_y <= bottom:
                            # Target is under crosshair - fire accordingly
                            if burst_enabled:
                                if self.burst_shots_left == 0 or (now - self.last_burst_time)*1000 >= fire_speed_ms:
                                    self.burst_shots_left = burst_count
                                    self.last_burst_time = now
                                if self.burst_shots_left > 0:
                                    if (now - self.last_fired)*1000 >= burst_delay:
                                        mouse_click_down_up(button)
                                        self.last_fired = now
                                        self.burst_shots_left -= 1
                                        self.last_triggered = True
                            elif auto_fire:
                                if not self.is_firing:
                                    mouse_press_down(button)
                                    self.is_firing = True
                                self.last_triggered = True
                            else:
                                if not self.last_triggered or (now - self.last_fired)*1000 >= fire_speed_ms:
                                    mouse_click_down_up(button)
                                    self.last_fired = now
                                    self.last_triggered = True
                            fired_this_loop = True
                            break
                    else:
                        # No target under crosshair
                        self.last_triggered = False
                        self.burst_shots_left = 0
                        if self.is_firing:
                            mouse_release_up(button)
                            self.is_firing = False

                    if auto_fire and not fired_this_loop and self.is_firing:
                        mouse_release_up(button)
                        self.is_firing = False
                else:
                    # Trigger disabled, ensure mouse button released
                    self.burst_shots_left = 0
                    if self.is_firing:
                        mouse_release_up(self.settings.get("trigger_mouse_button", "left"))
                        self.is_firing = False
                time.sleep(0.008)
            except Exception as e:
                logging.error(f"TriggerBotThread error: {e}")

    def stop(self):
        self.running = False
        if self.is_firing:
            mouse_release_up(self.settings.get("trigger_mouse_button", "left"))
            self.is_firing = False

# --- RECOIL THREAD (with fixes & customization) ---
class RecoilControlThread(QtCore.QThread):
    def __init__(self, settings):
        super().__init__()
        self.settings = settings
        self.running = False
        self.patterns = {
            "Default": [(0, 7), (0, 7), (0, 7), (0, 8), (0, 8), (0, 9), (0, 10), (0, 11)],
            "Pattern 1": [(1, 8), (0, 8), (-1, 8), (2, 8), (-2, 9), (1, 10), (0, 11)],
            "Pattern 2": [(0, 7), (1, 7), (2, 8), (2, 8), (1, 9), (0, 10), (-1, 11)],
        }
        self._terminate = threading.Event()
        self.shot_idx = 0

    def run(self):
        import random
        import keyboard
        self.running = True
        while not self._terminate.is_set():
            try:
                if self.settings.get('recoil_enabled', False):
                    key = self.settings.get('recoil_activation_key', 'Left Mouse Button')
                    if not key:
                        time.sleep(0.02)
                        continue
                    key_std = key.lower().replace(" ", "")
                    pressed = False
                    if "mouse" in key_std or "button" in key_std:
                        if "right" in key_std:
                            pressed = mouse.is_pressed(button="right")
                        elif "middle" in key_std:
                            pressed = mouse.is_pressed(button="middle")
                        else:
                            pressed = mouse.is_pressed(button="left")
                    else:
                        try:
                            pressed = keyboard.is_pressed(key)
                        except Exception:
                            pressed = False
                    if pressed:
                        pattern_name = self.settings.get('recoil_pattern', 'Default')
                        pattern = self.patterns.get(pattern_name, self.patterns["Default"])
                        strength = self.settings.get('recoil_strength', 50)
                        jitter = self.settings.get('recoil_jitter', 5)
                        delay = self.settings.get('recoil_delay', 12)
                        humanize = self.settings.get('recoil_humanize', 6)
                        side = self.settings.get('recoil_side', 0)
                        if pattern_name == "Custom" and self.settings.get("recoil_custom_pattern"):
                            pattern = self.settings.get("recoil_custom_pattern")
                        if self.shot_idx >= len(pattern):
                            self.shot_idx = 0
                        move_x, move_y = pattern[self.shot_idx]
                        move_x += side
                        move_y = int(move_y * (strength / 50.0))
                        move_x += random.randint(-jitter, jitter)
                        move_y += random.randint(-humanize, humanize)
                        move_mouse_relative(move_x, move_y)
                        self.shot_idx += 1
                        time.sleep(delay / 1000.0)
                    else:
                        self.shot_idx = 0
                        time.sleep(0.01)
                else:
                    self.shot_idx = 0
                    time.sleep(0.02)
            except Exception as ex:
                logging.error(f"RecoilControlThread error: {ex}")
                time.sleep(0.04)
        self.running = False

    def stop(self):
        self._terminate.set()

# --- UI utility classes: HotkeyButton, ColorButton, SidebarButton, ScrollAreaNoBar, OverlayWindow ---

class HotkeyButton(QtWidgets.QPushButton):
    def __init__(self, label, setting_key, main_window):
        super().__init__(label)
        self.setting_key = setting_key
        self.main_window = main_window
        self.setText(self.main_window.settings.get(setting_key, label))
        self.clicked.connect(self.listen_for_hotkey)
        self.listening = False

    def listen_for_hotkey(self):
        self.setText("Press any key or mouse button...")
        self.listening = True
        self.grabKeyboard()
        self.grabMouse()

    def keyPressEvent(self, event):
        if self.listening:
            key = event.text() or event.key()
            if isinstance(key, int):
                key = QtGui.QKeySequence(key).toString()
            self.main_window.update_setting(self.setting_key, key)
            self.setText(key)
            self.listening = False
            self.releaseKeyboard()
            self.releaseMouse()
        else:
            super().keyPressEvent(event)

    def mousePressEvent(self, event):
        if self.listening:
            if event.button() == QtCore.Qt.LeftButton:
                btn = "Left Mouse Button"
            elif event.button() == QtCore.Qt.RightButton:
                btn = "Right Mouse Button"
            elif event.button() == QtCore.Qt.MiddleButton:
                btn = "Middle Mouse Button"
            else:
                btn = f"Mouse Button {event.button()}"
            self.main_window.update_setting(self.setting_key, btn)
            self.setText(btn)
            self.listening = False
            self.releaseKeyboard()
            self.releaseMouse()
        else:
            super().mousePressEvent(event)

class ColorButton(QtWidgets.QPushButton):
    def __init__(self, color, slot):
        super().__init__()
        self.setMaximumWidth(48)
        self.color = color
        self.slot = slot
        self.setStyleSheet(f"background-color: {color}; border: 1px solid #888;")
        self.clicked.connect(self.pick_color)

    def pick_color(self):
        col = QtWidgets.QColorDialog.getColor(QtGui.QColor(self.color))
        if col.isValid():
            self.color = col.name()
            self.setStyleSheet(f"background-color: {self.color}; border: 1px solid #888;")
            self.slot(self.color)

class SidebarButton(QtWidgets.QPushButton):
    def __init__(self, icon, index):
        super().__init__()
        self.setIcon(icon)
        self.setCheckable(True)
        self.setFixedSize(56, 56)
        self.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
                border-radius: 14px;
                padding: 8px;
            }
            QPushButton:hover {
                background-color: rgba(179, 102, 255, 0.2);
            }
            QPushButton:checked {
                background-color: #B366FF;
            }
        """)


class ScrollAreaNoBar(QtWidgets.QScrollArea):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.setWidgetResizable(True)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setStyleSheet("""
            QScrollBar:vertical, QScrollBar:horizontal { width: 0px; height: 0px; }
        """)

    def wheelEvent(self, event):
        return super().wheelEvent(event)

class OverlayWindow(QtWidgets.QWidget):
    def __init__(self, settings, get_classes, main_window=None):
        super().__init__()

        self.settings = settings
        self.get_classes = get_classes
        self.main_window = main_window
        
        self.setWindowFlags(
            QtCore.Qt.WindowStaysOnTopHint |
            QtCore.Qt.FramelessWindowHint |
            QtCore.Qt.Tool
        )
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)

        # --- Windows-specific: Make the window click-through ---
        if sys.platform == 'win32':
            hwnd = self.winId().__int__()
            extended_style = ctypes.windll.user32.GetWindowLongW(hwnd, -20)  # GWL_EXSTYLE = -20
            new_extended_style = extended_style | 0x80000 | 0x20  # WS_EX_LAYERED | WS_EX_TRANSPARENT
            ctypes.windll.user32.SetWindowLongW(hwnd, -20, new_extended_style)

        self.screen = QtWidgets.QApplication.primaryScreen().geometry()
        self.setGeometry(self.screen)
        self.fov_center = self.screen.center()
        self.boxes = []
        self.labels = []
        self.confidences = []
        self.classes = []
        self.aim_active = False
        self.opacity = self.settings.get('window_transparency', 0.87)
        self.setWindowOpacity(self.opacity)
        self.frame_times = []
        self.show_fps = True
        self.show()

    def set_opacity(self, opacity):
        self.opacity = opacity
        self.setWindowOpacity(opacity)
        self.repaint()

    def update_overlay(self, boxes, labels, confidences, classes):
        self.boxes = boxes
        self.labels = labels
        self.confidences = confidences
        self.classes = classes
        self.repaint()

    def set_aim_active(self, active):
        self.aim_active = active
        self.repaint()

    def paintEvent(self, event):
        import time
        now = time.time()
        self.frame_times.append(now)
        if len(self.frame_times) > 60:
            self.frame_times.pop(0)

        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setOpacity(self.opacity)
        # --- Draw FOV ---
        if self.settings.get('fov_enabled', True):
            pen = QtGui.QPen(parse_qcolor(self.settings.get('fov_color', "#B366FF")))
            pen.setWidth(1)
            pen.setCosmetic(True)
            painter.setPen(pen)
            r = clamp(self.settings.get('fov_radius', 200), 50, 600)
            center = self.fov_center
            painter.setBrush(QtCore.Qt.NoBrush)
            painter.drawEllipse(center, r, r)
        # --- Draw ESP Boxes ---
        if self.settings.get('esp_enabled', True):
            style = self.settings.get('esp_box_style', "Box")
            box_color = parse_qcolor(self.settings.get('esp_box_color', "#B366FF"))
            fill_color = parse_qcolor(self.settings.get('esp_fill_color', "#7E3FF2"))
            line_color = parse_qcolor(self.settings.get('esp_line_color', "#B366FF"))
            label_font = QtGui.QFont(self.settings.get('ui_font', "Segoe UI"), self.settings.get('esp_font_size', 12))
            painter.setFont(label_font)
            for i, box in enumerate(self.boxes):
                x1, y1, x2, y2 = box
                if style == "Box":
                    pen = QtGui.QPen(box_color)
                    pen.setWidth(2)
                    pen.setCosmetic(True)
                    painter.setPen(pen)
                    painter.setBrush(QtCore.Qt.NoBrush)
                    painter.drawRect(x1, y1, x2 - x1, y2 - y1)
                elif style == "Corner":
                    pen = QtGui.QPen(box_color)
                    pen.setWidthF(2.4)
                    pen.setCosmetic(True)
                    painter.setPen(pen)
                    painter.setBrush(QtCore.Qt.NoBrush)
                    l = 18
                    painter.drawLine(x1, y1, x1+l, y1)
                    painter.drawLine(x1, y1, x1, y1+l)
                    painter.drawLine(x2, y1, x2-l, y1)
                    painter.drawLine(x2, y1, x2, y1+l)
                    painter.drawLine(x1, y2, x1+l, y2)
                    painter.drawLine(x1, y2, x1, y2-l)
                    painter.drawLine(x2, y2, x2-l, y2)
                    painter.drawLine(x2, y2, x2, y2-l)
                elif style == "Filled":
                    painter.setPen(QtCore.Qt.NoPen)
                    painter.setBrush(fill_color)
                    painter.drawRect(x1, y1, x2 - x1, y2 - y1)
                # --- Draw Snap Lines ---
                if self.settings.get('esp_show_lines', True):
                    pen = QtGui.QPen(line_color)
                    pen.setWidth(2)
                    pen.setCosmetic(True)
                    pen.setStyle(QtCore.Qt.SolidLine)
                    painter.setPen(pen)
                    cx, cy = self.fov_center.x(), self.fov_center.y()
                    painter.drawLine(cx, cy, (x1 + x2) // 2, (y1 + y2) // 2)
                # --- Draw label with thin outline ---
                pen = QtGui.QPen(QtGui.QColor("#FFFFFF"), 1)
                painter.setPen(pen)
                if i < len(self.labels):
                    painter.drawText(x1, y1-8, self.labels[i])
        # --- FPS DISPLAY ---
        y_fps = 28
        if self.show_fps and len(self.frame_times) > 1:
            fps = len(self.frame_times) / (self.frame_times[-1] - self.frame_times[0])
            fps_text = f"Overlay FPS: {fps:.1f}"
            painter.setFont(QtGui.QFont("Segoe UI", 14, QtGui.QFont.Bold))
            painter.setPen(QtGui.QPen(QtGui.QColor("#00FF00"), 1))
            painter.drawText(12, y_fps, fps_text)
            y_fps += 22

        # --- Detection FPS DISPLAY (TRUE FPS) ---
        if self.main_window is not None and hasattr(self.main_window, "detection_fps_times"):
            times = self.main_window.detection_fps_times
            if len(times) > 1:
                fps = len(times) / (times[-1] - times[0])
                painter.setFont(QtGui.QFont("Segoe UI", 14, QtGui.QFont.Bold))
                painter.setPen(QtGui.QPen(QtGui.QColor("#1100FF"), 1))
                painter.drawText(12, y_fps, f"Detection FPS: {fps:.1f}")

# --- ModernMainWindow (frameless, all tabs, clean thread stop), and main entry point ---

class ModernMainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.settings = load_settings()
        self.setWindowTitle(f"{APPNAME} — Melvin .cheats YOLO11n UD Detection by Melvin")
        self.setGeometry(120, 120, 900, 650)
        self.setMinimumSize(750, 500)
        # Frameless window, drag from anywhere without controls/widgets
        self.setWindowFlags(
            QtCore.Qt.FramelessWindowHint |
            QtCore.Qt.Window
        )
        self._drag_pos = None
        self._dragging = False
        self.apply_theme()
        self.detection_results = ([], [], [], [])
        self.detection_fps_times = []  # For true detection FPS display!
        self.overlay = OverlayWindow(self.settings, self.get_detection_classes, main_window=self)
        self.detection_thread = DetectionThread(self.settings)
        self.detection_thread.detection_signal.connect(self.overlay.update_overlay)
        self.aim_assist_thread = AimAssistThread(self.settings, self.get_detection_targets, self.get_fov_center)
        self.aim_assist_thread.aim_active_signal.connect(self.set_aim_active)
        self.trigger_bot_thread = TriggerBotThread(self.settings, self.get_detection_targets, self.get_fov_center)
        self.recoil_thread = RecoilControlThread(self.settings)
        self.threads = [self.detection_thread, self.aim_assist_thread, self.trigger_bot_thread, self.recoil_thread]
        self.init_ui()
        self.start_detection()
        self.menu_shortcut_key = self.settings.get("menu_shortcut", "F8")
        self.shortcut_listener_thread = threading.Thread(target=self.listen_for_menu_shortcut, daemon=True)
        self.shortcut_listener_thread.start()
        # Rounded corners
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.radius = 22  # You can tweak this for more/less rounding

    # --- Frameless Dragging ---
    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton and not self.childAt(event.pos()):
            self._drag_pos = event.globalPos() - self.frameGeometry().topLeft()
            self._dragging = True
            event.accept()
        else:
            super().mousePressEvent(event)
    def mouseMoveEvent(self, event):
        if self._dragging and event.buttons() == QtCore.Qt.LeftButton:
            self.move(event.globalPos() - self._drag_pos)
            event.accept()
        else:
            super().mouseMoveEvent(event)
    def mouseReleaseEvent(self, event):
        self._dragging = False
        super().mouseReleaseEvent(event)

    def listen_for_menu_shortcut(self):
        while True:
            try:
                if keyboard.is_pressed(self.menu_shortcut_key):
                    self.showNormal()
                    self.activateWindow()
                    self.raise_()
                    time.sleep(0.8)
            except Exception:
                pass
            time.sleep(0.15)

    def apply_theme(self):
        purple = "#B366FF"
        font = self.settings.get('ui_font', "Segoe UI")
        font_size = self.settings.get('ui_font_size', 11)
        opacity = self.settings.get('window_transparency', 0.87)
        self.setWindowOpacity(opacity)
        
        self.setStyleSheet(f"""
            QWidget {{
                background-color: rgba(24,26,27,220);
                color: #FFFFFF;
                font-family: "{font}";
                font-size: {font_size}pt;
                border-radius: 18px;
            }}
            QTabBar::tab:selected {{ 
                background: {purple}; 
                color: #181A1B; 
                border-radius: 10px;
                padding: 8px 16px;
            }}
            QTabWidget::pane {{ 
                border: 2px solid {purple}; 
                border-radius: 10px;
                margin-top: 10px;
            }}
            QPushButton {{ 
                background: rgba(34,34,34,0.8);
                color: {purple};
                border-radius: 8px;
                padding: 8px;
                border: 1px solid rgba(179,102,255,0.3);
            }}
            QPushButton:hover {{
                background: rgba(34,34,34,0.9);
                border: 1px solid {purple};
            }}
            QSlider::groove:horizontal {{
                background: #222;
                height: 8px;
                border-radius: 4px;
            }}
            QSlider::handle:horizontal {{
                background: {purple};
                width: 16px;
                height: 16px;
                margin: -4px 0;
                border-radius: 8px;
            }}
            QCheckBox::indicator {{
                width: 18px;
                height: 18px;
                border-radius: 4px;
                border: 2px solid {purple};
            }}
            QCheckBox::indicator:checked {{
                background: {purple};
            }}
            QComboBox {{
                padding: 6px;
                border-radius: 8px;
                border: 1px solid rgba(179,102,255,0.3);
                background: rgba(34,34,34,0.8);
            }}
            QComboBox::drop-down {{
                border: none;
            }}
            QComboBox::down-arrow {{
                image: none;
                border: none;
            }}
        """)


    def set_aim_active(self, active):
        self.overlay.set_aim_active(active)

    def update_setting(self, key, value):
        self.settings[key] = value
        save_settings(self.settings)
        self.overlay.settings = self.settings
        self.detection_thread.settings = self.settings
        self.aim_assist_thread.settings = self.settings
        self.trigger_bot_thread.settings = self.settings
        self.recoil_thread.settings = self.settings
        self.apply_theme()
        self.repaint()

    def get_detection_targets(self):
        return self.detection_results[0]

    def get_detection_classes(self):
        return self.detection_results[3]

    def get_fov_center(self):
        g = QtWidgets.QApplication.primaryScreen().geometry()
        return (g.width()//2, g.height()//2)

    def start_detection(self):
        for t in self.threads:
            if not t.isRunning():
                t.start()
        self.detection_thread.detection_signal.connect(self.handle_detection_results)

    def handle_detection_results(self, boxes, labels, confidences, classes):
        self.detection_results = (boxes, labels, confidences, classes)
        # --- Track true detection FPS ---
        import time
        now = time.time()
        self.detection_fps_times.append(now)
        if len(self.detection_fps_times) > 60:
            self.detection_fps_times.pop(0)

    def closeEvent(self, event):
        for t in self.threads:
            try:
                t.stop()
            except Exception: pass
        for t in self.threads:
            try:
                t.wait(1000)
            except Exception: pass
        event.accept()

    def init_ui(self):
        main_layout = QtWidgets.QHBoxLayout(self)
        sidebar = QtWidgets.QVBoxLayout()
        sidebar.setSpacing(6)
        sidebar.setContentsMargins(18, 18, 6, 18)

        icon_paths = [
            QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\aim.png"),
            QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\fov.png"),
            QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\esp.png"),
            QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\trigger.png"),
            QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\settings.png"),
            QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\recoil.png"),
        ]
        self.sidebar_buttons = []
        for i in range(6):
            btn = SidebarButton(icon_paths[i], i)
            btn.setIconSize(QtCore.QSize(50, 50))
            btn.clicked.connect(lambda checked, idx=i: self.set_tab(idx))
            self.sidebar_buttons.append(btn)
            sidebar.addWidget(btn)

        discord_icon = QtGui.QIcon(r"C:\Users\belze\OneDrive\Desktop\UD Premium Lite\icons\discord.png")
        discord_btn = QtWidgets.QPushButton()
        discord_btn.setIcon(discord_icon)
        discord_btn.setIconSize(QtCore.QSize(50, 50))
        discord_btn.setFixedSize(56, 56)
        discord_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
            }
            QPushButton:hover {
                background-color: #5865F2;
                border-radius: 14px;
            }
        """)
        discord_btn.clicked.connect(lambda: webbrowser.open_new_tab("https://discord.gg/q7hXusbe"))
        sidebar.addStretch(1)
        sidebar.addWidget(discord_btn)

        self.tabs = QtWidgets.QTabWidget()
        self.tabs.setStyleSheet("QTabBar::tab { min-width: 120px; font-size: 14pt; border-radius: 10px; }")
        self.tabs.addTab(self.make_aim_tab(), "AIM")
        self.tabs.addTab(self.make_fov_tab(), "FOV")
        self.tabs.addTab(self.make_esp_tab(), "ESP")
        self.tabs.addTab(self.make_trigger_tab(), "TRIGGER")
        self.tabs.addTab(self.make_settings_tab(), "SETTINGS")
        self.tabs.addTab(self.make_recoil_tab(), "RECOIL")
        self.tabs.tabBar().hide()
        container = QtWidgets.QWidget()
        content_layout = QtWidgets.QVBoxLayout(container)
        content_layout.setContentsMargins(10, 18, 18, 18)
        content_layout.addWidget(self.tabs)
        main_layout.addLayout(sidebar, 0)
        main_layout.addWidget(container, 1)
        main_layout.setSpacing(0)
        self.setLayout(main_layout)
        self.set_tab(0)

    def set_tab(self, idx):
        self.tabs.setCurrentIndex(idx)
        for i, btn in enumerate(self.sidebar_buttons):
            btn.setChecked(i == idx)

    def make_percent_slider(self, minv, maxv, value, label, slot, decimals=2, suffix='%'):
        hbox = QtWidgets.QHBoxLayout()
        slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        slider.setMinimum(int(minv*100))
        slider.setMaximum(int(maxv*100))
        slider.setValue(int(value*100))
        percent_label = QtWidgets.QLabel(f"{int(value*100)}{suffix}")
        def update_label(v):
            percent_label.setText(f"{v}{suffix}")
            slot(round(v/100, decimals))
        slider.valueChanged.connect(lambda v: update_label(v))
        hbox.addWidget(QtWidgets.QLabel(label))
        hbox.addWidget(slider)
        hbox.addWidget(percent_label)
        w = QtWidgets.QWidget()
        w.setLayout(hbox)
        return w

    # ---- UI Tab Methods ----

    def make_aim_tab(self):
        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        aim_enable = QtWidgets.QCheckBox("Enable Aim Assist")
        aim_enable.setChecked(self.settings['aim_enabled'])
        aim_enable.stateChanged.connect(lambda s: self.update_setting('aim_enabled', bool(s)))
        layout.addWidget(aim_enable)
        aim_hotkey_btn = HotkeyButton("Set AIM Hotkey", "aim_hotkey", self)
        layout.addWidget(QtWidgets.QLabel("Aim Assist Hotkey:"))
        layout.addWidget(aim_hotkey_btn)
        move_speed = self.make_percent_slider(0.1, 1.5, self.settings.get('aim_move_speed', 0.8), "Move Speed", lambda v: self.update_setting('aim_move_speed', v), decimals=2)
        layout.addWidget(move_speed)
        aim_smoothing = self.make_percent_slider(0.0, 1.0, self.settings.get('aim_smoothing', 0.45), "Aim Smoothing", lambda v: self.update_setting('aim_smoothing', v), decimals=2)
        layout.addWidget(aim_smoothing)
        sticky_max = self.make_percent_slider(5, 80, self.settings.get('aim_sticky_max', 35)/80, "Sticky Max", lambda v: self.update_setting('aim_sticky_max', int(v*80)), decimals=0, suffix="px")
        layout.addWidget(sticky_max)
        sticky_decay = self.make_percent_slider(0.5, 1.0, self.settings.get('aim_sticky_decay', 0.85), "Sticky Decay", lambda v: self.update_setting('aim_sticky_decay', v), decimals=2)
        layout.addWidget(sticky_decay)
        sticky_boost = self.make_percent_slider(1.0, 2.5, self.settings.get('aim_sticky_boost', 1.55), "Sticky Boost", lambda v: self.update_setting('aim_sticky_boost', v), decimals=2)
        layout.addWidget(sticky_boost)
        pred_factor = self.make_percent_slider(0.0, 1.0, self.settings.get('aim_prediction_factor', 0.1), "Prediction Factor", lambda v: self.update_setting('aim_prediction_factor', v), decimals=2)
        layout.addWidget(pred_factor)
        # --- Silent Aim ---
        silent_enable = QtWidgets.QCheckBox("Enable Silent Aim (hold key to flick to target)")
        silent_enable.setChecked(self.settings.get('silent_aim_enabled', False))
        silent_enable.stateChanged.connect(lambda s: self.update_setting('silent_aim_enabled', bool(s)))
        layout.addWidget(silent_enable)
        silent_hotkey_btn = HotkeyButton("Set Silent Aim Hotkey", "silent_aim_hotkey", self)
        layout.addWidget(QtWidgets.QLabel("Silent Aim Hotkey:"))
        layout.addWidget(silent_hotkey_btn)
        silent_speed = self.make_percent_slider(0.2, 5.0, self.settings.get('silent_aim_speed', 1.0)/5, "Silent Aim Flick Speed", lambda v: self.update_setting('silent_aim_speed', v*5), decimals=2)
        layout.addWidget(silent_speed)
        layout.addStretch(1)
        scroll = ScrollAreaNoBar()
        scroll.setWidgetResizable(True)
        w.setLayout(layout)
        scroll.setWidget(w)
        return scroll

    def make_fov_tab(self):
        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        fov_enable = QtWidgets.QCheckBox("Show FOV Circle")
        fov_enable.setChecked(self.settings['fov_enabled'])
        fov_enable.stateChanged.connect(lambda s: self.update_setting('fov_enabled', bool(s)))
        layout.addWidget(fov_enable)
        fov_radius = self.make_percent_slider(0.1, 0.8, self.settings['fov_radius']/800, "FOV Circle Size", lambda v: self.update_setting('fov_radius', int(v*800)), decimals=2)
        layout.addWidget(fov_radius)
        fov_thick_slider = self.make_percent_slider(1, 8, self.settings.get('fov_thickness', 1)/8, "FOV Circle Thickness", lambda v: self.update_setting('fov_thickness', int(v*8)), decimals=0, suffix="px")
        layout.addWidget(fov_thick_slider)
        fov_opacity = self.make_percent_slider(0.05, 1.0, self.settings.get('fov_opacity', 0.23), "FOV Opacity", lambda v: self.update_setting('fov_opacity', v), decimals=2)
        layout.addWidget(fov_opacity)
        fov_color = ColorButton(self.settings.get('fov_color', "#B366FF"), lambda c: self.update_setting('fov_color', c))
        layout.addWidget(QtWidgets.QLabel("FOV Circle Color:"))
        layout.addWidget(fov_color)
        layout.addStretch(1)
        scroll = ScrollAreaNoBar()
        scroll.setWidgetResizable(True)
        w.setLayout(layout)
        scroll.setWidget(w)
        return scroll

    def make_esp_tab(self):
        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        esp_enable = QtWidgets.QCheckBox("Enable ESP")
        esp_enable.setChecked(self.settings['esp_enabled'])
        esp_enable.stateChanged.connect(lambda s: self.update_setting('esp_enabled', bool(s)))
        layout.addWidget(esp_enable)
        box_color = ColorButton(self.settings.get('esp_box_color', "#B366FF"), lambda c: self.update_setting('esp_box_color', c))
        fill_color = ColorButton(self.settings.get('esp_fill_color', "#7E3FF2"), lambda c: self.update_setting('esp_fill_color', c))
        line_color = ColorButton(self.settings.get('esp_line_color', "#B366FF"), lambda c: self.update_setting('esp_line_color', c))
        layout.addWidget(QtWidgets.QLabel("ESP Box Color:"))
        layout.addWidget(box_color)
        layout.addWidget(QtWidgets.QLabel("ESP Fill Color:"))
        layout.addWidget(fill_color)
        layout.addWidget(QtWidgets.QLabel("ESP Line Color:"))
        layout.addWidget(line_color)
        esp_style = QtWidgets.QComboBox()
        esp_style.addItems(["Box", "Corner", "Filled"])
        esp_style.setCurrentText(self.settings.get('esp_box_style', "Box"))
        esp_style.currentTextChanged.connect(lambda v: self.update_setting('esp_box_style', v))
        layout.addWidget(QtWidgets.QLabel("ESP Box Style:"))
        layout.addWidget(esp_style)
        esp_lines = QtWidgets.QCheckBox("Show Lines to ESP Boxes")
        esp_lines.setChecked(self.settings.get('esp_show_lines', True))
        esp_lines.stateChanged.connect(lambda s: self.update_setting('esp_show_lines', bool(s)))
        layout.addWidget(esp_lines)
        font_size = self.make_percent_slider(8, 22, self.settings.get('esp_font_size', 12)/22, "ESP Font Size", lambda v: self.update_setting('esp_font_size', int(v*22)), decimals=0, suffix="px")
        layout.addWidget(font_size)
        esp_box_slider = self.make_percent_slider(1, 8, self.settings.get('esp_box_thickness', 1)/8, "ESP Box Thickness", lambda v: self.update_setting('esp_box_thickness', int(v*8)), decimals=0, suffix="px")
        layout.addWidget(esp_box_slider)
        esp_line_slider = self.make_percent_slider(1, 8, self.settings.get('esp_line_thickness', 1)/8, "ESP Line Thickness", lambda v: self.update_setting('esp_line_thickness', int(v*8)), decimals=0, suffix="px")
        layout.addWidget(esp_line_slider)
        layout.addStretch(1)
        scroll = ScrollAreaNoBar()
        scroll.setWidgetResizable(True)
        w.setLayout(layout)
        scroll.setWidget(w)
        return scroll

    def make_trigger_tab(self):
        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        trigger_enable = QtWidgets.QCheckBox("Enable Trigger Bot")
        trigger_enable.setChecked(self.settings.get("trigger_enabled", False))
        trigger_enable.stateChanged.connect(lambda s: self.update_setting("trigger_enabled", bool(s)))
        layout.addWidget(trigger_enable)
        trigger_auto = QtWidgets.QCheckBox("Auto Fire (hold button down while target in center)")
        trigger_auto.setChecked(self.settings.get("trigger_auto_fire", False))
        trigger_auto.stateChanged.connect(lambda s: self.update_setting("trigger_auto_fire", bool(s)))
        layout.addWidget(trigger_auto)
        burst_enable = QtWidgets.QCheckBox("Enable Burst Mode")
        burst_enable.setChecked(self.settings.get("trigger_burst", False))
        burst_enable.stateChanged.connect(lambda s: self.update_setting("trigger_burst", bool(s)))
        layout.addWidget(burst_enable)
        hbox = QtWidgets.QHBoxLayout()
        slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        slider.setMinimum(50)
        slider.setMaximum(1000)
        slider.setValue(self.settings.get("trigger_fire_speed", 150))
        fire_label = QtWidgets.QLabel(f"{self.settings.get('trigger_fire_speed', 150)} ms")
        def set_fire_speed(v):
            fire_label.setText(f"{v} ms")
            self.update_setting("trigger_fire_speed", int(v))
        slider.valueChanged.connect(set_fire_speed)
        hbox.addWidget(QtWidgets.QLabel("Fire Speed (ms between bursts):"))
        hbox.addWidget(slider)
        hbox.addWidget(fire_label)
        layout.addLayout(hbox)
        hbox2 = QtWidgets.QHBoxLayout()
        tol_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        tol_slider.setMinimum(0)
        tol_slider.setMaximum(10)
        tol_slider.setValue(self.settings.get("trigger_tolerance", 2))
        tol_label = QtWidgets.QLabel(f"{self.settings.get('trigger_tolerance', 2)} px")
        def set_tol(v):
            tol_label.setText(f"{v} px")
            self.update_setting('trigger_tolerance', int(v))
        tol_slider.valueChanged.connect(set_tol)
        hbox2.addWidget(QtWidgets.QLabel("Trigger Center Tolerance:"))
        hbox2.addWidget(tol_slider)
        hbox2.addWidget(tol_label)
        layout.addLayout(hbox2)
        hbox3 = QtWidgets.QHBoxLayout()
        burst_count_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        burst_count_slider.setMinimum(2)
        burst_count_slider.setMaximum(8)
        burst_count_slider.setValue(self.settings.get("trigger_burst_count", 3))
        burst_count_label = QtWidgets.QLabel(f"{self.settings.get('trigger_burst_count', 3)} shots")
        def set_burst_count(v):
            burst_count_label.setText(f"{v} shots")
            self.update_setting("trigger_burst_count", int(v))
        burst_count_slider.valueChanged.connect(set_burst_count)
        hbox3.addWidget(QtWidgets.QLabel("Burst Count:"))
        hbox3.addWidget(burst_count_slider)
        hbox3.addWidget(burst_count_label)
        layout.addLayout(hbox3)
        hbox4 = QtWidgets.QHBoxLayout()
        burst_delay_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        burst_delay_slider.setMinimum(20)
        burst_delay_slider.setMaximum(200)
        burst_delay_slider.setValue(self.settings.get("trigger_burst_delay", 60))
        burst_delay_label = QtWidgets.QLabel(f"{self.settings.get('trigger_burst_delay', 60)} ms")
        def set_burst_delay(v):
            burst_delay_label.setText(f"{v} ms")
            self.update_setting("trigger_burst_delay", int(v))
        burst_delay_slider.valueChanged.connect(set_burst_delay)
        hbox4.addWidget(QtWidgets.QLabel("Burst Delay (between shots):"))
        hbox4.addWidget(burst_delay_slider)
        hbox4.addWidget(burst_delay_label)
        layout.addLayout(hbox4)
        layout.addStretch(1)
        scroll = ScrollAreaNoBar()
        scroll.setWidgetResizable(True)
        w.setLayout(layout)
        scroll.setWidget(w)
        return scroll

    def make_settings_tab(self):
        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        conf_slider = self.make_percent_slider(0.1, 0.9, self.settings.get('confidence', 0.5), "Detection Confidence", lambda v: self.update_setting('confidence', v), decimals=2)
        layout.addWidget(conf_slider)
        window_opacity_slider = self.make_percent_slider(0.1, 1.0, self.settings.get('window_transparency', 0.87), "Menu/Background Opacity", lambda v: self.update_setting('window_transparency', v), decimals=2)
        layout.addWidget(window_opacity_slider)
        hbox = QtWidgets.QHBoxLayout()
        import_btn = QtWidgets.QPushButton("Import Settings")
        export_btn = QtWidgets.QPushButton("Export Settings")
        hbox.addWidget(import_btn)
        hbox.addWidget(export_btn)
        layout.addLayout(hbox)
        import_btn.clicked.connect(lambda: self.handle_import_settings())
        export_btn.clicked.connect(lambda: export_settings(self.settings, self))
        shortcut_hbox = QtWidgets.QHBoxLayout()
        shortcut_label = QtWidgets.QLabel("Menu Shortcut Key:")
        shortcut_edit = QtWidgets.QLineEdit(self.settings.get("menu_shortcut", "F8"))
        shortcut_edit.setMaximumWidth(60)
        def set_shortcut():
            key = shortcut_edit.text().strip()
            if key:
                self.update_setting("menu_shortcut", key)
                self.menu_shortcut_key = key
        shortcut_edit.editingFinished.connect(set_shortcut)
        shortcut_hbox.addWidget(shortcut_label)
        shortcut_hbox.addWidget(shortcut_edit)
        layout.addLayout(shortcut_hbox)
        layout.addStretch(1)
        scroll = ScrollAreaNoBar()
        scroll.setWidgetResizable(True)
        w.setLayout(layout)
        scroll.setWidget(w)
        return scroll

    def make_recoil_tab(self):
        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        recoil_enable = QtWidgets.QCheckBox("Enable Recoil Control")
        recoil_enable.setChecked(self.settings.get('recoil_enabled', False))
        recoil_enable.stateChanged.connect(lambda s: self.update_setting('recoil_enabled', bool(s)))
        layout.addWidget(recoil_enable)
        recoil_strength_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        recoil_strength_slider.setMinimum(0)
        recoil_strength_slider.setMaximum(100)
        recoil_strength_slider.setValue(self.settings.get('recoil_strength', 50))
        recoil_strength_label = QtWidgets.QLabel(f"{self.settings.get('recoil_strength', 50)}%")
        def set_strength(v):
            recoil_strength_label.setText(f"{v}%")
            self.update_setting('recoil_strength', v)
        recoil_strength_slider.valueChanged.connect(set_strength)
        layout.addWidget(QtWidgets.QLabel("Recoil Strength"))
        layout.addWidget(recoil_strength_slider)
        layout.addWidget(recoil_strength_label)
        recoil_jitter_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        recoil_jitter_slider.setMinimum(0)
        recoil_jitter_slider.setMaximum(20)
        recoil_jitter_slider.setValue(self.settings.get('recoil_jitter', 5))
        recoil_jitter_label = QtWidgets.QLabel(f"{self.settings.get('recoil_jitter', 5)} px")
        def set_jitter(v):
            recoil_jitter_label.setText(f"{v} px")
            self.update_setting('recoil_jitter', v)
        recoil_jitter_slider.valueChanged.connect(set_jitter)
        layout.addWidget(QtWidgets.QLabel("Jitter (humanization)"))
        layout.addWidget(recoil_jitter_slider)
        layout.addWidget(recoil_jitter_label)
        pattern_combo = QtWidgets.QComboBox()
        pattern_combo.addItems(["Default", "Pattern 1", "Pattern 2"])
        pattern_combo.setCurrentText(self.settings.get('recoil_pattern', "Default"))
        pattern_combo.currentTextChanged.connect(lambda v: self.update_setting('recoil_pattern', v))
        layout.addWidget(QtWidgets.QLabel("Recoil Pattern"))
        layout.addWidget(pattern_combo)
        activation_key = HotkeyButton("Set Recoil Activation Key", "recoil_activation_key", self)
        layout.addWidget(QtWidgets.QLabel("Activation Key:"))
        layout.addWidget(activation_key)
        layout.addStretch(1)
        scroll = ScrollAreaNoBar()
        scroll.setWidgetResizable(True)
        w.setLayout(layout)
        scroll.setWidget(w)
        return scroll

    def handle_import_settings(self):
        new_settings = import_settings(self)
        if new_settings:
            self.settings = new_settings
            save_settings(self.settings)
            QtWidgets.QMessageBox.information(self, "Imported", "Settings imported. Restart app for all changes.")
            
    def paintEvent(self, event):
        path = QtGui.QPainterPath()
        path.addRoundedRect(QtCore.QRectF(self.rect()), self.radius, self.radius)
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setClipPath(path)
        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(QtGui.QColor(24, 26, 27, 220))
        painter.drawPath(path)
        super().paintEvent(event)
        
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    settings = load_settings()
    main_win = ModernMainWindow()
    main_win.show()
    sys.exit(app.exec_())