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

188 lines
5.4 KiB
Python

# Description: Theme system for EU-Utility
# Provides dark, light, and auto theme support
"""
EU-Utility Theme System
Supports Dark, Light, and Auto (system-based) themes
"""
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
from PyQt6.QtGui import QColor, QPalette
import json
import os
class ThemeManager:
"""Central theme management for EU-Utility."""
theme_changed = pyqtSignal(str) # Emitted when theme changes
# Theme definitions
THEMES = {
'dark': {
'name': 'Dark',
'bg_primary': '#1a1a2e',
'bg_secondary': '#16213e',
'bg_tertiary': '#0f3460',
'accent': '#e94560',
'text_primary': '#ffffff',
'text_secondary': '#b8b8b8',
'border': '#2d2d44',
'success': '#4caf50',
'warning': '#ff9800',
'error': '#f44336',
},
'light': {
'name': 'Light',
'bg_primary': '#f5f5f5',
'bg_secondary': '#ffffff',
'bg_tertiary': '#e0e0e0',
'accent': '#2196f3',
'text_primary': '#212121',
'text_secondary': '#757575',
'border': '#bdbdbd',
'success': '#4caf50',
'warning': '#ff9800',
'error': '#f44336',
},
'eu_classic': {
'name': 'EU Classic',
'bg_primary': '#141f23',
'bg_secondary': '#1a2a30',
'bg_tertiary': '#0f1416',
'accent': '#ff8c42',
'text_primary': '#ffffff',
'text_secondary': '#a0a0a0',
'border': '#2a3a40',
'success': '#4ecdc4',
'warning': '#ff8c42',
'error': '#e74c3c',
}
}
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self.current_theme = 'eu_classic'
self.auto_theme = False
self._load_settings()
def _load_settings(self):
"""Load theme settings from file."""
try:
config_path = os.path.expanduser('~/.eu-utility/theme.json')
if os.path.exists(config_path):
with open(config_path, 'r') as f:
settings = json.load(f)
self.current_theme = settings.get('theme', 'eu_classic')
self.auto_theme = settings.get('auto', False)
except:
pass
def _save_settings(self):
"""Save theme settings to file."""
try:
config_dir = os.path.expanduser('~/.eu-utility')
os.makedirs(config_dir, exist_ok=True)
config_path = os.path.join(config_dir, 'theme.json')
with open(config_path, 'w') as f:
json.dump({
'theme': self.current_theme,
'auto': self.auto_theme
}, f)
except:
pass
def get_theme(self, name=None):
"""Get theme colors dictionary."""
if name is None:
name = self.current_theme
return self.THEMES.get(name, self.THEMES['eu_classic'])
def set_theme(self, name):
"""Set active theme."""
if name in self.THEMES:
self.current_theme = name
self._save_settings()
self.theme_changed.emit(name)
def get_stylesheet(self, theme_name=None):
"""Generate QSS stylesheet for theme."""
t = self.get_theme(theme_name)
return f"""
QWidget {{
background-color: {t['bg_primary']};
color: {t['text_primary']};
font-family: 'Segoe UI', sans-serif;
}}
QPushButton {{
background-color: {t['bg_tertiary']};
color: {t['text_primary']};
border: 1px solid {t['border']};
padding: 8px 16px;
border-radius: 4px;
}}
QPushButton:hover {{
background-color: {t['accent']};
}}
QLineEdit, QTextEdit {{
background-color: {t['bg_secondary']};
color: {t['text_primary']};
border: 1px solid {t['border']};
padding: 6px;
border-radius: 4px;
}}
QLabel {{
color: {t['text_primary']};
}}
QComboBox {{
background-color: {t['bg_secondary']};
color: {t['text_primary']};
border: 1px solid {t['border']};
padding: 6px;
border-radius: 4px;
}}
QProgressBar {{
background-color: {t['bg_secondary']};
border: 1px solid {t['border']};
border-radius: 4px;
}}
QProgressBar::chunk {{
background-color: {t['accent']};
border-radius: 4px;
}}
QScrollBar:vertical {{
background-color: {t['bg_secondary']};
width: 12px;
border-radius: 6px;
}}
QScrollBar::handle:vertical {{
background-color: {t['border']};
border-radius: 6px;
}}
"""
# Global instance
theme_manager = ThemeManager()