"""Jetons de couleur et fragments de feuille de style partagés. Les couleurs étaient écrites en dur au fil des widgets : vingt-quatre valeurs distinctes dans le seul panneau d'administration, cinq bandeaux d'onglets sans rapport de teinte entre eux, et des blocs de résultat dont le fond ne correspondait ni à celui de la page ni à celui de leurs voisins. Tout passe désormais par ce module — une couleur ne se choisit plus au cas par cas. """ # ── Fonds ──────────────────────────────────────────────────────────────────── BG = "#1c2833" # fond de l'application et de toutes les pages BG_SUNKEN = "#17202a" # zones en creux : configurations, blocs de code BG_RAISED = "#253545" # éléments posés sur le fond : listes, en-têtes secondaires BORDER = "rgba(255,255,255,0.18)" BORDER_SOFT = "rgba(255,255,255,0.10)" INPUT_BG = "rgba(255,255,255,0.10)" # fond des champs de saisie et listes # ── Textes ─────────────────────────────────────────────────────────────────── TEXT = "white" TEXT_MUTED = "rgba(255,255,255,0.70)" TEXT_FAINT = "rgba(255,255,255,0.50)" TEXT_CODE = "#a8d8ea" # texte à chasse fixe (aperçus de config, rapports) # ── Accent ─────────────────────────────────────────────────────────────────── ACCENT = "#2471a3" ACCENT_HOVER = "#1a5276" ACCENT_ACTIVE = "#154360" ACCENT_LIGHT = "#5dade2" # ── États ──────────────────────────────────────────────────────────────────── # Chaque état porte un trio : teinte du texte, fond translucide, bouton plein. OK_TEXT, OK_BG, OK_SOLID = "#a9dfbf", "rgba(39,174,96,0.18)", "#1e8449" WARN_TEXT, WARN_BG, WARN_SOLID = "#f7dc6f", "rgba(241,196,15,0.15)", "#b7950b" FAIL_TEXT, FAIL_BG, FAIL_SOLID = "#f1948a", "rgba(231,76,60,0.18)", "#c0392b" SKIP_TEXT, SKIP_BG = "rgba(255,255,255,0.45)", "rgba(255,255,255,0.05)" # Teintes de survol des boutons pleins ok/fail — reprises telles quelles à # plusieurs endroits (panneau admin, fenêtre principale) : un seul nom leur # évite de dériver l'une de l'autre au fil des modifications. OK_HOVER = "#27ae60" FAIL_HOVER = "#e74c3c" _STATE = { "ok": (OK_TEXT, OK_BG), "warn": (WARN_TEXT, WARN_BG), "fail": (FAIL_TEXT, FAIL_BG), "skip": (SKIP_TEXT, SKIP_BG), "idle": (TEXT_MUTED, "transparent"), } # ── Badge de ping (fenêtre principale) ────────────────────────────────────── PING_CSS = { "good": "background:#1e8449; color:white;", "medium": "background:#d4ac0d; color:black;", "bad": "background:#922b21; color:white;", "offline": f"background:{BG_RAISED}; color:{TEXT_FAINT};", } # ── Entrées de journal (fenêtre principale) ───────────────────────────────── LOG_COLORS = { "success": OK_TEXT, "error": FAIL_TEXT, "warning": WARN_TEXT, "info": ACCENT_LIGHT, } # ── Bandeaux d'onglets ─────────────────────────────────────────────────────── # Six teintes calées sur la même luminosité et la même saturation : les # onglets restent distinguables sans que l'un paraisse plus clair ou plus # saturé que les autres, ce qui était le cas des valeurs choisies à la main. BANNER = { "wireguard": "#1a5276", "keys": "#1a4a6e", "test": "#1a4a7a", "settings": "#155e52", "security": "#7a3b1a", "about": "#243b53", } def result_style(state: str, font_size: int = 11) -> str: """Feuille de style d'un bloc de résultat, selon son état. Les blocs affichaient chacun leur propre fond rgba, recopié à la main : même état, teintes différentes d'un widget à l'autre. """ color, bg = _STATE.get(state, _STATE["idle"]) return (f"padding: 8px; border-radius: 6px; font-size: {font_size}px;" f" background: {bg}; color: {color};") def result_view_style(state: str = "idle", font_size: int = 12) -> str: """Même chose pour un QTextEdit en lecture seule (rapport défilant).""" color, bg = _STATE.get(state, _STATE["idle"]) return ( f"QTextEdit {{ background: {bg}; color: {color};" f" border: 1px solid {BORDER_SOFT}; border-radius: 6px;" f" padding: 8px; font-size: {font_size}px; }}" f"QScrollBar:vertical {{ background: transparent; width: 8px;" f" border: none; }}" f"QScrollBar::handle:vertical {{ background: {BG_RAISED};" f" border-radius: 4px; }}" "QScrollBar::add-line:vertical," "QScrollBar::sub-line:vertical { height: 0; }" ) def scrollarea_style() -> str: return ( f"QScrollArea {{ border: none; background: {BG}; }}" f"QScrollBar:vertical {{ background: {BG}; width: 8px; border: none; }}" f"QScrollBar::handle:vertical {{ background: {BG_RAISED};" f" border-radius: 4px; }}" "QScrollBar::add-line:vertical," "QScrollBar::sub-line:vertical { height: 0; }" ) def banner_style(color: str) -> str: return (f"background: {color}; color: {TEXT}; font-size: 13px;" " font-weight: bold; padding-left: 8px;") #: CSS sombre commun à toutes les pages. TAB_CSS = f""" QWidget {{ background-color: {BG}; color: {TEXT}; }} QLabel {{ color: {TEXT}; background: transparent; }} QCheckBox {{ color: {TEXT}; background: transparent; }} QGroupBox {{ color: {TEXT}; background: transparent; border: 1px solid {BORDER}; border-radius: 5px; margin-top: 10px; font-weight: bold; }} QGroupBox::title {{ subcontrol-origin: margin; left: 10px; padding: 0 4px; color: {TEXT}; }} QLineEdit {{ background: {INPUT_BG}; color: {TEXT}; border: 1px solid {BORDER}; border-radius: 4px; padding: 4px 6px; }} QLineEdit:read-only {{ background: rgba(255,255,255,0.06); }} QLineEdit:disabled {{ background: rgba(255,255,255,0.04); color: {TEXT_FAINT}; }} QSpinBox {{ background: {INPUT_BG}; color: {TEXT}; border: 1px solid {BORDER}; border-radius: 4px; padding: 3px 6px; }} QSpinBox::up-button, QSpinBox::down-button {{ background: rgba(255,255,255,0.15); }} QComboBox {{ background: {INPUT_BG}; color: {TEXT}; border: 1px solid {BORDER}; border-radius: 4px; padding: 3px 8px; }} QComboBox::drop-down {{ border: none; }} QComboBox QAbstractItemView {{ background: {BG_RAISED}; color: {TEXT}; selection-background-color: {ACCENT}; }} QTextEdit {{ background: {BG_SUNKEN}; color: {TEXT_CODE}; border: 1px solid {BORDER}; border-radius: 4px; }} QPushButton {{ background: {ACCENT}; color: {TEXT}; border-radius: 5px; padding: 7px 12px; border: none; }} QPushButton:hover {{ background: {ACCENT_HOVER}; }} QPushButton:checked {{ background: {ACCENT_ACTIVE}; }} QPushButton:disabled {{ background: rgba(255,255,255,0.08); color: {TEXT_FAINT}; }} """ #: Barre d'onglets, utilisée par le panneau principal et les sous-onglets. TABBAR_CSS = f""" QTabWidget::pane {{ border: none; background: {BG}; }} QTabBar::tab {{ background: {BG_RAISED}; color: {TEXT_MUTED}; padding: 6px 14px; border: none; }} QTabBar::tab:selected {{ background: {ACCENT}; color: {TEXT}; }} QTabBar::tab:hover:!selected {{ background: {ACCENT_HOVER}; color: {TEXT}; }} """ def primary_button_style(solid: str = OK_SOLID, hover: str = OK_HOVER) -> str: """Bouton d'action principale d'une page (lancer un test, par exemple).""" return (f"QPushButton {{ padding: 9px; background: {solid}; color: {TEXT};" f" border-radius: 5px; font-weight: bold; border: none; }}" f"QPushButton:hover {{ background: {hover}; }}") def secondary_button_style() -> str: """Bouton d'action secondaire d'une page (à côté d'une action principale).""" return (f"QPushButton {{ padding: 9px; background: {BG_RAISED}; color: {TEXT};" f" border-radius: 5px; border: none; }}" f"QPushButton:hover {{ background: {ACCENT_HOVER}; }}") def hint_style() -> str: """Encart d'introduction posé en tête de chaque onglet. Assez contrasté pour se détacher du fond de page, assez sobre pour ne pas concurrencer le contenu : un liseré d'accent à gauche, pas de cadre plein. """ return (f"background: rgba(36,113,163,0.16);" f" border-left: 3px solid {ACCENT_LIGHT};" f" border-radius: 4px; padding: 9px 11px;" f" color: {TEXT_MUTED}; font-size: 11px;")