feat(gui): GUI V6 G4 — alignement visuel sur la maquette v6 (option A)
Refonte de la couche présentation pour reprendre docs/ui_mockup_v6.html, sans changer de techno UI ni la logique G1-G3. - theme.py : 4 thèmes aux tokens EXACTS de la maquette (sombre #1a1a2e/#16213e/ #e94560, clair, médical, neutre), palette complète + status_color. - ui_kit.py (nouveau) : composants stylés (Card titrée, boutons primary/secondary/ success/pilule, StatCard, ToggleRow) appliquant la palette. - app.py : shell étroit, header identité + version + statut licence + liseré accent, barre d'onglets custom (plus de CTkTabview brut), navigation par recréation, changement de thème à chaud. - tab_usage : carte Apparence (sélecteur de thème), dropzone stylée, grille formats, barre d'actions, progression à étapes + journal, résultats en cartes statistiques. - tab_config : sous-navigation Réglages/Masquage/Partage/Règles ; Réglages câblé au ConfigState (profil, moteurs NER, dossier sortie). - tab_about : grille d'informations + bloc licence (logique inchangée). Logique inchangée : engine_bridge, config_state, license_client/store, runner. Tests : +9 (theme). self-test exit 0, 55 tests gui_v6, 202 tests/unit (0 régression). Smoke construction headless (Xvfb) : 3 onglets × 4 thèmes rendus sans erreur. Pas de pywebview, aucun .exe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
163
gui_v6/theme.py
163
gui_v6/theme.py
@@ -1,83 +1,140 @@
|
||||
"""Thèmes et palette de la GUI V6.
|
||||
"""Thèmes et palette de la GUI V6 (G4 — alignement maquette).
|
||||
|
||||
Mappe les tokens de couleur de la maquette ``docs/ui_mockup_v6.html`` vers
|
||||
customtkinter. Lot G1 : 4 thèmes de base + helper d'application. L'import de ce
|
||||
module ne crée aucun widget (compatible ``--self-test`` sans display).
|
||||
Reprend les 4 thèmes et les tokens couleur exacts de ``docs/ui_mockup_v6.html``.
|
||||
customtkinter n'a pas de variables CSS : on colore chaque widget via la palette
|
||||
résolue ici. Les tokens semi-transparents de la maquette (rgba) sont rendus en
|
||||
hex approchés sur le fond du thème.
|
||||
|
||||
Import de ce module : aucun widget créé (compatible ``--self-test``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
# Tokens couleur par thème (bandeau statut licence, accents, surfaces).
|
||||
THEMES: Dict[str, dict] = {
|
||||
"clair": {
|
||||
"appearance": "light",
|
||||
"color_theme": "blue",
|
||||
"accent": "#2563eb",
|
||||
"ok": "#16a34a",
|
||||
"warn": "#d97706",
|
||||
"error": "#dc2626",
|
||||
},
|
||||
# Palette complète par thème (tokens alignés sur la maquette HTML).
|
||||
PALETTES: Dict[str, dict] = {
|
||||
# Défaut maquette — sombre indigo / accent rose.
|
||||
"sombre": {
|
||||
"appearance": "dark",
|
||||
"color_theme": "blue",
|
||||
"accent": "#3b82f6",
|
||||
"ok": "#22c55e",
|
||||
"warn": "#f59e0b",
|
||||
"error": "#ef4444",
|
||||
"bg": "#1a1a2e",
|
||||
"card": "#16213e",
|
||||
"card_border": "#0f3460",
|
||||
"primary": "#e94560",
|
||||
"primary_dim": "#c73652",
|
||||
"accent": "#f5a623",
|
||||
"text": "#e0e0e0",
|
||||
"text_dim": "#9ca3af",
|
||||
"text_muted": "#6b7280",
|
||||
"success": "#10b981",
|
||||
"warning": "#f59e0b",
|
||||
"danger": "#ef4444",
|
||||
"blue": "#3b82f6",
|
||||
"divider": "#23233a",
|
||||
"btn_sec_bg": "#222a40",
|
||||
"btn_sec_border": "#333a4d",
|
||||
},
|
||||
"médical": {
|
||||
# Clair — fond gris moyen, cartes blanches.
|
||||
"clair": {
|
||||
"appearance": "light",
|
||||
"color_theme": "green",
|
||||
"accent": "#0d9488",
|
||||
"ok": "#15803d",
|
||||
"warn": "#ca8a04",
|
||||
"error": "#b91c1c",
|
||||
"bg": "#cdd2da",
|
||||
"card": "#ffffff",
|
||||
"card_border": "#9aa3b0",
|
||||
"primary": "#c93050",
|
||||
"primary_dim": "#a82545",
|
||||
"accent": "#b45309",
|
||||
"text": "#0d1117",
|
||||
"text_dim": "#1f2937",
|
||||
"text_muted": "#374151",
|
||||
"success": "#047857",
|
||||
"warning": "#b45309",
|
||||
"danger": "#b91c1c",
|
||||
"blue": "#1d4ed8",
|
||||
"divider": "#e6e9ee",
|
||||
"btn_sec_bg": "#eef0f3",
|
||||
"btn_sec_border": "#9aa3b0",
|
||||
},
|
||||
"contraste": {
|
||||
# Médical — fond bleu structuré.
|
||||
"medical": {
|
||||
"appearance": "light",
|
||||
"bg": "#b8ceea",
|
||||
"card": "#eef5ff",
|
||||
"card_border": "#6897ca",
|
||||
"primary": "#1a56db",
|
||||
"primary_dim": "#1340b0",
|
||||
"accent": "#0369a1",
|
||||
"text": "#071427",
|
||||
"text_dim": "#0f2a4a",
|
||||
"text_muted": "#1e3a5f",
|
||||
"success": "#166534",
|
||||
"warning": "#92400e",
|
||||
"danger": "#991b1b",
|
||||
"blue": "#1e40af",
|
||||
"divider": "#dbe8fb",
|
||||
"btn_sec_bg": "#e2edfc",
|
||||
"btn_sec_border": "#6897ca",
|
||||
},
|
||||
# Neutre — gris sombre.
|
||||
"neutre": {
|
||||
"appearance": "dark",
|
||||
"color_theme": "dark-blue",
|
||||
"accent": "#60a5fa",
|
||||
"ok": "#4ade80",
|
||||
"warn": "#fbbf24",
|
||||
"error": "#f87171",
|
||||
"bg": "#1f2937",
|
||||
"card": "#374151",
|
||||
"card_border": "#6b7280",
|
||||
"primary": "#818cf8",
|
||||
"primary_dim": "#6366f1",
|
||||
"accent": "#fbbf24",
|
||||
"text": "#f9fafb",
|
||||
"text_dim": "#e5e7eb",
|
||||
"text_muted": "#d1d5db",
|
||||
"success": "#34d399",
|
||||
"warning": "#fbbf24",
|
||||
"danger": "#f87171",
|
||||
"blue": "#60a5fa",
|
||||
"divider": "#2f3a49",
|
||||
"btn_sec_bg": "#3d4a5c",
|
||||
"btn_sec_border": "#6b7280",
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_THEME = "clair"
|
||||
DEFAULT_THEME = "sombre"
|
||||
|
||||
# Couleurs du bandeau de statut licence selon l'état métier.
|
||||
STATUS_COLORS = {
|
||||
"active": "ok",
|
||||
"grace": "warn",
|
||||
"expired": "error",
|
||||
"revoked": "error",
|
||||
"invalid": "error",
|
||||
"unavailable": "warn",
|
||||
"none": "warn",
|
||||
THEME_LABELS = {
|
||||
"sombre": "🌙 Sombre",
|
||||
"clair": "☀️ Clair",
|
||||
"medical": "🏥 Médical",
|
||||
"neutre": "🌿 Neutre",
|
||||
}
|
||||
|
||||
# Token de palette utilisé pour colorer un statut licence.
|
||||
_STATUS_TOKEN = {
|
||||
"active": "success",
|
||||
"grace": "warning",
|
||||
"expired": "danger",
|
||||
"revoked": "danger",
|
||||
"invalid": "danger",
|
||||
"unavailable": "warning",
|
||||
"none": "text_muted",
|
||||
}
|
||||
|
||||
|
||||
def theme_names() -> list[str]:
|
||||
return list(THEMES.keys())
|
||||
def theme_names() -> List[str]:
|
||||
return list(PALETTES.keys())
|
||||
|
||||
|
||||
def get_theme(name: str) -> dict:
|
||||
return THEMES.get(name, THEMES[DEFAULT_THEME])
|
||||
def get_palette(name: str) -> dict:
|
||||
return PALETTES.get(name, PALETTES[DEFAULT_THEME])
|
||||
|
||||
|
||||
def status_color(theme_name: str, status: str) -> str:
|
||||
"""Couleur hex pour un statut licence, dans le thème donné."""
|
||||
theme = get_theme(theme_name)
|
||||
return theme[STATUS_COLORS.get(status, "warn")]
|
||||
palette = get_palette(theme_name)
|
||||
return palette[_STATUS_TOKEN.get(status, "text_muted")]
|
||||
|
||||
|
||||
def apply_theme(name: str = DEFAULT_THEME) -> dict:
|
||||
"""Applique le thème à customtkinter. Import paresseux de ctk."""
|
||||
theme = get_theme(name)
|
||||
"""Applique le mode d'apparence customtkinter et retourne la palette."""
|
||||
palette = get_palette(name)
|
||||
import customtkinter as ctk
|
||||
|
||||
ctk.set_appearance_mode(theme["appearance"])
|
||||
ctk.set_default_color_theme(theme["color_theme"])
|
||||
return theme
|
||||
ctk.set_appearance_mode(palette["appearance"])
|
||||
return palette
|
||||
|
||||
Reference in New Issue
Block a user