EU-Utility/projects/EU-Utility/core/floating_icon.py

136 lines
4.4 KiB
Python

"""
EU-Utility - Floating Icon
In-game floating button with Phosphor solid icons.
"""
from pathlib import Path
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication, QGraphicsDropShadowEffect
from PyQt6.QtCore import Qt, QPoint, pyqtSignal, QSize
from PyQt6.QtGui import QMouseEvent, QEnterEvent, QColor, QPixmap, QPainter
from PyQt6.QtSvg import QSvgRenderer
class FloatingIcon(QWidget):
"""Draggable floating icon with Phosphor solid icon."""
clicked = pyqtSignal()
ICONS_DIR = Path(__file__).parent.parent / "assets" / "icons"
def __init__(self, parent=None):
super().__init__(parent)
# Frameless, always on top
self.setWindowFlags(
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.WindowStaysOnTopHint |
Qt.WindowType.Tool
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setFixedSize(40, 40)
# Position - near top-left game icons
screen = QApplication.primaryScreen().geometry()
self.move(250, 10)
self.dragging = False
self.drag_position = QPoint()
self.click_threshold = 5
self.click_start_pos = QPoint()
self._setup_ui()
def _load_svg_icon(self, name, size=24):
"""Load SVG icon as pixmap."""
svg_path = self.ICONS_DIR / f"{name}.svg"
if not svg_path.exists():
return None
renderer = QSvgRenderer(str(svg_path))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return pixmap
def _setup_ui(self):
"""Setup floating icon with Phosphor target icon."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.icon_label = QLabel()
self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.icon_label.setFixedSize(40, 40)
# Load Phosphor target icon
pixmap = self._load_svg_icon("target", size=22)
if pixmap:
self.icon_label.setPixmap(pixmap)
else:
self.icon_label.setText("")
# EU-style frosted glass
self.icon_label.setStyleSheet("""
QLabel {
background-color: rgba(25, 30, 40, 230);
border-radius: 10px;
border: 1.5px solid rgba(255, 255, 255, 40);
}
""")
# Glow effect
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(15)
shadow.setColor(QColor(100, 150, 200, 80))
shadow.setOffset(0, 0)
self.icon_label.setGraphicsEffect(shadow)
layout.addWidget(self.icon_label)
def mousePressEvent(self, event: QMouseEvent):
if event.button() == Qt.MouseButton.LeftButton:
self.dragging = True
self.click_start_pos = event.globalPosition().toPoint()
self.drag_position = self.click_start_pos - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event: QMouseEvent):
if self.dragging:
new_pos = event.globalPosition().toPoint() - self.drag_position
self.move(new_pos)
event.accept()
def mouseReleaseEvent(self, event: QMouseEvent):
if event.button() == Qt.MouseButton.LeftButton:
release_pos = event.globalPosition().toPoint()
distance = (release_pos - self.click_start_pos).manhattanLength()
self.dragging = False
if distance < self.click_threshold:
self.clicked.emit()
event.accept()
def enterEvent(self, event: QEnterEvent):
self.icon_label.setStyleSheet("""
QLabel {
background-color: rgba(45, 60, 85, 250);
border-radius: 10px;
border: 1.5px solid rgba(120, 180, 255, 100);
}
""")
self.setCursor(Qt.CursorShape.PointingHandCursor)
def leaveEvent(self, event):
self.icon_label.setStyleSheet("""
QLabel {
background-color: rgba(25, 30, 40, 230);
border-radius: 10px;
border: 1.5px solid rgba(255, 255, 255, 40);
}
""")
self.setCursor(Qt.CursorShape.ArrowCursor)