Files
AccountForger-neuerUpload/views/widgets/dark_mode_toggle.py
Claude Project Manager 2644c4e111 DarkMode ist existent yeah
2025-08-10 17:46:30 +02:00

233 Zeilen
8.0 KiB
Python

"""
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'''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 3V4M12 20V21M4 12H3M6.31412 6.31412L5.5 5.5M17.6859 6.31412L18.5 5.5M6.31412 17.69L5.5 18.5001M17.6859 17.69L18.5 18.5001M21 12H20M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>'''
# Moon icon for Dark Mode (dark blue strokes matching primary color)
moon_svg_content = b'''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.32031 11.6835C3.32031 16.6541 7.34975 20.6835 12.3203 20.6835C16.1075 20.6835 19.3483 18.3443 20.6768 15.032C19.6402 15.4486 18.5059 15.6834 17.3203 15.6834C12.3497 15.6834 8.32031 11.654 8.32031 6.68342C8.32031 5.50338 8.55165 4.36259 8.96453 3.32996C5.65605 4.66028 3.32031 7.89912 3.32031 11.6835Z" stroke="#232D53" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>'''
# 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