""" Dark Mode Toggle Widget for PyQt5 Based on Corporate Design Guidelines with smooth animations """ from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QRectF, pyqtProperty, QByteArray from PyQt5.QtGui import QPainter, QColor, QBrush, QPen from PyQt5.QtSvg import QSvgRenderer class DarkModeToggle(QWidget): """ Animated Toggle-Switch for Dark/Light Mode with embedded SVG Icons. Features: - Smooth 200ms animation with InOutCubic easing - Embedded sun/moon icons (no external files needed) - Corporate design colors (#CBD5E0 for light, #00D4FF for dark) - Size: 60x30 pixels - PyQt5 compatible Signals: toggled(bool): Emitted when toggle state changes (True = Dark Mode) """ # Signal emitted when toggle state changes toggled = pyqtSignal(bool) def __init__(self, parent=None, initial_dark_mode=False): """ Initialize the Dark Mode Toggle. Args: parent: Parent widget initial_dark_mode: Initial state (False = Light Mode, True = Dark Mode) """ super().__init__(parent) # Fixed size as per design spec self.setFixedSize(60, 30) self.setCursor(Qt.PointingHandCursor) # State management self._checked = initial_dark_mode # Animation property for smooth sliding self._handle_position = 27.0 if initial_dark_mode else 3.0 # Configure animation self._animation = QPropertyAnimation(self, b"handle_position") self._animation.setDuration(200) # 200ms as per spec self._animation.setEasingCurve(QEasingCurve.InOutCubic) # Initialize SVG renderers self.sun_svg = QSvgRenderer() self.moon_svg = QSvgRenderer() # Load embedded SVG content self._load_svg_icons() # Tooltip for accessibility self._update_tooltip() def _load_svg_icons(self): """Load embedded SVG icons for sun and moon.""" # Sun icon for Light Mode (black strokes) sun_svg_content = b''' ''' # Moon icon for Dark Mode (dark blue strokes matching primary color) moon_svg_content = b''' ''' # Load SVG data into renderers self.sun_svg.load(QByteArray(sun_svg_content)) self.moon_svg.load(QByteArray(moon_svg_content)) # Property for animation def get_handle_position(self): """Get current handle position for animation.""" return self._handle_position def set_handle_position(self, pos): """Set handle position and trigger repaint.""" self._handle_position = pos self.update() # Trigger paintEvent # Define property for QPropertyAnimation handle_position = pyqtProperty(float, get_handle_position, set_handle_position) def paintEvent(self, event): """ Custom paint event to draw the toggle switch. Draws: 1. Track (background pill shape) 2. Handle (white circular button) 3. Icon (sun or moon in the handle) """ painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # Draw track (pill-shaped background) track_rect = QRectF(0, 0, self.width(), self.height()) track_radius = self.height() / 2 # Track color based on state if self._checked: # Dark mode active - cyan color from corporate design track_color = QColor("#00D4FF") else: # Light mode active - gray color from design spec track_color = QColor("#CBD5E0") painter.setBrush(QBrush(track_color)) painter.setPen(QPen(Qt.NoPen)) painter.drawRoundedRect(track_rect, track_radius, track_radius) # Draw handle (white circle that slides) handle_diameter = self.height() - 6 # 24px (30 - 6) handle_rect = QRectF( self._handle_position, # Animated position 3, # 3px from top handle_diameter, handle_diameter ) # White handle painter.setBrush(QBrush(QColor("#FFFFFF"))) painter.setPen(QPen(Qt.NoPen)) painter.drawEllipse(handle_rect) # Draw icon inside handle icon_size = 16 # Icon size within the 24px handle icon_rect = QRectF( handle_rect.x() + (handle_diameter - icon_size) / 2, handle_rect.y() + (handle_diameter - icon_size) / 2, icon_size, icon_size ) # Render appropriate icon if self._checked: # Dark mode - show moon icon self.moon_svg.render(painter, icon_rect) else: # Light mode - show sun icon self.sun_svg.render(painter, icon_rect) def mousePressEvent(self, event): """ Handle mouse click to toggle state. Only responds to left mouse button clicks. """ if event.button() == Qt.LeftButton: self.toggle() def toggle(self): """Toggle between Light and Dark mode.""" self.setChecked(not self._checked) def setChecked(self, checked): """ Set the toggle state programmatically. Args: checked: True for Dark Mode, False for Light Mode """ # Only proceed if state actually changes if self._checked == checked: return self._checked = checked # Stop any running animation self._animation.stop() # Set animation endpoints if checked: # Animate to dark mode position (right side) # Handle should be at: width - handle_diameter - 3px margin self._animation.setEndValue(self.width() - (self.height() - 6) - 3) else: # Animate to light mode position (left side) self._animation.setEndValue(3.0) # Start animation self._animation.start() # Update tooltip self._update_tooltip() # Emit signal for theme change self.toggled.emit(checked) def isChecked(self): """ Get current toggle state. Returns: bool: True if Dark Mode is active, False for Light Mode """ return self._checked def _update_tooltip(self): """Update tooltip based on current state.""" if self._checked: self.setToolTip("Zu Light Mode wechseln") else: self.setToolTip("Zu Dark Mode wechseln") def isDarkMode(self): """ Convenience method to check if dark mode is active. Returns: bool: True if Dark Mode is active """ return self._checked def isLightMode(self): """ Convenience method to check if light mode is active. Returns: bool: True if Light Mode is active """ return not self._checked