Files
WGSecure/app/ui/theme.py
T
2026-09-04 08:55:35 +02:00

235 lines
9.4 KiB
Python

"""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 ───────────────────────────────────────────────────────
# Cinq 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",
"mfa": "#4a2a6e",
"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;")