Files
whisper_voice/src/ui/overlay.py
2026-01-24 17:03:52 +02:00

106 lines
3.7 KiB
Python

"""
Overlay Window Module.
======================
Premium High-Fidelity Overlay for Whisper Voice.
Features glassmorphism, pulsating status indicators, and smart positioning.
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
from PySide6.QtCore import Qt, Slot, QPoint, QPropertyAnimation, QEasingCurve
from PySide6.QtGui import QColor, QFont, QGuiApplication
from src.ui.visualizer import AudioVisualizer
from src.ui.styles import Theme, StyleGenerator, load_modern_fonts
from src.ui.components import FramelessWindow, ModernFrame
class OverlayWindow(FramelessWindow):
"""
The main transparent overlay (The Pill).
Refactored for 2026 Premium Aesthetics.
"""
def __init__(self):
super().__init__()
self.setFixedSize(320, 95)
# Main Layout
self.master_layout = QVBoxLayout(self)
self.master_layout.setContentsMargins(10, 10, 10, 10)
# The Glass Pill Container
self.pill = ModernFrame()
self.pill.setStyleSheet(StyleGenerator.get_glass_card(radius=24))
self.master_layout.addWidget(self.pill)
# Layout inside the pill
self.layout = QHBoxLayout(self.pill)
self.layout.setContentsMargins(20, 10, 20, 10)
self.layout.setSpacing(15)
# Status Visualization (Left Dot)
self.status_dot = QWidget()
self.status_dot.setFixedSize(14, 14)
self.status_dot.setStyleSheet(f"background-color: {Theme.ACCENT_CYAN}; border-radius: 7px; border: 2px solid white;")
self.layout.addWidget(self.status_dot)
# Text/Visualizer Stack
self.content_stack = QVBoxLayout()
self.content_stack.setSpacing(2)
self.content_stack.setContentsMargins(0, 0, 0, 0)
self.status_label = QLabel("READY")
self.status_label.setFont(load_modern_fonts())
self.status_label.setStyleSheet(f"color: white; font-weight: 800; font-size: 11px; letter-spacing: 2px;")
self.content_stack.addWidget(self.status_label)
self.visualizer = AudioVisualizer()
self.visualizer.setFixedHeight(30)
self.content_stack.addWidget(self.visualizer)
self.layout.addLayout(self.content_stack)
# Animations
self.pulse_timer = None # Use style-based pulsing to avoid window flags issues
# Initial State
self.hide()
self.first_show = True
def showEvent(self, event):
"""Handle positioning and config updates."""
from src.core.config import ConfigManager
config = ConfigManager()
self.setWindowOpacity(config.get("opacity"))
if self.first_show:
self.center_above_taskbar()
self.first_show = False
super().showEvent(event)
def center_above_taskbar(self):
screen = QGuiApplication.primaryScreen()
if not screen: return
avail_rect = screen.availableGeometry()
x = avail_rect.x() + (avail_rect.width() - self.width()) // 2
y = avail_rect.bottom() - self.height() - 15
self.move(x, y)
@Slot(str)
def update_status(self, text: str):
"""Updates the status text and visual indicator."""
self.status_label.setText(text.upper())
if "RECORDING" in text.upper():
color = Theme.ACCENT_GREEN
elif "THINKING" in text.upper():
color = Theme.ACCENT_PURPLE
else:
color = Theme.ACCENT_CYAN
self.status_dot.setStyleSheet(f"background-color: {color}; border-radius: 7px; border: 2px solid white;")
@Slot(float)
def update_visualizer(self, amp: float):
self.visualizer.set_amplitude(amp)