From 2644c4e11104c702b52bc0cb828345f23763a887 Mon Sep 17 00:00:00 2001 From: Claude Project Manager Date: Sun, 10 Aug 2025 17:46:30 +0200 Subject: [PATCH] DarkMode ist existent yeah --- .claude/settings.local.json | 10 +- controllers/main_controller.py | 6 +- resources/icons/intelsight-dark.svg | 53 ++ resources/icons/intelsight-logo.svg | 14 +- resources/themes/dark.qss | 692 +++++++++++++++++- resources/themes/light.qss | 32 + test_logo_display.py | 65 ++ test_logo_switching.py | 88 +++ test_theme_system.py | 279 +++++++ themes/__init__.py | 8 + themes/qss_generator.py | 805 +++++++++++++++++++++ themes/theme_config.py | 247 +++++++ utils/theme_manager.py | 201 +++-- views/about_dialog.py | 51 +- views/base/__init__.py | 7 + views/base/theme_aware_widget.py | 99 +++ views/components/accounts_overview_view.py | 99 +-- views/components/platform_grid_view.py | 10 +- views/components/tab_navigation.py | 34 +- views/main_window.py | 118 ++- views/platform_selector.py | 2 +- views/widgets/account_card.py | 169 +---- views/widgets/dark_mode_toggle.py | 233 ++++++ views/widgets/platform_button.py | 34 +- 24 files changed, 2930 insertions(+), 426 deletions(-) create mode 100644 resources/icons/intelsight-dark.svg create mode 100644 test_logo_display.py create mode 100644 test_logo_switching.py create mode 100644 test_theme_system.py create mode 100644 themes/__init__.py create mode 100644 themes/qss_generator.py create mode 100644 themes/theme_config.py create mode 100644 views/base/__init__.py create mode 100644 views/base/theme_aware_widget.py create mode 100644 views/widgets/dark_mode_toggle.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5931fef..3262f6d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,8 +5,14 @@ "Bash(nslookup:*)", "WebFetch(domain:multilogin.com)", "WebFetch(domain:dicloak.com)", - "WebFetch(domain:support.google.com)" + "WebFetch(domain:support.google.com)", + "Bash(python3 -m pip list:*)", + "Bash(python3:*)", + "Bash(grep:*)" ], - "deny": [] + "deny": [], + "additionalDirectories": [ + "/mnt/a/GiTea/Styleguide" + ] } } \ No newline at end of file diff --git a/controllers/main_controller.py b/controllers/main_controller.py index 9e9cbd1..0cd491f 100644 --- a/controllers/main_controller.py +++ b/controllers/main_controller.py @@ -127,6 +127,9 @@ class MainController: # Signals verbinden self.connect_signals() + # Remove unused theme_toggled connection + # The theme toggle is now handled directly in MainWindow + # Platform Selector Signal-Verbindungen if hasattr(self.view.platform_selector, 'export_requested'): self.view.platform_selector.export_requested.connect( @@ -195,9 +198,6 @@ class MainController: # Zurück-Button verbinden self.view.back_to_selector_requested.connect(self.show_platform_selector) - # Theme-Toggle verbinden - self.view.theme_toggled.connect(self.on_theme_toggled) - def on_platform_selected(self, platform: str): """Wird aufgerufen, wenn eine Plattform ausgewählt wird.""" logger.info(f"Plattform ausgewählt: {platform}") diff --git a/resources/icons/intelsight-dark.svg b/resources/icons/intelsight-dark.svg new file mode 100644 index 0000000..c61d83e --- /dev/null +++ b/resources/icons/intelsight-dark.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IntelSight + + \ No newline at end of file diff --git a/resources/icons/intelsight-logo.svg b/resources/icons/intelsight-logo.svg index 7e5c2dd..b729e91 100644 --- a/resources/icons/intelsight-logo.svg +++ b/resources/icons/intelsight-logo.svg @@ -17,7 +17,7 @@ L 25 40 L 35 30 Z" fill="none" - stroke="currentColor" + stroke="#232D53" stroke-width="3.5" stroke-linejoin="miter"/> @@ -26,28 +26,28 @@ - + - + - + - + IntelSight \ No newline at end of file diff --git a/resources/themes/dark.qss b/resources/themes/dark.qss index cff011d..aa701f2 100644 --- a/resources/themes/dark.qss +++ b/resources/themes/dark.qss @@ -1 +1,691 @@ -/* Auto-generated empty stylesheet */ +/* + * AccountForger Dark Mode Theme + * Based on IntelSight Corporate Design System + * Color Palette from CORPORATE_DESIGN_DARK_MODE.md + */ + +/* ==================== COLOR VARIABLES (Reference) ==================== */ +/* + * Primary: #232D53 (Dark Blue) + * Accent: #00D4FF (Cyan) + * Accent Hover: #00B8E6 (Darker Cyan) + * Background: #000000 (Black) + * Secondary BG: #1A1F3A (Dark Blue for cards) + * Sidebar: #0A0A0A (Almost Black) + * Text Primary: #FFFFFF (White) + * Text Secondary: rgba(255, 255, 255, 0.7) (70% White) + * Text Tertiary: rgba(255, 255, 255, 0.6) (60% White) + * Error: #FF4444 (Red) + * Success: #4CAF50 (Green) + * Warning: #FFC107 (Yellow) + * Info: #2196F3 (Blue) + */ + +/* ==================== MAIN WINDOW ==================== */ +QMainWindow { + background-color: #000000; + color: #FFFFFF; +} + +/* ==================== WIDGETS & CONTAINERS ==================== */ +QWidget { + background-color: #000000; + color: #FFFFFF; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; + font-size: 14px; +} + +/* Platform selector main container */ +QWidget#main_container { + background-color: #000000; + padding: 40px; +} + +/* Tab content areas */ +QStackedWidget { + background-color: #000000; +} + +/* ==================== HEADERS & LABELS ==================== */ +QLabel { + color: #FFFFFF; + background-color: transparent; + padding: 2px; +} + +/* Title Labels */ +QLabel#platform_title { + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 32px; + font-weight: 700; + color: #FFFFFF; + padding: 16px; + letter-spacing: 1px; +} + +/* Secondary text */ +QLabel#secondary_text { + color: rgba(255, 255, 255, 0.7); +} + +/* ==================== PLATFORM BUTTONS (Clean styling) ==================== */ +/* Platform button container */ +PlatformButton { + background-color: transparent; +} + +/* Platform icon buttons */ +QPushButton#platform_icon_button { + background-color: #1A1F3A; + border: 1px solid transparent; + border-radius: 16px; + padding: 32px; +} + +QPushButton#platform_icon_button:hover { + background-color: #232D53; + border: 1px solid #00D4FF; +} + +QPushButton#platform_icon_button:pressed { + background-color: #2A3560; + border: 1px solid #00D4FF; +} + +QPushButton#platform_icon_button:disabled { + background-color: rgba(26, 31, 58, 0.5); + opacity: 0.5; +} + +/* Platform name labels */ +QLabel#platform_name_label { + color: #FFFFFF; + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-weight: 600; + letter-spacing: 0.5px; +} + +/* ==================== PRIMARY BUTTONS ==================== */ +QPushButton { + background-color: #00D4FF; + color: #000000; + border: none; + border-radius: 24px; + padding: 0 32px; + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 15px; + font-weight: 600; + min-height: 48px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +QPushButton:hover { + background-color: #00B8E6; + color: #FFFFFF; +} + +QPushButton:pressed { + background-color: #0099CC; + color: #FFFFFF; +} + +QPushButton:disabled { + background-color: rgba(0, 212, 255, 0.3); + color: rgba(255, 255, 255, 0.5); + opacity: 0.5; +} + +/* Secondary Button */ +QPushButton#secondary_button { + background-color: transparent; + color: #FFFFFF; + border: 1px solid #232D53; +} + +QPushButton#secondary_button:hover { + background-color: #232D53; + color: #FFFFFF; +} + +/* Icon Buttons */ +QPushButton#icon_button { + background-color: transparent; + border: none; + border-radius: 8px; + padding: 8px; +} + +QPushButton#icon_button:hover { + background-color: rgba(35, 45, 83, 0.5); +} + +/* ==================== INPUT FIELDS ==================== */ +QLineEdit, QSpinBox, QTextEdit, QPlainTextEdit { + background-color: #232D53; + color: #FFFFFF; + border: none; + border-radius: 8px; + padding: 12px 16px; + font-size: 14px; +} + +QLineEdit:focus, QSpinBox:focus, QTextEdit:focus, QPlainTextEdit:focus { + background-color: #2A3560; + outline: 2px solid #00D4FF; + outline-offset: -2px; +} + +QLineEdit:disabled, QSpinBox:disabled, QTextEdit:disabled { + background-color: rgba(35, 45, 83, 0.5); + color: rgba(255, 255, 255, 0.5); +} + +/* Placeholder text */ +QLineEdit { + selection-background-color: #00D4FF; + selection-color: #000000; +} + +/* ==================== DROPDOWNS / COMBOBOX ==================== */ +QComboBox { + background-color: #232D53; + color: #FFFFFF; + border: none; + border-radius: 8px; + padding: 8px 16px; + min-height: 32px; +} + +QComboBox:hover { + background-color: #2A3560; +} + +QComboBox:focus { + outline: 2px solid #00D4FF; + outline-offset: -2px; +} + +QComboBox::drop-down { + border: none; + width: 20px; + background-color: transparent; +} + +QComboBox::down-arrow { + image: none; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #00D4FF; + width: 0; + height: 0; + margin-right: 5px; +} + +QComboBox QAbstractItemView { + background-color: rgba(26, 31, 58, 0.95); + border: 1px solid #00D4FF; + border-radius: 8px; + selection-background-color: #232D53; + selection-color: #00D4FF; + color: #FFFFFF; +} + +/* ==================== CHECKBOXES & RADIO BUTTONS ==================== */ +QCheckBox, QRadioButton { + spacing: 8px; + color: #FFFFFF; +} + +QCheckBox::indicator, QRadioButton::indicator { + width: 20px; + height: 20px; + border: 2px solid #232D53; + background-color: transparent; +} + +QCheckBox::indicator { + border-radius: 4px; +} + +QRadioButton::indicator { + border-radius: 10px; +} + +QCheckBox::indicator:checked, QRadioButton::indicator:checked { + background-color: #00D4FF; + border-color: #00D4FF; +} + +QCheckBox::indicator:checked { + image: url(:/icons/check.svg); +} + +QRadioButton::indicator:checked { + background-color: #00D4FF; +} + +/* ==================== GROUP BOXES ==================== */ +QGroupBox { + background-color: #1A1F3A; + border: 1px solid transparent; + border-radius: 16px; + padding: 32px; + margin-top: 16px; + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-weight: 600; + color: #FFFFFF; +} + +QGroupBox::title { + color: #00D4FF; + font-size: 16px; + subcontrol-origin: margin; + left: 16px; + padding: 0 8px; + background-color: #1A1F3A; +} + +/* ==================== TABS ==================== */ +QTabWidget::pane { + background-color: #000000; + border: none; +} + +QTabBar::tab { + background-color: transparent; + color: rgba(255, 255, 255, 0.6); + padding: 12px 24px; + font-size: 14px; + font-weight: 500; + border: none; +} + +QTabBar::tab:hover { + background-color: rgba(35, 45, 83, 0.3); + color: #FFFFFF; +} + +QTabBar::tab:selected { + color: #FFFFFF; + background-color: transparent; + border-bottom: 2px solid #00D4FF; + font-weight: 600; +} + +/* Tab Navigation Component */ +TabNavigation { + background-color: #0A0A0A; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +TabNavigation QPushButton { + background-color: transparent; + color: rgba(255, 255, 255, 0.6); + border: none; + border-radius: 0; + padding: 16px 24px; + font-weight: 500; + min-height: 48px; +} + +TabNavigation QPushButton:hover { + background-color: rgba(35, 45, 83, 0.3); + color: #FFFFFF; +} + +TabNavigation QPushButton:checked { + color: #00D4FF; + background-color: transparent; + border-bottom: 2px solid #00D4FF; +} + +/* Badge in tabs */ +TabNavigation QLabel#badge { + background-color: #00D4FF; + color: #000000; + padding: 2px 6px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; +} + +/* ==================== TABLES ==================== */ +QTableWidget { + background-color: #000000; + alternate-background-color: rgba(26, 31, 58, 0.3); + gridline-color: rgba(255, 255, 255, 0.1); + border: none; + color: #FFFFFF; +} + +QTableWidget::item { + padding: 12px 16px; + color: #FFFFFF; +} + +QTableWidget::item:selected { + background-color: #232D53; + color: #00D4FF; +} + +QHeaderView::section { + background-color: #232D53; + color: #FFFFFF; + padding: 12px 16px; + border: none; + font-weight: 600; + text-transform: uppercase; + font-size: 13px; + letter-spacing: 0.5px; +} + +/* ==================== SCROLLBARS ==================== */ +QScrollBar:vertical { + background-color: #1A1F3A; + width: 8px; + border-radius: 4px; +} + +QScrollBar::handle:vertical { + background-color: #00D4FF; + min-height: 20px; + border-radius: 4px; +} + +QScrollBar::handle:vertical:hover { + background-color: #00B8E6; +} + +QScrollBar:horizontal { + background-color: #1A1F3A; + height: 8px; + border-radius: 4px; +} + +QScrollBar::handle:horizontal { + background-color: #00D4FF; + min-width: 20px; + border-radius: 4px; +} + +QScrollBar::handle:horizontal:hover { + background-color: #00B8E6; +} + +QScrollBar::add-line, QScrollBar::sub-line { + border: none; + background: none; +} + +/* ==================== PROGRESS BAR ==================== */ +QProgressBar { + background-color: #232D53; + border: none; + border-radius: 4px; + height: 8px; + text-align: center; +} + +QProgressBar::chunk { + background-color: #00D4FF; + border-radius: 4px; +} + +/* ==================== STATUS BAR ==================== */ +QStatusBar { + background-color: #0A0A0A; + color: rgba(255, 255, 255, 0.7); + font-size: 13px; + padding: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +/* ==================== MENU BAR ==================== */ +QMenuBar { + background-color: #0A0A0A; + color: #FFFFFF; + padding: 4px; +} + +QMenuBar::item { + padding: 8px 16px; + background-color: transparent; + color: #FFFFFF; +} + +QMenuBar::item:selected { + background-color: #232D53; + color: #00D4FF; +} + +QMenu { + background-color: rgba(26, 31, 58, 0.95); + border: 1px solid rgba(0, 212, 255, 0.2); + border-radius: 8px; + padding: 8px 0; +} + +QMenu::item { + padding: 8px 32px; + color: #FFFFFF; +} + +QMenu::item:selected { + background-color: #232D53; + color: #00D4FF; +} + +/* ==================== TOOLTIPS ==================== */ +QToolTip { + background-color: #232D53; + color: #FFFFFF; + border: 1px solid rgba(0, 212, 255, 0.2); + border-radius: 8px; + padding: 8px 12px; + font-size: 13px; +} + +/* ==================== DIALOGS ==================== */ +QDialog { + background-color: #000000; + color: #FFFFFF; +} + +/* Dialog Title Bar */ +QDialog QWidget#title_bar { + background-color: #232D53; + min-height: 40px; + border-radius: 8px 8px 0 0; +} + +/* ==================== MESSAGE BOXES ==================== */ +QMessageBox { + background-color: #000000; +} + +QMessageBox QLabel { + color: #FFFFFF; +} + +QMessageBox QPushButton { + background-color: #232D53; + color: #FFFFFF; + border: 1px solid transparent; + border-radius: 4px; + padding: 6px 20px; + min-width: 80px; + min-height: 26px; + font-weight: 500; +} + +QMessageBox QPushButton:hover { + background-color: #2A3560; + border-color: #00D4FF; +} + +QMessageBox QPushButton:pressed { + background-color: #1A1F3A; +} + +/* Delete Button - Red accent */ +QMessageBox QPushButton[text="Löschen"], QMessageBox QPushButton[text="Delete"] { + background-color: #FF4444; + color: #FFFFFF; + border: none; +} + +QMessageBox QPushButton[text="Löschen"]:hover, QMessageBox QPushButton[text="Delete"]:hover { + background-color: #FF6666; +} + +/* ==================== ACCOUNT CARDS ==================== */ +/* Account card widget */ +QWidget#account_card { + background-color: #1A1F3A; + border: 1px solid transparent; + border-radius: 16px; + padding: 32px; +} + +QWidget#account_card:hover { + background-color: #232D53; + border: 1px solid #00D4FF; +} + +/* ==================== LOG OUTPUT ==================== */ +QTextEdit#log_output { + background-color: #1A1F3A; + color: #FFFFFF; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace; + font-size: 13px; + border: none; + border-radius: 8px; + padding: 16px; +} + +/* ==================== BADGES & TAGS ==================== */ +QLabel#badge { + background-color: rgba(255, 255, 255, 0.1); + color: #FFFFFF; + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +QLabel#badge_primary { + background-color: #00D4FF; + color: #000000; +} + +QLabel#badge_success { + background-color: #4CAF50; + color: #FFFFFF; +} + +QLabel#badge_warning { + background-color: #FFC107; + color: #000000; +} + +QLabel#badge_error { + background-color: #FF4444; + color: #FFFFFF; +} + +/* ==================== SPECIAL EFFECTS ==================== */ +/* Glow effect for important elements */ +.glow { + box-shadow: 0 0 20px rgba(0, 212, 255, 0.3); +} + +/* Success text */ +.success { + color: #4CAF50; +} + +/* Error text */ +.error { + color: #FF4444; +} + +/* Warning text */ +.warning { + color: #FFC107; +} + +/* Info text */ +.info { + color: #2196F3; +} + +/* ==================== LANGUAGE DROPDOWN ==================== */ +LanguageDropdown { + background-color: transparent; +} + +LanguageDropdown QComboBox { + background-color: #232D53; + min-width: 150px; +} + +/* ==================== FORGE ANIMATION ==================== */ +ForgeAnimationWidget { + background-color: #000000; +} + +/* ==================== MODERN MESSAGE BOX ==================== */ +ModernMessageBox { + background-color: #000000; + border: 1px solid #232D53; + border-radius: 16px; +} + +ModernMessageBox QLabel#title { + color: #00D4FF; + font-size: 18px; + font-weight: 600; +} + +ModernMessageBox QLabel#message { + color: rgba(255, 255, 255, 0.9); +} + +/* ==================== SIDEBAR (if present) ==================== */ +QWidget#sidebar { + background-color: #0A0A0A; + border-right: 1px solid rgba(255, 255, 255, 0.1); +} + +/* ==================== PLATFORM GRID VIEW ==================== */ +PlatformGridView { + background-color: #000000; +} + +/* ==================== ACCOUNTS OVERVIEW ==================== */ +AccountsOverviewView { + background-color: #000000; +} + +/* Account cards in overview */ +AccountCard { + background-color: #1A1F3A; + border-radius: 12px; + padding: 16px; +} + +AccountCard:hover { + background-color: #232D53; + border: 1px solid #00D4FF; +} + +/* ==================== TRANSITIONS ==================== */ +/* Note: Qt doesn't support CSS transitions directly, + but these are documented for custom implementation */ +* { + /* Standard transition: all 0.3s ease */ + /* Fast transition: all 0.2s ease (for smaller elements) */ +} \ No newline at end of file diff --git a/resources/themes/light.qss b/resources/themes/light.qss index fc5d3ba..5b7cc5a 100644 --- a/resources/themes/light.qss +++ b/resources/themes/light.qss @@ -433,6 +433,38 @@ QMessageBox QPushButton[text="Löschen"]:pressed { background-color: #B71C1C; } +/* ==================== PLATFORM BUTTONS (Clean styling) ==================== */ +/* Platform icon buttons */ +QPushButton#platform_icon_button { + background-color: #F5F7FF; + border: 1px solid transparent; + border-radius: 16px; + padding: 32px; +} + +QPushButton#platform_icon_button:hover { + background-color: #E8EBFF; + border: 1px solid #0099CC; +} + +QPushButton#platform_icon_button:pressed { + background-color: #DCE2FF; + border: 1px solid #0099CC; +} + +QPushButton#platform_icon_button:disabled { + background-color: #F0F0F0; + opacity: 0.5; +} + +/* Platform name labels */ +QLabel#platform_name_label { + color: #232D53; + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-weight: 600; + letter-spacing: 0.5px; +} + /* ==================== SPECIAL COLORS ==================== */ /* Success */ .success { diff --git a/test_logo_display.py b/test_logo_display.py new file mode 100644 index 0000000..14f89b0 --- /dev/null +++ b/test_logo_display.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Debug script to check what's happening with logo switching +""" + +import os +import sys + +# Add project root to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from themes.theme_config import ThemeConfig + +def check_logo_files(): + """Check the actual logo files and their paths.""" + print("=" * 60) + print("LOGO FILE ANALYSIS") + print("=" * 60) + + base_dir = os.path.dirname(os.path.abspath(__file__)) + + # Check what's in the config + light_theme = ThemeConfig.get_theme('light') + dark_theme = ThemeConfig.get_theme('dark') + + print(f"\nTheme Config:") + print(f" Light theme logo_path: {light_theme.get('logo_path', 'NOT SET')}") + print(f" Dark theme logo_path: {dark_theme.get('logo_path', 'NOT SET')}") + + # Check actual files + icons_dir = os.path.join(base_dir, "resources", "icons") + print(f"\nIcon files in {icons_dir}:") + + for file in os.listdir(icons_dir): + if "intelsight" in file.lower(): + file_path = os.path.join(icons_dir, file) + file_size = os.path.getsize(file_path) + print(f" - {file} ({file_size} bytes)") + + # Test the path resolution + print("\n" + "=" * 60) + print("PATH RESOLUTION TEST") + print("=" * 60) + + # Simulate what happens in get_icon_path + for theme_name in ['light', 'dark']: + theme = ThemeConfig.get_theme(theme_name) + logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '') + full_path = os.path.join(base_dir, "resources", "icons", f"{logo_name}.svg") + + print(f"\n{theme_name.upper()} theme:") + print(f" logo_name from config: {logo_name}") + print(f" full_path: {full_path}") + print(f" file exists: {os.path.exists(full_path)}") + + if os.path.exists(full_path): + # Check if it's actually an SVG + with open(full_path, 'r') as f: + first_line = f.readline().strip() + is_svg = ' str: + """ + Generate complete QSS for the specified theme. + + Args: + theme_name: 'light' or 'dark' + + Returns: + Complete QSS stylesheet as string + """ + theme = ThemeConfig.get_theme(theme_name) + fonts = ThemeConfig.FONTS + sizes = ThemeConfig.FONT_SIZES + weights = ThemeConfig.FONT_WEIGHTS + spacing = ThemeConfig.SPACING + radius = ThemeConfig.RADIUS + + qss = f""" +/* ==================== AUTO-GENERATED THEME: {theme_name.upper()} ==================== */ +/* Generated from themes/theme_config.py - DO NOT EDIT DIRECTLY */ + +/* ==================== MAIN WINDOW ==================== */ +QMainWindow {{ + background-color: {theme['bg_primary']}; + color: {theme['text_primary']}; +}} + +/* ==================== BASE WIDGETS ==================== */ +QWidget {{ + background-color: {theme['bg_primary']}; + color: {theme['text_primary']}; + font-family: {fonts['secondary']}; + font-size: {sizes['base']}; +}} + +/* Content Stack - Main content area */ +QStackedWidget#content_stack {{ + background-color: {theme['bg_secondary']}; +}} + +/* ==================== LABELS ==================== */ +QLabel {{ + color: {theme['text_primary']}; + background-color: transparent; + padding: 2px; +}} + +/* Platform Title */ +QLabel#platform_title {{ + font-family: {fonts['primary']}; + font-size: {sizes['3xl']}; + font-weight: {weights['bold']}; + color: {theme['text_accent']}; + padding: {spacing['md']}; + letter-spacing: 1px; +}} + +/* Secondary Labels */ +QLabel#secondary_text {{ + color: {theme['text_secondary']}; +}} + +/* Platform Name Labels */ +QLabel#platform_name_label {{ + color: {theme['platform_label']}; + font-family: {fonts['primary']}; + font-weight: {weights['semibold']}; + letter-spacing: 0.5px; +}} + +/* ==================== PLATFORM BUTTONS ==================== */ +PlatformButton {{ + background-color: transparent; +}} + +QPushButton#platform_icon_button {{ + background-color: {theme['platform_bg']}; + border: 1px solid {theme['platform_border']}; + border-radius: {radius['xl']}; + padding: {spacing['xl']}; +}} + +QPushButton#platform_icon_button:hover {{ + background-color: {theme['platform_hover']}; + border: 1px solid {theme['platform_border_hover']}; +}} + +QPushButton#platform_icon_button:pressed {{ + background-color: {theme['platform_pressed']}; + border: 1px solid {theme['platform_border_hover']}; +}} + +QPushButton#platform_icon_button:disabled {{ + background-color: {theme['bg_tertiary']}; + opacity: 0.5; +}} + +/* ==================== BUTTONS ==================== */ +QPushButton {{ + background-color: {theme['btn_primary_bg']}; + color: {theme['btn_primary_text']}; + border: none; + border-radius: {radius['round']}; + padding: 0 {spacing['xl']}; + font-family: {fonts['primary']}; + font-size: {sizes['base']}; + font-weight: {weights['semibold']}; + min-height: 48px; + text-transform: uppercase; + letter-spacing: 0.5px; +}} + +QPushButton:hover {{ + background-color: {theme['accent_hover']}; +}} + +QPushButton:pressed {{ + background-color: {theme['accent_pressed']}; +}} + +QPushButton:disabled {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_tertiary']}; + opacity: 0.5; +}} + +/* Secondary Buttons */ +QPushButton#secondary_button {{ + background-color: {theme['btn_secondary_bg']}; + color: {theme['btn_secondary_text']}; + border: 1px solid {theme['btn_secondary_border']}; +}} + +QPushButton#secondary_button:hover {{ + background-color: {theme['bg_hover']}; +}} + +/* Danger Buttons */ +QPushButton#danger_button {{ + background-color: {theme['btn_danger_bg']}; + color: {theme['btn_danger_text']}; +}} + +QPushButton#danger_button:hover {{ + background-color: {theme['error']}; +}} + +/* ==================== TAB NAVIGATION ==================== */ +TabNavigation, QWidget#tab_navigation {{ + background-color: {theme['nav_bg']}; + border-bottom: 1px solid {theme['nav_border']}; +}} + +TabNavigation QPushButton {{ + background-color: {theme['nav_item']}; + color: {theme['nav_text']}; + border: none; + border-radius: 0; + padding: 6px {spacing['lg']} {spacing['sm']}; + font-weight: {weights['medium']}; + min-height: 48px; + text-transform: none; +}} + +TabNavigation QPushButton:hover {{ + background-color: {theme['nav_item_hover']}; + color: {theme['text_primary']}; +}} + +TabNavigation QPushButton:checked {{ + background-color: {theme['nav_item_active']}; + color: {theme['nav_text_active']}; + border-bottom: 2px solid {theme['accent']}; +}} + +/* Tab Badge */ +TabNavigation QLabel#badge {{ + background-color: {theme['accent']}; + color: {theme['btn_primary_text']}; + padding: 2px 6px; + border-radius: 10px; + font-size: {sizes['xs']}; + font-weight: {weights['semibold']}; +}} + +/* ==================== ACCOUNTS OVERVIEW ==================== */ +AccountsOverviewView, QWidget#accounts_overview {{ + background-color: {theme['bg_secondary']}; +}} + +/* Filter Sidebar */ +QWidget#filter_sidebar {{ + background-color: {theme['surface_sidebar']}; + border-right: 1px solid {theme['border_default']}; +}} + +/* Filter Buttons */ +QPushButton#filter_button {{ + background-color: transparent; + color: {theme['text_secondary']}; + border: none; + padding: {spacing['sm']} {spacing['md']}; + text-align: left; + font-size: {sizes['base']}; +}} + +QPushButton#filter_button:hover {{ + background-color: {theme['bg_hover']}; + color: {theme['text_primary']}; +}} + +QPushButton#filter_button[selected="true"] {{ + background-color: {theme['bg_selected']}; + color: {theme['text_accent']}; + border-left: 3px solid {theme['accent']}; +}} + +/* Content Area */ +QWidget#accounts_content {{ + background-color: {theme['bg_secondary']}; +}} + +/* ==================== ACCOUNT CARDS ==================== */ +AccountCard, QFrame#account_card {{ + background-color: {theme['surface_card']}; + border: 1px solid {theme['border_subtle']}; + border-radius: {radius['lg']}; + padding: {spacing['md']}; +}} + +AccountCard:hover, QFrame#account_card:hover {{ + border: 1px solid {theme['accent']}; +}} + +/* Account Card Buttons */ +QPushButton#login_button {{ + background-color: {theme['accent']}; + color: {theme['btn_primary_text']}; + border-radius: {radius['md']}; + padding: {spacing['sm']} {spacing['md']}; + font-size: {sizes['sm']}; + min-height: 36px; +}} + +QPushButton#login_button:hover {{ + background-color: {theme['accent_hover']}; +}} + +QPushButton#export_button {{ + background-color: {theme['success']}; + color: #FFFFFF; + border-radius: {radius['md']}; + padding: {spacing['sm']} {spacing['md']}; + font-size: {sizes['sm']}; + min-height: 36px; +}} + +QPushButton#delete_button {{ + background-color: {theme['btn_danger_bg']}; + color: {theme['btn_danger_text']}; + border-radius: {radius['md']}; + padding: {spacing['sm']} {spacing['md']}; + font-size: {sizes['sm']}; + min-height: 36px; +}} + +/* ==================== INPUT FIELDS ==================== */ +QLineEdit, QSpinBox, QTextEdit, QPlainTextEdit {{ + background-color: {theme['input_bg']}; + color: {theme['text_primary']}; + border: 1px solid {theme['input_border']}; + border-radius: {radius['md']}; + padding: {spacing['sm']} {spacing['md']}; + font-size: {sizes['base']}; +}} + +QLineEdit:focus, QSpinBox:focus, QTextEdit:focus, QPlainTextEdit:focus {{ + background-color: {theme['input_focus_bg']}; + border: 2px solid {theme['input_focus_border']}; + outline: none; +}} + +QLineEdit:disabled, QSpinBox:disabled, QTextEdit:disabled {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_tertiary']}; +}} + +/* ==================== COMBOBOX ==================== */ +QComboBox {{ + background-color: {theme['input_bg']}; + color: {theme['text_primary']}; + border: 1px solid {theme['input_border']}; + border-radius: {radius['md']}; + padding: {spacing['sm']} {spacing['md']}; + min-height: 32px; +}} + +QComboBox:hover {{ + border: 1px solid {theme['accent']}; +}} + +QComboBox:focus {{ + border: 2px solid {theme['input_focus_border']}; +}} + +QComboBox::drop-down {{ + border: none; + width: 20px; + background-color: transparent; +}} + +QComboBox::down-arrow {{ + image: none; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid {theme['accent']}; + width: 0; + height: 0; + margin-right: 5px; +}} + +QComboBox QAbstractItemView {{ + background-color: {theme['surface_card']}; + border: 1px solid {theme['border_default']}; + border-radius: {radius['md']}; + selection-background-color: {theme['bg_selected']}; + selection-color: {theme['text_accent']}; + color: {theme['text_primary']}; +}} + +/* ==================== TABLES ==================== */ +QTableWidget {{ + background-color: {theme['surface_card']}; + alternate-background-color: {theme['bg_secondary']}; + gridline-color: {theme['border_subtle']}; + border: none; + color: {theme['text_primary']}; +}} + +QTableWidget::item {{ + padding: {spacing['sm']} {spacing['md']}; + color: {theme['text_primary']}; +}} + +QTableWidget::item:selected {{ + background-color: {theme['bg_selected']}; + color: {theme['text_accent']}; +}} + +QHeaderView::section {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_primary']}; + padding: {spacing['sm']} {spacing['md']}; + border: none; + font-weight: {weights['semibold']}; + text-transform: uppercase; + font-size: {sizes['sm']}; + letter-spacing: 0.5px; +}} + +/* ==================== SCROLL AREAS ==================== */ +QScrollArea {{ + background-color: {theme['bg_primary']}; + border: none; +}} + +QScrollArea > QWidget > QWidget {{ + background-color: {theme['bg_primary']}; +}} + +/* ==================== SCROLLBARS ==================== */ +QScrollBar:vertical {{ + background-color: {theme['scrollbar_bg']}; + width: 8px; + border-radius: 4px; +}} + +QScrollBar::handle:vertical {{ + background-color: {theme['scrollbar_handle']}; + min-height: 20px; + border-radius: 4px; +}} + +QScrollBar::handle:vertical:hover {{ + background-color: {theme['scrollbar_handle_hover']}; +}} + +QScrollBar:horizontal {{ + background-color: {theme['scrollbar_bg']}; + height: 8px; + border-radius: 4px; +}} + +QScrollBar::handle:horizontal {{ + background-color: {theme['scrollbar_handle']}; + min-width: 20px; + border-radius: 4px; +}} + +QScrollBar::handle:horizontal:hover {{ + background-color: {theme['scrollbar_handle_hover']}; +}} + +QScrollBar::add-line, QScrollBar::sub-line {{ + border: none; + background: none; +}} + +/* ==================== PROGRESS BAR ==================== */ +QProgressBar {{ + background-color: {theme['bg_tertiary']}; + border: none; + border-radius: 4px; + height: 8px; + text-align: center; +}} + +QProgressBar::chunk {{ + background-color: {theme['accent']}; + border-radius: 4px; +}} + +/* ==================== TOOLTIPS ==================== */ +QToolTip {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_primary']}; + border: 1px solid {theme['border_default']}; + border-radius: {radius['md']}; + padding: {spacing['sm']} {spacing['sm']}; + font-size: {sizes['sm']}; +}} + +/* ==================== MESSAGE BOXES ==================== */ +QMessageBox {{ + background-color: {theme['bg_primary']}; +}} + +QMessageBox QLabel {{ + color: {theme['text_primary']}; +}} + +QMessageBox QPushButton {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_primary']}; + border: 1px solid {theme['border_default']}; + border-radius: 4px; + padding: 6px 20px; + min-width: 80px; + min-height: 26px; + font-weight: {weights['medium']}; + text-transform: none; +}} + +QMessageBox QPushButton:hover {{ + background-color: {theme['bg_hover']}; + border-color: {theme['accent']}; +}} + +/* Delete Buttons in Message Boxes */ +QMessageBox QPushButton[text="Löschen"], QMessageBox QPushButton[text="Delete"] {{ + background-color: {theme['btn_danger_bg']}; + color: {theme['btn_danger_text']}; + border: none; +}} + +/* ==================== STATUS BAR ==================== */ +QStatusBar {{ + background-color: {theme['bg_secondary']}; + color: {theme['text_secondary']}; + font-size: {sizes['sm']}; + padding: {spacing['sm']}; + border-top: 1px solid {theme['border_subtle']}; +}} + +/* ==================== MENU BAR ==================== */ +QMenuBar {{ + background-color: {theme['bg_primary']}; + color: {theme['text_primary']}; + padding: 4px; +}} + +QMenuBar::item {{ + padding: {spacing['sm']} {spacing['md']}; + background-color: transparent; + color: {theme['text_primary']}; +}} + +QMenuBar::item:selected {{ + background-color: {theme['bg_hover']}; + color: {theme['text_accent']}; +}} + +/* Logo button in menu bar */ +QPushButton#logo_button {{ + background-color: transparent; + border: none; + padding: 5px; +}} + +QPushButton#logo_button:hover {{ + background-color: {theme['bg_hover']}; + border-radius: {radius['sm']}; +}} + +QWidget#menubar_right_container {{ + background: transparent; +}} + +/* ==================== CHECKBOXES & RADIO BUTTONS ==================== */ +QCheckBox, QRadioButton {{ + spacing: 8px; + color: {theme['text_primary']}; +}} + +QCheckBox::indicator, QRadioButton::indicator {{ + width: 20px; + height: 20px; + border: 2px solid {theme['border_strong']}; + background-color: {theme['input_bg']}; +}} + +QCheckBox::indicator {{ + border-radius: 4px; +}} + +QRadioButton::indicator {{ + border-radius: 10px; +}} + +QCheckBox::indicator:checked, QRadioButton::indicator:checked {{ + background-color: {theme['accent']}; + border-color: {theme['accent']}; +}} + +/* ==================== GROUP BOXES ==================== */ +QGroupBox {{ + background-color: {theme['surface_card']}; + border: 1px solid {theme['border_default']}; + border-radius: {radius['xl']}; + padding: {spacing['xl']}; + margin-top: {spacing['md']}; + font-family: {fonts['primary']}; + font-weight: {weights['semibold']}; + color: {theme['text_primary']}; +}} + +QGroupBox::title {{ + color: {theme['text_accent']}; + font-size: {sizes['lg']}; + subcontrol-origin: margin; + left: {spacing['md']}; + padding: 0 {spacing['sm']}; + background-color: {theme['surface_card']}; +}} + +/* ==================== TABS ==================== */ +QTabWidget::pane {{ + background-color: {theme['bg_secondary']}; + border: none; +}} + +QTabBar::tab {{ + background-color: transparent; + color: {theme['text_secondary']}; + padding: {spacing['sm']} {spacing['lg']}; + font-size: {sizes['base']}; + font-weight: {weights['medium']}; + border: none; +}} + +QTabBar::tab:hover {{ + background-color: {theme['bg_hover']}; + color: {theme['text_primary']}; +}} + +QTabBar::tab:selected {{ + color: {theme['text_accent']}; + background-color: transparent; + border-bottom: 2px solid {theme['accent']}; + font-weight: {weights['semibold']}; +}} + +/* ==================== DIALOGS ==================== */ +QDialog {{ + background-color: {theme['surface_modal']}; + color: {theme['text_primary']}; +}} + +/* ==================== LOG OUTPUT ==================== */ +QTextEdit#log_output {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_primary']}; + font-family: {fonts['monospace']}; + font-size: {sizes['sm']}; + border: none; + border-radius: {radius['md']}; + padding: {spacing['md']}; +}} + +/* ==================== BADGES ==================== */ +QLabel#badge {{ + background-color: {theme['bg_tertiary']}; + color: {theme['text_primary']}; + padding: 4px 12px; + border-radius: 12px; + font-size: {sizes['xs']}; + font-weight: {weights['semibold']}; + text-transform: uppercase; + letter-spacing: 0.5px; +}} + +QLabel#badge_primary {{ + background-color: {theme['accent']}; + color: {theme['btn_primary_text']}; +}} + +QLabel#badge_success {{ + background-color: {theme['success']}; + color: #FFFFFF; +}} + +QLabel#badge_warning {{ + background-color: {theme['warning']}; + color: #000000; +}} + +QLabel#badge_error {{ + background-color: {theme['error']}; + color: #FFFFFF; +}} + +/* ==================== ACCOUNTS OVERVIEW SPECIFIC ==================== */ +QLabel#platform_header {{ + color: {theme['text_primary']}; + padding: 8px 0; + font-family: {fonts['primary']}; + font-size: {sizes['lg']}; + font-weight: {weights['semibold']}; +}} + +QPushButton#delete_confirm_button {{ + background-color: {theme['error']}; + color: #FFFFFF; + border: 1px solid {theme['error']}; + border-radius: {radius['sm']}; + padding: 6px 20px; + min-width: 80px; + min-height: 26px; + font-weight: {weights['medium']}; +}} + +QPushButton#delete_confirm_button:hover {{ + background-color: {theme['error_dark']}; + border-color: {theme['error_dark']}; +}} + +QPushButton#delete_confirm_button:pressed {{ + background-color: {theme['error_dark']}; +}} + +/* ==================== ACCOUNT CARD ==================== */ +QFrame#accountCard {{ + background-color: {theme['surface_card']}; + border: 2px solid {theme['border_default']}; + border-radius: {radius['md']}; + padding: 16px; +}} + +QFrame#accountCard:hover {{ + border: 2px solid {theme['accent']}; +}} + +QFrame#accountCard[status="active"] {{ + background-color: {theme['success_bg']}; + border: 2px solid {theme['success']}; +}} + +QFrame#accountCard[status="active"]:hover {{ + border: 2px solid {theme['success']}; +}} + +QFrame#accountCard[status="inactive"] {{ + background-color: {theme['error_bg']}; + border: 2px solid {theme['error']}; +}} + +QFrame#accountCard[status="inactive"]:hover {{ + border: 2px solid {theme['error_dark']}; +}} + +QLabel#account_username {{ + color: {theme['text_primary']}; + font-family: {fonts['primary']}; + font-size: 16px; + font-weight: {weights['semibold']}; +}} + +QLabel#account_detail_text {{ + color: {theme['text_secondary']}; + font-size: {sizes['sm']}; + font-family: {fonts['primary']}; +}} + +QLabel#account_date_text {{ + color: {theme['text_tertiary']}; + font-size: {sizes['xs']}; + font-family: {fonts['primary']}; +}} + +QPushButton#account_icon_btn {{ + background: transparent; + border: none; + padding: 2px; + min-width: 20px; + max-width: 20px; + min-height: 20px; + max-height: 20px; +}} + +QPushButton#account_icon_btn:hover {{ + background-color: {theme['bg_hover']}; + border-radius: {radius['sm']}; +}} + +QPushButton#account_login_btn {{ + background-color: {theme['accent']}; + color: {theme['btn_primary_text']}; + border: none; + border-radius: {radius['sm']}; + padding: 6px 16px; + font-size: {sizes['xs']}; + font-weight: {weights['medium']}; + font-family: {fonts['primary']}; + min-width: 60px; +}} + +QPushButton#account_login_btn:hover {{ + background-color: {theme['accent_hover']}; +}} + +QPushButton#account_login_btn:pressed {{ + background-color: {theme['accent_pressed']}; +}} + +QPushButton#account_export_btn {{ + background-color: {theme['success']}; + color: #FFFFFF; + border: none; + border-radius: {radius['sm']}; + padding: 4px 12px; + font-size: {sizes['xs']}; + font-weight: {weights['medium']}; + font-family: {fonts['primary']}; + min-width: 120px; + min-height: 36px; + text-align: center; +}} + +QPushButton#account_export_btn:hover {{ + background-color: {theme['success']}; + opacity: 0.9; +}} + +QPushButton#account_export_btn:pressed {{ + background-color: {theme['success']}; + opacity: 0.8; +}} + +QPushButton#account_delete_btn {{ + background-color: {theme['error']}; + color: #FFFFFF; + border: none; + border-radius: {radius['sm']}; + padding: 8px 16px; + font-size: {sizes['xs']}; + font-weight: {weights['medium']}; + font-family: {fonts['primary']}; + min-width: 90px; +}} + +QPushButton#account_delete_btn:hover {{ + background-color: {theme['error_dark']}; +}} + +QPushButton#account_delete_btn:pressed {{ + background-color: {theme['error_dark']}; + opacity: 0.9; +}} +""" + + return qss \ No newline at end of file diff --git a/themes/theme_config.py b/themes/theme_config.py new file mode 100644 index 0000000..6771040 --- /dev/null +++ b/themes/theme_config.py @@ -0,0 +1,247 @@ +""" +Theme Configuration - Single Source of Truth for all UI Colors and Styles +Based on IntelSight Corporate Design System +""" + +class ThemeConfig: + """ + Centralized theme configuration. + All colors, fonts, and styling values should be defined here. + NO hardcoded colors in widgets! + """ + + THEMES = { + 'light': { + # ========== BACKGROUNDS ========== + 'bg_primary': '#FFFFFF', # Main window background + 'bg_secondary': '#F8FAFC', # Content areas, cards + 'bg_tertiary': '#F5F7FF', # Input fields, subtle backgrounds + 'bg_hover': '#F0F4FF', # Hover states + 'bg_selected': '#E8EBFF', # Selected items + + # ========== SURFACES ========== + 'surface_card': '#FFFFFF', # Card backgrounds + 'surface_modal': '#FFFFFF', # Modal backgrounds + 'surface_sidebar': '#FAFBFC', # Sidebar background + + # ========== TEXT ========== + 'text_primary': '#1E1E1E', # Main text + 'text_secondary': '#666666', # Secondary text + 'text_tertiary': '#999999', # Disabled/hint text + 'text_accent': '#232D53', # Headers, important text + + # ========== BRAND COLORS ========== + 'accent': '#0099CC', # Primary accent (buttons, links) + 'accent_hover': '#0078A3', # Accent hover state + 'accent_pressed': '#005C7A', # Accent pressed state + + # ========== PLATFORM BUTTONS ========== + 'platform_bg': '#F5F7FF', # Platform button background + 'platform_hover': '#E8EBFF', # Platform button hover + 'platform_pressed': '#DCE2FF', # Platform button pressed + 'platform_border': 'transparent', # Platform button border + 'platform_border_hover': '#0099CC', # Platform button border hover + 'platform_label': '#232D53', # Platform name text + + # ========== NAVIGATION ========== + 'nav_bg': '#FFFFFF', # Navigation background + 'nav_border': '#E2E8F0', # Navigation borders + 'nav_item': 'transparent', # Nav item background + 'nav_item_hover': '#F7FAFC', # Nav item hover + 'nav_item_active': '#E6F2FF', # Nav item active + 'nav_text': '#2D3748', # Nav text + 'nav_text_active': '#1E40AF', # Nav active text + + # ========== INPUTS ========== + 'input_bg': '#F5F7FF', # Input background + 'input_border': '#E0E6FF', # Input border + 'input_focus_bg': '#FFFFFF', # Input focused background + 'input_focus_border': '#0099CC', # Input focused border + + # ========== BUTTONS ========== + 'btn_primary_bg': '#0099CC', # Primary button + 'btn_primary_text': '#FFFFFF', # Primary button text + 'btn_secondary_bg': 'transparent', # Secondary button + 'btn_secondary_text': '#232D53', # Secondary button text + 'btn_secondary_border': '#232D53', # Secondary button border + 'btn_danger_bg': '#F44336', # Danger button + 'btn_danger_text': '#FFFFFF', # Danger button text + + # ========== STATUS COLORS ========== + 'success': '#4CAF50', # Success states + 'success_bg': '#E8F5E9', # Success background + 'warning': '#FFC107', # Warning states + 'warning_bg': '#FFF8E1', # Warning background + 'error': '#F44336', # Error states + 'error_dark': '#D32F2F', # Error dark for hover + 'error_bg': '#FFEBEE', # Error background + 'info': '#2196F3', # Info states + 'info_bg': '#E3F2FD', # Info background + + # ========== BORDERS ========== + 'border_default': '#E0E6FF', # Default borders + 'border_subtle': '#F0F0F0', # Subtle borders + 'border_strong': '#CCCCCC', # Strong borders + + # ========== SHADOWS ========== + 'shadow_sm': '0 1px 3px rgba(0,0,0,0.12)', + 'shadow_md': '0 4px 6px rgba(0,0,0,0.15)', + 'shadow_lg': '0 10px 20px rgba(0,0,0,0.15)', + + # ========== SCROLLBAR ========== + 'scrollbar_bg': '#F5F7FF', + 'scrollbar_handle': '#0099CC', + 'scrollbar_handle_hover': '#0078A3', + + # ========== LOGO ========== + 'logo_path': 'intelsight-logo.svg', + }, + + 'dark': { + # ========== BACKGROUNDS ========== + 'bg_primary': '#000000', # Main window background + 'bg_secondary': '#0A0A0A', # Content areas + 'bg_tertiary': '#1A1F3A', # Cards, elevated surfaces + 'bg_hover': '#232D53', # Hover states + 'bg_selected': '#2A3560', # Selected items + + # ========== SURFACES ========== + 'surface_card': '#1A1F3A', # Card backgrounds + 'surface_modal': '#0A0A0A', # Modal backgrounds + 'surface_sidebar': '#0A0A0A', # Sidebar background + + # ========== TEXT ========== + 'text_primary': '#FFFFFF', # Main text + 'text_secondary': 'rgba(255,255,255,0.7)', # Secondary text + 'text_tertiary': 'rgba(255,255,255,0.5)', # Disabled/hint text + 'text_accent': '#00D4FF', # Headers, important text + + # ========== BRAND COLORS ========== + 'accent': '#00D4FF', # Primary accent (buttons, links) + 'accent_hover': '#00B8E6', # Accent hover state + 'accent_pressed': '#0099CC', # Accent pressed state + + # ========== PLATFORM BUTTONS ========== + 'platform_bg': '#1A1F3A', # Platform button background + 'platform_hover': '#232D53', # Platform button hover + 'platform_pressed': '#2A3560', # Platform button pressed + 'platform_border': 'transparent', # Platform button border + 'platform_border_hover': '#00D4FF', # Platform button border hover + 'platform_label': '#FFFFFF', # Platform name text + + # ========== NAVIGATION ========== + 'nav_bg': '#0A0A0A', # Navigation background + 'nav_border': 'rgba(255,255,255,0.1)', # Navigation borders + 'nav_item': 'transparent', # Nav item background + 'nav_item_hover': 'rgba(35,45,83,0.3)', # Nav item hover + 'nav_item_active': '#232D53', # Nav item active + 'nav_text': 'rgba(255,255,255,0.6)', # Nav text + 'nav_text_active': '#00D4FF', # Nav active text + + # ========== INPUTS ========== + 'input_bg': '#232D53', # Input background + 'input_border': 'transparent', # Input border + 'input_focus_bg': '#2A3560', # Input focused background + 'input_focus_border': '#00D4FF', # Input focused border + + # ========== BUTTONS ========== + 'btn_primary_bg': '#00D4FF', # Primary button + 'btn_primary_text': '#000000', # Primary button text + 'btn_secondary_bg': 'transparent', # Secondary button + 'btn_secondary_text': '#FFFFFF', # Secondary button text + 'btn_secondary_border': '#232D53', # Secondary button border + 'btn_danger_bg': '#FF4444', # Danger button + 'btn_danger_text': '#FFFFFF', # Danger button text + + # ========== STATUS COLORS ========== + 'success': '#4CAF50', # Success states + 'success_bg': 'rgba(76,175,80,0.2)', # Success background + 'warning': '#FFC107', # Warning states + 'warning_bg': 'rgba(255,193,7,0.2)', # Warning background + 'error': '#FF4444', # Error states + 'error_dark': '#CC0000', # Error dark for hover + 'error_bg': 'rgba(255,68,68,0.2)', # Error background + 'info': '#2196F3', # Info states + 'info_bg': 'rgba(33,150,243,0.2)', # Info background + + # ========== BORDERS ========== + 'border_default': 'rgba(255,255,255,0.1)', # Default borders + 'border_subtle': 'rgba(255,255,255,0.05)', # Subtle borders + 'border_strong': 'rgba(255,255,255,0.2)', # Strong borders + + # ========== SHADOWS ========== + 'shadow_sm': '0 1px 3px rgba(0,0,0,0.3)', + 'shadow_md': '0 4px 6px rgba(0,0,0,0.4)', + 'shadow_lg': '0 10px 20px rgba(0,0,0,0.5)', + + # ========== SCROLLBAR ========== + 'scrollbar_bg': '#1A1F3A', + 'scrollbar_handle': '#00D4FF', + 'scrollbar_handle_hover': '#00B8E6', + + # ========== LOGO ========== + 'logo_path': 'intelsight-dark.svg', + } + } + + @classmethod + def get_theme(cls, theme_name: str) -> dict: + """Get theme configuration by name""" + return cls.THEMES.get(theme_name, cls.THEMES['light']) + + @classmethod + def get_color(cls, theme_name: str, color_key: str) -> str: + """Get specific color from theme""" + theme = cls.get_theme(theme_name) + return theme.get(color_key, '#000000') + + # ========== TYPOGRAPHY ========== + FONTS = { + 'primary': "'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", + 'secondary': "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif", + 'monospace': "'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace", + } + + FONT_SIZES = { + 'xs': '11px', + 'sm': '12px', + 'base': '14px', + 'lg': '16px', + 'xl': '18px', + '2xl': '24px', + '3xl': '32px', + } + + FONT_WEIGHTS = { + 'regular': '400', + 'medium': '500', + 'semibold': '600', + 'bold': '700', + } + + # ========== SPACING ========== + SPACING = { + 'xs': '4px', + 'sm': '8px', + 'md': '16px', + 'lg': '24px', + 'xl': '32px', + '2xl': '40px', + } + + # ========== BORDER RADIUS ========== + RADIUS = { + 'sm': '4px', + 'md': '8px', + 'lg': '12px', + 'xl': '16px', + 'round': '24px', + 'full': '50%', + } + + # ========== TRANSITIONS ========== + TRANSITIONS = { + 'fast': '0.2s ease', + 'normal': '0.3s ease', + 'slow': '0.5s ease', + } \ No newline at end of file diff --git a/utils/theme_manager.py b/utils/theme_manager.py index 38cbe74..90581a9 100644 --- a/utils/theme_manager.py +++ b/utils/theme_manager.py @@ -1,5 +1,7 @@ """ -Theme Manager - Verwaltet das Erscheinungsbild der Anwendung (nur Light Mode) +Theme Manager - Verwaltet das Erscheinungsbild der Anwendung (Light & Dark Mode) +Enhanced with Dark Mode support based on Corporate Design Guidelines +Now using centralized theme configuration """ import os @@ -8,17 +10,26 @@ import logging from typing import Dict, Any, Optional from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QPalette, QColor -from PyQt5.QtCore import Qt, QSettings +from PyQt5.QtCore import Qt, QSettings, pyqtSignal, QObject + +# Import new theme system +from themes.theme_config import ThemeConfig +from themes.qss_generator import QSSGenerator logger = logging.getLogger("theme_manager") -class ThemeManager: +class ThemeManager(QObject): """ Verwaltet das Erscheinungsbild der Anwendung. + Supports Light and Dark themes with smooth transitions. """ + # Signal emitted when theme changes + theme_changed = pyqtSignal(str) + # Themennamen LIGHT_THEME = "light" + DARK_THEME = "dark" def __init__(self, app: QApplication): """ @@ -27,9 +38,12 @@ class ThemeManager: Args: app: Die QApplication-Instanz """ + super().__init__() self.app = app self.settings = QSettings("Chimaira", "SocialMediaAccountGenerator") - self.current_theme = self.LIGHT_THEME + + # Load saved theme preference or default to light + self.current_theme = self.settings.value("theme", self.LIGHT_THEME) # Basisverzeichnis ermitteln self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -38,86 +52,133 @@ class ThemeManager: os.makedirs(os.path.join(self.base_dir, "resources", "themes"), exist_ok=True) os.makedirs(os.path.join(self.base_dir, "resources", "icons"), exist_ok=True) - # Lade QSS-Dateien für Themes + # Generate QSS from theme configuration + self.qss_generator = QSSGenerator() self.theme_stylesheets = { - self.LIGHT_THEME: self._load_stylesheet("light.qss") + self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME), + self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME) } - # Wende das Light Theme an - self.apply_theme(self.LIGHT_THEME) + # Apply saved theme + self.apply_theme(self.current_theme) - logger.info(f"ThemeManager initialisiert mit Theme: {self.current_theme}") + logger.info(f"ThemeManager initialized with theme: {self.current_theme}") - def _load_stylesheet(self, filename: str) -> str: - """Lädt ein QSS-Stylesheet aus einer Datei.""" - try: - stylesheet_path = os.path.join(self.base_dir, "resources", "themes", filename) - if os.path.exists(stylesheet_path): - with open(stylesheet_path, 'r', encoding='utf-8') as f: - return f.read() - else: - logger.warning(f"Stylesheet-Datei nicht gefunden: {stylesheet_path}") - # Erzeuge eine leere Stylesheet-Datei, wenn sie nicht existiert - with open(stylesheet_path, 'w', encoding='utf-8') as f: - f.write("/* Auto-generated empty stylesheet */\n") - return "" - except Exception as e: - logger.error(f"Fehler beim Laden des Stylesheets {filename}: {e}") - return "" + def regenerate_stylesheets(self): + """Regenerate stylesheets from theme configuration""" + self.theme_stylesheets = { + self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME), + self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME) + } + + def get_color(self, color_key: str) -> str: + """Get a color from the current theme""" + return ThemeConfig.get_color(self.current_theme, color_key) def apply_theme(self, theme_name: str) -> bool: """ - Wendet das Light Theme auf die Anwendung an. + Wendet das angegebene Theme auf die Anwendung an. Args: - theme_name: Wird ignoriert, immer Light Theme verwendet + theme_name: Name des Themes ("light" oder "dark") Returns: bool: True, wenn das Theme erfolgreich angewendet wurde, sonst False """ try: - # Palette für das Light Theme erstellen + # Validate theme name + if theme_name not in [self.LIGHT_THEME, self.DARK_THEME]: + logger.warning(f"Unknown theme '{theme_name}', falling back to light theme") + theme_name = self.LIGHT_THEME + + # Create palette based on theme palette = QPalette() - # Light Theme Palette - palette.setColor(QPalette.Window, QColor(240, 240, 240)) - palette.setColor(QPalette.WindowText, Qt.black) - palette.setColor(QPalette.Base, Qt.white) - palette.setColor(QPalette.AlternateBase, QColor(245, 245, 245)) - palette.setColor(QPalette.ToolTipBase, Qt.white) - palette.setColor(QPalette.ToolTipText, Qt.black) - palette.setColor(QPalette.Text, Qt.black) - palette.setColor(QPalette.Button, QColor(240, 240, 240)) - palette.setColor(QPalette.ButtonText, Qt.black) - palette.setColor(QPalette.BrightText, Qt.red) - palette.setColor(QPalette.Link, QColor(0, 0, 255)) - palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) - palette.setColor(QPalette.HighlightedText, Qt.white) + # Get colors from theme configuration + theme = ThemeConfig.get_theme(theme_name) - # Palette auf die Anwendung anwenden + if theme_name == self.DARK_THEME: + # Dark Theme Palette from configuration + palette.setColor(QPalette.Window, QColor(theme['bg_primary'])) + palette.setColor(QPalette.WindowText, QColor(theme['text_primary'])) + palette.setColor(QPalette.Base, QColor(theme['bg_tertiary'])) + palette.setColor(QPalette.AlternateBase, QColor(theme['bg_secondary'])) + palette.setColor(QPalette.ToolTipBase, QColor(theme['bg_tertiary'])) + palette.setColor(QPalette.ToolTipText, QColor(theme['text_primary'])) + palette.setColor(QPalette.Text, QColor(theme['text_primary'])) + palette.setColor(QPalette.Button, QColor(theme['bg_tertiary'])) + palette.setColor(QPalette.ButtonText, QColor(theme['text_primary'])) + palette.setColor(QPalette.BrightText, QColor(theme['accent'])) + palette.setColor(QPalette.Link, QColor(theme['accent'])) + palette.setColor(QPalette.Highlight, QColor(theme['accent'])) + palette.setColor(QPalette.HighlightedText, QColor(theme['btn_primary_text'])) + else: + # Light Theme Palette from configuration + palette.setColor(QPalette.Window, QColor(theme['bg_primary'])) + palette.setColor(QPalette.WindowText, QColor(theme['text_primary'])) + palette.setColor(QPalette.Base, QColor(theme['bg_primary'])) + palette.setColor(QPalette.AlternateBase, QColor(theme['bg_secondary'])) + palette.setColor(QPalette.ToolTipBase, QColor(theme['bg_primary'])) + palette.setColor(QPalette.ToolTipText, QColor(theme['text_primary'])) + palette.setColor(QPalette.Text, QColor(theme['text_primary'])) + palette.setColor(QPalette.Button, QColor(theme['bg_secondary'])) + palette.setColor(QPalette.ButtonText, QColor(theme['text_primary'])) + palette.setColor(QPalette.BrightText, QColor(theme['error'])) + palette.setColor(QPalette.Link, QColor(theme['accent'])) + palette.setColor(QPalette.Highlight, QColor(theme['accent'])) + palette.setColor(QPalette.HighlightedText, QColor(theme['btn_primary_text'])) + + # Apply palette to application self.app.setPalette(palette) - # Stylesheet anwenden - self.app.setStyleSheet(self.theme_stylesheets.get(self.LIGHT_THEME, "")) + # Apply stylesheet + stylesheet = self.theme_stylesheets.get(theme_name, "") + self.app.setStyleSheet(stylesheet) - # Aktuelles Theme speichern - self.current_theme = self.LIGHT_THEME - self.settings.setValue("theme", self.LIGHT_THEME) + # Save current theme + self.current_theme = theme_name + self.settings.setValue("theme", theme_name) - logger.info(f"Theme '{self.LIGHT_THEME}' erfolgreich angewendet") + # Update logo if main window is available + self._update_logo(theme_name) + + # Emit signal for theme change + self.theme_changed.emit(theme_name) + + logger.info(f"Theme '{theme_name}' successfully applied") return True except Exception as e: - logger.error(f"Fehler beim Anwenden des Themes '{self.LIGHT_THEME}': {e}") + logger.error(f"Error applying theme '{theme_name}': {e}") return False + def toggle_theme(self) -> str: + """ + Toggles between Light and Dark theme. + + Returns: + str: The name of the newly applied theme + """ + new_theme = self.DARK_THEME if self.current_theme == self.LIGHT_THEME else self.LIGHT_THEME + self.apply_theme(new_theme) + return new_theme + + def is_dark_mode(self) -> bool: + """ + Check if dark mode is currently active. + + Returns: + bool: True if dark mode is active, False otherwise + """ + return self.current_theme == self.DARK_THEME + def get_current_theme(self) -> str: """Gibt den Namen des aktuellen Themes zurück.""" - return self.LIGHT_THEME + return self.current_theme def get_icon_path(self, icon_name: str) -> str: """ - Gibt den Pfad zum Icon zurück. + Gibt den Pfad zum Icon zurück (theme-aware). Args: icon_name: Name des Icons (ohne Dateierweiterung) @@ -126,8 +187,38 @@ class ThemeManager: str: Pfad zum Icon """ # Social Media Icons bleiben unverändert (immer farbig) - if icon_name in ["instagram", "facebook", "twitter", "tiktok", "vk"]: + if icon_name in ["instagram", "facebook", "twitter", "tiktok", "vk", "gmail", "ok"]: return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg") - # Für andere Icons, die möglicherweise Theme-spezifisch sind - return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg") \ No newline at end of file + # Logo is theme-specific + if icon_name == "intelsight-logo": + theme = ThemeConfig.get_theme(self.current_theme) + logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '') + return os.path.join(self.base_dir, "resources", "icons", f"{logo_name}.svg") + + # For other icons + return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg") + + def _update_logo(self, theme_name: str): + """ + Updates the logo based on the current theme. + + Args: + theme_name: Name of the theme ("light" or "dark") + """ + # Skip this method - logo will be updated directly in MainWindow._on_theme_toggled + # This avoids circular import issues + logger.debug(f"_update_logo called for theme: {theme_name} (delegating to MainWindow)") + + def get_color(self, color_key: str) -> str: + """ + Get a color from the current theme. + + Args: + color_key: Key of the color in theme configuration + + Returns: + Color value as hex string + """ + theme = ThemeConfig.get_theme(self.current_theme) + return theme.get(color_key, '') \ No newline at end of file diff --git a/views/about_dialog.py b/views/about_dialog.py index 9fe1091..99660f5 100644 --- a/views/about_dialog.py +++ b/views/about_dialog.py @@ -14,6 +14,12 @@ class AboutDialog(QDialog): # Remove the standard "?" help button that appears on some platforms self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.language_manager = language_manager + self.theme_manager = None + + # Try to get theme manager from parent (MainWindow) + if parent and hasattr(parent, 'theme_manager'): + self.theme_manager = parent.theme_manager + self._setup_ui() if self.language_manager: self.language_manager.language_changed.connect(self.update_texts) @@ -21,49 +27,54 @@ class AboutDialog(QDialog): def _setup_ui(self): self.setWindowTitle("About") - # Dialog-Größe festlegen für bessere Zentrierung - self.setMinimumWidth(500) - self.setMinimumHeight(400) + # Dialog-Größe festlegen + self.setFixedSize(550, 450) # Fixed size for consistent appearance layout = QVBoxLayout(self) - layout.setContentsMargins(30, 30, 30, 30) - layout.setSpacing(20) - layout.setAlignment(Qt.AlignCenter) # Layout auch zentrieren + layout.setContentsMargins(20, 20, 40, 40) # Less margin on top/left for logo + layout.setSpacing(25) - # Add logo + # Add logo in top-left corner logo_label = QLabel() - logo_label.setAlignment(Qt.AlignCenter) + logo_label.setAlignment(Qt.AlignLeft) # Align left instead of center - # Get the logo path - current_dir = os.path.dirname(os.path.abspath(__file__)) - parent_dir = os.path.dirname(current_dir) - logo_path = os.path.join(parent_dir, "resources", "icons", "intelsight-logo.svg") + # Get the theme-aware logo path + if self.theme_manager: + # Use theme manager to get correct logo based on current theme + logo_path = self.theme_manager.get_icon_path("intelsight-logo") + else: + # Fallback to light logo if no theme manager + current_dir = os.path.dirname(os.path.abspath(__file__)) + parent_dir = os.path.dirname(current_dir) + logo_path = os.path.join(parent_dir, "resources", "icons", "intelsight-logo.svg") if os.path.exists(logo_path): - # Load logo and display it at a larger size + # Load logo and display it at a smaller size for corner placement logo_pixmap = QPixmap(logo_path) - # Scale the logo to a reasonable size while maintaining aspect ratio + # Scale the logo smaller for top-left corner scaled_pixmap = logo_pixmap.scaled( - 300, 120, # Etwas kleiner für bessere Proportionen + 200, 60, # Smaller size for corner placement Qt.KeepAspectRatio, Qt.SmoothTransformation ) logo_label.setPixmap(scaled_pixmap) - # Feste Größe für das Label setzen, um Zentrierung zu gewährleisten logo_label.setFixedSize(scaled_pixmap.size()) else: # Fallback if logo not found logo_label.setText("IntelSight") - logo_label.setStyleSheet("font-size: 24px; font-weight: bold;") + logo_label.setStyleSheet("font-size: 18px; font-weight: bold;") - # Logo mit Alignment hinzufügen - layout.addWidget(logo_label, 0, Qt.AlignCenter) + # Logo in top-left corner + layout.addWidget(logo_label, 0, Qt.AlignLeft | Qt.AlignTop) + # Add some space after logo + layout.addSpacing(20) + self.info_label = QLabel() self.info_label.setAlignment(Qt.AlignCenter) self.info_label.setWordWrap(True) self.info_label.setMaximumWidth(450) # Maximale Breite für bessere Lesbarkeit - layout.addWidget(self.info_label, 0, Qt.AlignCenter) + layout.addWidget(self.info_label, 1, Qt.AlignCenter) # Use stretch factor 1 # Spacer für bessere vertikale Verteilung layout.addStretch() diff --git a/views/base/__init__.py b/views/base/__init__.py new file mode 100644 index 0000000..9bcc352 --- /dev/null +++ b/views/base/__init__.py @@ -0,0 +1,7 @@ +""" +Base View Components - Theme-aware base classes for all UI elements +""" + +from views.base.theme_aware_widget import ThemeAwareWidget + +__all__ = ['ThemeAwareWidget'] \ No newline at end of file diff --git a/views/base/theme_aware_widget.py b/views/base/theme_aware_widget.py new file mode 100644 index 0000000..7e2e7e6 --- /dev/null +++ b/views/base/theme_aware_widget.py @@ -0,0 +1,99 @@ +""" +Theme-Aware Base Widget - Base class for all custom widgets +Provides automatic theme updates and color access +""" + +from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import pyqtSlot +import logging + +logger = logging.getLogger(__name__) + + +class ThemeAwareWidget(QWidget): + """ + Base class for all custom widgets that need theme support. + Automatically connects to theme changes and provides helper methods. + """ + + def __init__(self, parent=None): + """ + Initialize theme-aware widget. + + Args: + parent: Parent widget + """ + super().__init__(parent) + self._theme_manager = None + self._setup_theme_connection() + + def _setup_theme_connection(self): + """Setup connection to theme manager for automatic updates.""" + try: + # Try to find theme manager in main window + main_window = self.window() + if main_window and hasattr(main_window, 'theme_manager'): + self._theme_manager = main_window.theme_manager + # Connect to theme change signal + self._theme_manager.theme_changed.connect(self._on_theme_changed) + logger.debug(f"{self.__class__.__name__} connected to theme manager") + except Exception as e: + logger.warning(f"Could not connect to theme manager: {e}") + + @pyqtSlot(str) + def _on_theme_changed(self, theme_name: str): + """ + Called when theme changes. + Override in subclasses to handle theme-specific updates. + + Args: + theme_name: Name of the new theme ('light' or 'dark') + """ + # Subclasses should override this method + pass + + def get_theme_color(self, color_key: str) -> str: + """ + Get a color from the current theme. + + Args: + color_key: Key of the color in theme configuration + + Returns: + Color value as hex string, or empty string if not found + """ + if self._theme_manager: + return self._theme_manager.get_color(color_key) + return '' + + def get_current_theme(self) -> str: + """ + Get the current theme name. + + Returns: + Current theme name ('light' or 'dark'), or 'light' as default + """ + if self._theme_manager: + return self._theme_manager.get_current_theme() + return 'light' + + def is_dark_mode(self) -> bool: + """ + Check if dark mode is currently active. + + Returns: + True if dark mode is active, False otherwise + """ + if self._theme_manager: + return self._theme_manager.is_dark_mode() + return False + + def showEvent(self, event): + """ + Override showEvent to ensure theme connection is established. + Some widgets might not have access to main window during __init__. + """ + super().showEvent(event) + # Try to setup theme connection again if not established + if not self._theme_manager: + self._setup_theme_connection() \ No newline at end of file diff --git a/views/components/accounts_overview_view.py b/views/components/accounts_overview_view.py index 0ada009..4cd8429 100644 --- a/views/components/accounts_overview_view.py +++ b/views/components/accounts_overview_view.py @@ -33,12 +33,7 @@ class SidebarFilter(QWidget): def init_ui(self): """Initialisiert die UI nach Styleguide""" self.setMaximumWidth(260) # Styleguide: Sidebar-Breite - self.setStyleSheet(""" - QWidget { - background-color: #FFFFFF; - border-right: 1px solid #E2E8F0; - } - """) + self.setObjectName("filter_sidebar") # For QSS targeting layout = QVBoxLayout(self) layout.setContentsMargins(20, 20, 20, 20) @@ -70,29 +65,7 @@ class SidebarFilter(QWidget): btn = QPushButton(f"{name} (0)") btn.setObjectName(key) btn.setCursor(Qt.PointingHandCursor) - btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - border: none; - text-align: left; - padding: 12px 16px; - border-radius: 8px; - font-size: 14px; - color: #4A5568; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - } - QPushButton:hover { - background-color: #F7FAFC; - color: #2D3748; - } - QPushButton[selected="true"] { - background-color: #E6F2FF; - color: #1E40AF; - font-weight: 500; - border-left: 3px solid #3182CE; - padding-left: 13px; - } - """) + btn.setObjectName("filter_button") # For QSS targeting btn.clicked.connect(lambda: self._on_filter_clicked(key)) # Erste Option als aktiv setzen @@ -155,11 +128,7 @@ class AccountsOverviewView(QWidget): def init_ui(self): """Initialisiert die UI nach Styleguide""" - self.setStyleSheet(""" - QWidget { - background-color: #F8FAFC; - } - """) + self.setObjectName("accounts_overview") # For QSS targeting # Hauptlayout main_layout = QHBoxLayout(self) @@ -173,7 +142,7 @@ class AccountsOverviewView(QWidget): # Content Area content_widget = QWidget() - content_widget.setStyleSheet("background-color: #F8FAFC;") + content_widget.setObjectName("accounts_content") # For QSS targeting content_layout = QVBoxLayout(content_widget) content_layout.setContentsMargins(40, 30, 40, 30) content_layout.setSpacing(20) @@ -185,10 +154,7 @@ class AccountsOverviewView(QWidget): title_font = QFont("Poppins", 24) title_font.setBold(True) self.title.setFont(title_font) - self.title.setStyleSheet(""" - color: #1A365D; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - """) + self.title.setObjectName("section_title") # For QSS targeting header_layout.addWidget(self.title) header_layout.addStretch() @@ -198,34 +164,11 @@ class AccountsOverviewView(QWidget): # Scroll Area für Accounts self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) - self.scroll.setStyleSheet(""" - QScrollArea { - border: none; - background-color: transparent; - } - QScrollBar:vertical { - background-color: #F1F5F9; - width: 8px; - border-radius: 4px; - } - QScrollBar::handle:vertical { - background-color: #CBD5E0; - min-height: 20px; - border-radius: 4px; - } - QScrollBar::handle:vertical:hover { - background-color: #A0AEC0; - } - QScrollBar::add-line:vertical, - QScrollBar::sub-line:vertical { - border: none; - background: none; - } - """) + # No inline styles - handled by QSS # Grid Container self.container = QWidget() - self.container.setStyleSheet("background-color: transparent;") + self.container.setObjectName("grid_container") # For QSS targeting self.grid_layout = QGridLayout(self.container) self.grid_layout.setSpacing(24) # Styleguide Grid-Gap self.grid_layout.setContentsMargins(0, 0, 0, 0) @@ -300,11 +243,7 @@ class AccountsOverviewView(QWidget): header_font = QFont("Poppins", 18) header_font.setWeight(QFont.DemiBold) header.setFont(header_font) - header.setStyleSheet(""" - color: #1A365D; - padding: 8px 0; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - """) + header.setObjectName("platform_header") # For QSS targeting self.grid_layout.addWidget(header, row, 0, 1, 3) row += 1 @@ -364,26 +303,8 @@ class AccountsOverviewView(QWidget): cancel_button = msg_box.addButton("Abbrechen", QMessageBox.NoRole) msg_box.setDefaultButton(cancel_button) # Abbrechen als Standard - # Explizites Styling für den Löschen-Button - delete_button.setStyleSheet(""" - QPushButton { - background-color: #F44336; - color: #FFFFFF; - border: 1px solid #D32F2F; - border-radius: 4px; - padding: 6px 20px; - min-width: 80px; - min-height: 26px; - font-weight: 500; - } - QPushButton:hover { - background-color: #D32F2F; - border-color: #B71C1C; - } - QPushButton:pressed { - background-color: #B71C1C; - } - """) + # Set object name for QSS targeting + delete_button.setObjectName("delete_confirm_button") msg_box.exec_() diff --git a/views/components/platform_grid_view.py b/views/components/platform_grid_view.py index ebc691d..e91b186 100644 --- a/views/components/platform_grid_view.py +++ b/views/components/platform_grid_view.py @@ -45,19 +45,11 @@ class PlatformGridView(QWidget): title_font.setBold(True) self.title_label.setFont(title_font) - self.title_label.setStyleSheet(""" - QLabel { - color: #1A365D; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - margin-bottom: 20px; - } - """) - layout.addWidget(self.title_label) # Container für Plattform-Grid platforms_container = QWidget() - platforms_container.setStyleSheet("background: transparent;") + platforms_container.setObjectName("platforms_container") # For QSS targeting grid_layout = QGridLayout(platforms_container) grid_layout.setSpacing(24) # Styleguide Grid-Gap diff --git a/views/components/tab_navigation.py b/views/components/tab_navigation.py index 644a3aa..3dc2660 100644 --- a/views/components/tab_navigation.py +++ b/views/components/tab_navigation.py @@ -32,13 +32,8 @@ class TabNavigation(QWidget): # Feste Höhe nach Styleguide self.setFixedHeight(48) - # Basis-Styling - self.setStyleSheet(""" - QWidget { - background-color: #FFFFFF; - border-bottom: 1px solid #E2E8F0; - } - """) + # Set object name for QSS targeting - NO inline styles! + self.setObjectName("tab_navigation") # Layout layout = QHBoxLayout(self) @@ -70,30 +65,7 @@ class TabNavigation(QWidget): font.setWeight(QFont.Medium) btn.setFont(font) - btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - border-bottom: 2px solid transparent; - padding: 12px 24px; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - font-size: 15px; - font-weight: 500; - color: #4A5568; - min-width: 100px; - } - QPushButton:checked { - color: #1A365D; - border-bottom-color: #3182CE; - } - QPushButton:hover:!checked { - color: #2D3748; - background-color: #F7FAFC; - } - QPushButton:pressed { - color: #1A365D; - } - """) + # No inline styles - handled by QSS generator return btn diff --git a/views/main_window.py b/views/main_window.py index 341fbbe..0d289b8 100644 --- a/views/main_window.py +++ b/views/main_window.py @@ -27,7 +27,6 @@ class MainWindow(QMainWindow): # Signale platform_selected = pyqtSignal(str) back_to_selector_requested = pyqtSignal() - theme_toggled = pyqtSignal() def __init__(self, theme_manager=None, language_manager=None, db_manager=None): super().__init__() @@ -143,32 +142,53 @@ class MainWindow(QMainWindow): self.add_platform_tabs(platform_controller) def _create_menus(self): - """Erstellt die Menüeinträge.""" + """Erstellt die Menüeinträge und Dark Mode Toggle.""" # Erstelle ein Logo-Button anstelle des Text-Menüs - logo_widget = QPushButton() - logo_widget.setIcon(QIcon(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - "resources", "icons", "intelsight-logo.svg"))) - logo_widget.setIconSize(QSize(120, 40)) - logo_widget.setFlat(True) - logo_widget.setCursor(Qt.PointingHandCursor) - logo_widget.setStyleSheet(""" - QPushButton { - background-color: transparent; - border: none; - padding: 5px; - } - QPushButton:hover { - background-color: rgba(0, 0, 0, 0.05); - border-radius: 4px; - } - """) - logo_widget.clicked.connect(self._show_about_dialog) + self.logo_widget = QPushButton() # Store as instance variable for easier access - # Add logo to menu bar - self.menuBar().setCornerWidget(logo_widget, Qt.TopLeftCorner) + # Get the correct logo based on current theme + if self.theme_manager: + logo_path = self.theme_manager.get_icon_path("intelsight-logo") + else: + # Fallback if no theme manager + logo_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "resources", "icons", "intelsight-logo.svg") + + self.logo_widget.setIcon(QIcon(logo_path)) + self.logo_widget.setIconSize(QSize(120, 40)) + self.logo_widget.setFlat(True) + self.logo_widget.setCursor(Qt.PointingHandCursor) + self.logo_widget.setObjectName("logo_button") # For QSS targeting + self.logo_widget.clicked.connect(self._show_about_dialog) + + # Add logo to menu bar (left side) + self.menuBar().setCornerWidget(self.logo_widget, Qt.TopLeftCorner) + + # Create container for dark mode toggle (right side) + right_container = QWidget() + right_container.setObjectName("menubar_right_container") # For QSS targeting + right_layout = QHBoxLayout(right_container) + right_layout.setContentsMargins(0, 5, 10, 5) # Add some right margin + + # Import and create Dark Mode Toggle + from views.widgets.dark_mode_toggle import DarkModeToggle + self.dark_mode_toggle = DarkModeToggle(self) + + # Set initial state based on current theme + if self.theme_manager: + initial_dark = self.theme_manager.is_dark_mode() + self.dark_mode_toggle.setChecked(initial_dark) + + # Connect toggle to theme manager + self.dark_mode_toggle.toggled.connect(self._on_theme_toggled) + + right_layout.addWidget(self.dark_mode_toggle) + + # Add dark mode toggle to menu bar (right side) + self.menuBar().setCornerWidget(right_container, Qt.TopRightCorner) # Store reference for language updates - self.about_action = logo_widget + self.about_action = self.logo_widget def _show_about_dialog(self): @@ -177,6 +197,17 @@ class MainWindow(QMainWindow): dialog.exec_() + def _on_theme_toggled(self, is_dark): + """Handle theme toggle from the dark mode switch.""" + if self.theme_manager: + if is_dark: + self.theme_manager.apply_theme(self.theme_manager.DARK_THEME) + else: + self.theme_manager.apply_theme(self.theme_manager.LIGHT_THEME) + + # Explicitly update logo after theme change + self.update_logo() + def refresh_language_ui(self): """ Aktualisiert alle UI-Texte nach einem Sprachwechsel. @@ -238,4 +269,43 @@ class MainWindow(QMainWindow): def add_log_widget(self, text_widget): """Fügt einen GUI-Handler zum Logger hinzu.""" - add_gui_handler(logger, text_widget) \ No newline at end of file + add_gui_handler(logger, text_widget) + + def update_logo(self, theme_name: str = None): + """ + Updates the logo based on the current theme. + + Args: + theme_name: Name of the theme ("light" or "dark") - not used, kept for compatibility + """ + try: + # Use stored reference to logo widget + if hasattr(self, 'logo_widget') and self.logo_widget and self.theme_manager: + # Get the new logo path from theme manager based on current theme + current_theme = self.theme_manager.get_current_theme() + logo_path = self.theme_manager.get_icon_path("intelsight-logo") + + print(f"DEBUG: Updating logo for theme '{current_theme}'") + print(f"DEBUG: Logo path: {logo_path}") + print(f"DEBUG: File exists: {os.path.exists(logo_path)}") + + if os.path.exists(logo_path): + icon = QIcon(logo_path) + self.logo_widget.setIcon(icon) + # Force update + self.logo_widget.update() + self.logo_widget.repaint() + + logger.info(f"Logo updated to {logo_path} for theme {current_theme}") + print(f"DEBUG: Logo icon set successfully") + else: + logger.warning(f"Logo file not found: {logo_path}") + print(f"DEBUG: Logo file not found!") + else: + print(f"DEBUG: Cannot update logo - missing components:") + print(f" - has logo_widget: {hasattr(self, 'logo_widget')}") + print(f" - logo_widget exists: {hasattr(self, 'logo_widget') and self.logo_widget}") + print(f" - theme_manager exists: {self.theme_manager is not None}") + except Exception as e: + logger.error(f"Could not update logo: {e}") + print(f"DEBUG: Exception updating logo: {e}") \ No newline at end of file diff --git a/views/platform_selector.py b/views/platform_selector.py index ec882e3..8298ec6 100644 --- a/views/platform_selector.py +++ b/views/platform_selector.py @@ -48,7 +48,7 @@ class PlatformSelector(QWidget): # Content Container mit gestapelten Widgets self.content_stack = QStackedWidget() - self.content_stack.setStyleSheet("background-color: #F8FAFC;") + self.content_stack.setObjectName("content_stack") # For QSS targeting, no inline styles! # Platform Grid View (Tab 0) self.platform_grid = PlatformGridView(self.language_manager) diff --git a/views/widgets/account_card.py b/views/widgets/account_card.py index f883140..fb13b3e 100644 --- a/views/widgets/account_card.py +++ b/views/widgets/account_card.py @@ -85,10 +85,7 @@ class AccountCard(QFrame): username_font = QFont("Poppins", 16) username_font.setWeight(QFont.DemiBold) username_label.setFont(username_font) - username_label.setStyleSheet(""" - color: #1A365D; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - """) + username_label.setObjectName("account_username") # For QSS targeting info_layout.addWidget(username_label) info_layout.addStretch() @@ -97,25 +94,7 @@ class AccountCard(QFrame): # Login Button self.login_btn = QPushButton("Login") self.login_btn.setCursor(Qt.PointingHandCursor) - self.login_btn.setStyleSheet(""" - QPushButton { - background-color: #3182CE; - color: #FFFFFF; - border: none; - border-radius: 6px; - padding: 6px 16px; - font-size: 12px; - font-weight: 500; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - min-width: 60px; - } - QPushButton:hover { - background-color: #2563EB; - } - QPushButton:pressed { - background-color: #1D4ED8; - } - """) + self.login_btn.setObjectName("account_login_btn") # For QSS targeting self.login_btn.clicked.connect(lambda: self._on_login_clicked()) header_layout.addWidget(self.login_btn) @@ -136,32 +115,14 @@ class AccountCard(QFrame): email_layout.setSpacing(8) email_label = QLabel(self.account_data.get("email", "")) - email_label.setStyleSheet(""" - color: #4A5568; - font-size: 13px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - """) + email_label.setObjectName("account_detail_text") # For QSS targeting email_layout.addWidget(email_label) # Email Copy Button email_copy_btn = QPushButton() email_copy_btn.setToolTip("E-Mail kopieren") email_copy_btn.setCursor(Qt.PointingHandCursor) - email_copy_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - min-width: 20px; - max-width: 20px; - min-height: 20px; - max-height: 20px; - } - QPushButton:hover { - background-color: #F7FAFC; - border-radius: 4px; - } - """) + email_copy_btn.setObjectName("account_icon_btn") # For QSS targeting self.copy_icon = IconFactory.get_icon("copy", size=16) self.check_icon = IconFactory.get_icon("check", size=16, color="#10B981") self.email_copy_btn = email_copy_btn @@ -183,32 +144,14 @@ class AccountCard(QFrame): pass_layout.setSpacing(8) self.password_label = QLabel("••••••••") - self.password_label.setStyleSheet(""" - color: #4A5568; - font-size: 13px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - """) + self.password_label.setObjectName("account_detail_text") # For QSS targeting pass_layout.addWidget(self.password_label) # Copy Button copy_btn = QPushButton() copy_btn.setToolTip("Passwort kopieren") copy_btn.setCursor(Qt.PointingHandCursor) - copy_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - min-width: 20px; - max-width: 20px; - min-height: 20px; - max-height: 20px; - } - QPushButton:hover { - background-color: #F7FAFC; - border-radius: 4px; - } - """) + copy_btn.setObjectName("account_icon_btn") # For QSS targeting self.password_copy_btn = copy_btn self.password_copy_btn.setIcon(self.copy_icon) self.password_copy_btn.setIconSize(QSize(16, 16)) @@ -219,21 +162,7 @@ class AccountCard(QFrame): self.visibility_btn = QPushButton() self.visibility_btn.setToolTip("Passwort anzeigen") self.visibility_btn.setCursor(Qt.PointingHandCursor) - self.visibility_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - min-width: 20px; - max-width: 20px; - min-height: 20px; - max-height: 20px; - } - QPushButton:hover { - background-color: #F7FAFC; - border-radius: 4px; - } - """) + self.visibility_btn.setObjectName("account_icon_btn") # For QSS targeting self.eye_icon = IconFactory.get_icon("eye", size=16) self.eye_slash_icon = IconFactory.get_icon("eye-slash", size=16) self.visibility_btn.setIcon(self.eye_icon) @@ -249,11 +178,7 @@ class AccountCard(QFrame): details_grid.addWidget(date_icon, 2, 0) date_label = QLabel(self.account_data.get("created_at", "")) - date_label.setStyleSheet(""" - color: #A0AEC0; - font-size: 12px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - """) + date_label.setObjectName("account_date_text") # For QSS targeting details_grid.addWidget(date_label, 2, 1) layout.addLayout(details_grid) @@ -265,27 +190,7 @@ class AccountCard(QFrame): # Export Button self.export_btn = QPushButton("Profil\nexportieren") self.export_btn.setCursor(Qt.PointingHandCursor) - self.export_btn.setStyleSheet(""" - QPushButton { - background-color: #10B981; - color: #FFFFFF; - border: none; - border-radius: 6px; - padding: 4px 12px; - font-size: 12px; - font-weight: 500; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - min-width: 120px; - min-height: 36px; - text-align: center; - } - QPushButton:hover { - background-color: #059669; - } - QPushButton:pressed { - background-color: #047857; - } - """) + self.export_btn.setObjectName("account_export_btn") # For QSS targeting self.export_btn.clicked.connect(lambda: self.export_requested.emit(self.account_data)) actions_layout.addWidget(self.export_btn) @@ -294,25 +199,7 @@ class AccountCard(QFrame): # Delete Button self.delete_btn = QPushButton("Löschen") self.delete_btn.setCursor(Qt.PointingHandCursor) - self.delete_btn.setStyleSheet(""" - QPushButton { - background-color: #DC2626; - color: #FFFFFF; - border: none; - border-radius: 6px; - padding: 8px 16px; - font-size: 12px; - font-weight: 500; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - min-width: 90px; - } - QPushButton:hover { - background-color: #B91C1C; - } - QPushButton:pressed { - background-color: #991B1B; - } - """) + self.delete_btn.setObjectName("account_delete_btn") # For QSS targeting self.delete_btn.clicked.connect(lambda: self.delete_requested.emit(self.account_data)) actions_layout.addWidget(self.delete_btn) @@ -379,40 +266,14 @@ class AccountCard(QFrame): self.password_copy_timer.stop() def _apply_status_styling(self): - """Wendet Status-basiertes Styling mit Pastel-Hintergrund und farbiger Umrandung an""" + """Wendet Status-basiertes Styling an""" # Status aus Account-Daten lesen - Standard ist "active" (grün) status = self.account_data.get("status", "active") - # Status-Farben definieren (nur Grün/Rot) - status_styles = { - "active": { - "background": "#F0FDF4", # Sehr helles Mintgrün - "border": "#10B981", # Kräftiges Grün - "hover_border": "#059669" # Dunkleres Grün beim Hover - }, - "inactive": { - "background": "#FEF2F2", # Sehr helles Rosa - "border": "#EF4444", # Kräftiges Rot - "hover_border": "#DC2626" # Dunkleres Rot beim Hover - } - } - - # Aktueller Status oder Fallback auf active (grün) - current_style = status_styles.get(status, status_styles["active"]) - - # CSS-Styling anwenden - self.setStyleSheet(f""" - QFrame#accountCard {{ - background-color: {current_style["background"]}; - border: 2px solid {current_style["border"]}; - border-radius: 8px; - padding: 16px; - }} - QFrame#accountCard:hover {{ - border: 2px solid {current_style["hover_border"]}; - background-color: {current_style["background"]}; - }} - """) + # Set a property that QSS can use for styling + self.setProperty("status", status) + # Force style refresh + self.setStyle(self.style()) def update_status(self, new_status: str): """Aktualisiert den Status der Account-Karte und das Styling""" diff --git a/views/widgets/dark_mode_toggle.py b/views/widgets/dark_mode_toggle.py new file mode 100644 index 0000000..c4a4b1c --- /dev/null +++ b/views/widgets/dark_mode_toggle.py @@ -0,0 +1,233 @@ +""" +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 \ No newline at end of file diff --git a/views/widgets/platform_button.py b/views/widgets/platform_button.py index 720e218..5595594 100644 --- a/views/widgets/platform_button.py +++ b/views/widgets/platform_button.py @@ -38,27 +38,8 @@ class PlatformButton(QWidget): self.icon_button.setIconSize(QSize(120, 120)) # Größeres Icon self.icon_button.setMinimumSize(150, 150) - # Platform button styling based on Styleguide - self.icon_button.setStyleSheet(""" - QPushButton { - background-color: #F5F7FF; - border: 1px solid transparent; - border-radius: 16px; - padding: 32px; - } - QPushButton:hover { - background-color: #E8EBFF; - border: 1px solid #0099CC; - } - QPushButton:pressed { - background-color: #DCE2FF; - border: 1px solid #0099CC; - } - QPushButton:disabled { - background-color: #F0F0F0; - opacity: 0.5; - } - """) + # Set object name for QSS targeting + self.icon_button.setObjectName("platform_icon_button") # Button-Signal verbinden self.icon_button.clicked.connect(self.clicked) @@ -70,15 +51,8 @@ class PlatformButton(QWidget): name_font.setPointSize(12) name_font.setBold(True) self.name_label.setFont(name_font) - # Name label styling based on Styleguide - self.name_label.setStyleSheet(""" - QLabel { - color: #232D53; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - font-weight: 600; - letter-spacing: 0.5px; - } - """) + # Set object name for QSS targeting + self.name_label.setObjectName("platform_name_label") # Widgets zum Layout hinzufügen layout.addWidget(self.icon_button, 0, Qt.AlignCenter)