Files
anonymisation/gui_v6/ui_kit.py
Domi31tls 34c681b791 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>
2026-06-12 12:06:05 +02:00

149 lines
4.8 KiB
Python

"""Composants UI stylés de la GUI V6 (G4), alignés sur la maquette.
Chaque helper reçoit une ``palette`` (cf. theme.py) et applique les couleurs
correspondantes à des widgets customtkinter. Aucune logique métier ici.
Les widgets ne sont créés qu'à l'appel (import sûr pour ``--self-test``).
"""
from __future__ import annotations
from typing import Optional
import customtkinter as ctk
CARD_RADIUS = 8
def font(size: int = 13, weight: str = "normal") -> "ctk.CTkFont":
return ctk.CTkFont(size=size, weight=weight)
class Card(ctk.CTkFrame):
"""Carte maquette : fond `card`, bordure `card_border`, titre uppercase optionnel."""
def __init__(self, master, palette: dict, title: Optional[str] = None, **kwargs):
super().__init__(
master,
fg_color=palette["card"],
border_color=palette["card_border"],
border_width=1,
corner_radius=CARD_RADIUS,
**kwargs,
)
self._palette = palette
self.body = self # alias pour clarté
if title:
ctk.CTkLabel(
self,
text=title.upper(),
text_color=palette["text_dim"],
font=font(11, "bold"),
anchor="w",
).pack(anchor="w", padx=16, pady=(14, 8))
def primary_button(master, palette: dict, text: str, command=None, large: bool = False):
return ctk.CTkButton(
master,
text=text,
command=command,
fg_color=palette["primary"],
hover_color=palette["primary_dim"],
text_color="#ffffff",
corner_radius=CARD_RADIUS,
height=38 if large else 32,
font=font(14 if large else 13, "bold"),
)
def secondary_button(master, palette: dict, text: str, command=None):
return ctk.CTkButton(
master,
text=text,
command=command,
fg_color=palette["btn_sec_bg"],
hover_color=palette["card_border"],
text_color=palette["text"],
border_color=palette["btn_sec_border"],
border_width=1,
corner_radius=CARD_RADIUS,
height=32,
font=font(13),
)
def success_button(master, palette: dict, text: str, command=None):
return ctk.CTkButton(
master,
text=text,
command=command,
fg_color=palette["success"],
hover_color=palette["success"],
text_color="#ffffff",
corner_radius=CARD_RADIUS,
height=32,
font=font(13, "bold"),
)
def pill_button(master, palette: dict, text: str, command=None, active: bool = False):
"""Bouton pilule (sélecteur de thème / sous-onglet)."""
return ctk.CTkButton(
master,
text=text,
command=command,
fg_color=palette["primary"] if active else "transparent",
hover_color=palette["primary_dim"],
text_color="#ffffff" if active else palette["text_dim"],
border_color=palette["card_border"] if not active else palette["primary"],
border_width=2,
corner_radius=99,
height=30,
font=font(12, "bold" if active else "normal"),
)
class StatCard(ctk.CTkFrame):
"""Carte statistique (rgrid/sc) : grande valeur + label."""
def __init__(self, master, palette: dict, value: str, label: str, value_color: Optional[str] = None, **kwargs):
super().__init__(
master,
fg_color=palette["btn_sec_bg"],
border_color=palette["btn_sec_border"],
border_width=1,
corner_radius=CARD_RADIUS,
**kwargs,
)
ctk.CTkLabel(
self, text=value, text_color=value_color or palette["primary"], font=font(22, "bold")
).pack(pady=(10, 0))
ctk.CTkLabel(self, text=label, text_color=palette["text_muted"], font=font(11)).pack(
pady=(0, 10)
)
class ToggleRow(ctk.CTkFrame):
"""Ligne de réglage (srow) : libellé + indice + interrupteur."""
def __init__(self, master, palette: dict, label: str, hint: str = "", value: bool = True, command=None, **kwargs):
super().__init__(master, fg_color="transparent", **kwargs)
left = ctk.CTkFrame(self, fg_color="transparent")
left.pack(side="left", fill="x", expand=True)
ctk.CTkLabel(left, text=label, text_color=palette["text"], font=font(13), anchor="w").pack(anchor="w")
if hint:
ctk.CTkLabel(left, text=hint, text_color=palette["text_muted"], font=font(11), anchor="w").pack(anchor="w")
self.var = ctk.BooleanVar(value=value)
self.switch = ctk.CTkSwitch(
self,
text="",
variable=self.var,
command=command,
progress_color=palette["primary"],
width=44,
)
self.switch.pack(side="right", padx=(8, 0))
def get(self) -> bool:
return bool(self.var.get())