From 3039489e39dcaaa796c167527ebe292c37ac7e76 Mon Sep 17 00:00:00 2001 From: william Date: Sun, 23 Aug 2026 19:23:19 +0200 Subject: [PATCH] Fix Jauge name centering and add name appearance controls Center the name+bar block vertically within the Jauge's own box (justify-content:center) so it no longer looks pinned to the top once a name label adds extra content height. Add appearance settings for the name: position (above the bar, or beside it on the left), alignment when above (centered or left-aligned), font family, font size, bold, and italic. Co-Authored-By: Claude Sonnet 5 --- screens/rendering/render_jauge.py | 37 +++++++++++++++----- screens/widgets/registry.py | 11 ++++++ tests/test_jauge.py | 56 +++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/screens/rendering/render_jauge.py b/screens/rendering/render_jauge.py index 5e6ddb1b..3639e0e0 100644 --- a/screens/rendering/render_jauge.py +++ b/screens/rendering/render_jauge.py @@ -56,12 +56,29 @@ def _render_jauge(el, meta, slug, children_map=None, ctx=None, parent_flex_direc "Modifier une donnée"), donc toujours à jour sans action supplémentaire à poser dans le graphe de logique.""" attrs = el.get("attributes") or {} - # display:flex column : le nom (optionnel) empile AU-DESSUS de la barre - # plutôt que de se superposer dedans — voir name_html plus bas. Le fond/ - # bordure/overflow vivent maintenant sur le wrapper de la barre - # (bar_wrap_style), pas ici, pour que le nom reste en dehors de la zone - # rognée par "overflow:hidden". - style = _style_string(el, " display:flex; flex-direction:column; gap:4px;", parent_flex_direction) + nom_position = attrs.get("_nom_position") or "dessus" + nom_alignement = attrs.get("_nom_alignement") or "centre" + nom_police = attrs.get("_nom_police") or "inherit" + nom_taille = attrs.get("_nom_taille") or "12" + nom_gras = attrs.get("_nom_gras") == "1" + nom_italique = attrs.get("_nom_italique") == "1" + # display:flex — le nom (optionnel) s'empile AU-DESSUS de la barre (mode + # "dessus", flex-direction:column) ou se place à sa gauche (mode "cote", + # flex-direction:row) plutôt que de se superposer dedans — voir name_html + # plus bas. "justify-content:center" recentre le bloc nom+barre quand la + # jauge se retrouve avec une hauteur supérieure à son contenu (ex. héritée + # d'un conteneur parent qui l'étire) — sans ça le bloc restait collé en + # haut de sa propre boîte, donnant une impression de centrage faussé dès + # qu'un nom (donc plus de contenu) était ajouté. Le fond/bordure/overflow + # vivent sur le wrapper de la barre (bar_wrap_style), pas ici, pour que le + # nom reste en dehors de la zone rognée par "overflow:hidden". + flex_dir = "row" if nom_position == "cote" else "column" + align_items = "center" if nom_position == "cote" else "stretch" + style = _style_string( + el, + f" display:flex; flex-direction:{flex_dir}; align-items:{align_items}; justify-content:center; gap:4px;", + parent_flex_direction, + ) definition_id = attrs.get("_definition_id") row_id = attrs.get("_row_id") field_name = (attrs.get("_champ") or "").strip() @@ -101,9 +118,13 @@ def _render_jauge(el, meta, slug, children_map=None, ctx=None, parent_flex_direc if field_name_nom: nom_value = _read_field_value(slug, definition_id, row_id, field_name_nom) if nom_value not in (None, ""): + text_align = "left" if (nom_position == "dessus" and nom_alignement == "gauche") else "center" name_html = ( - f'
{html_lib.escape(str(nom_value))}
' + f'
{html_lib.escape(str(nom_value))}
' ) # La classe "jaugeBar" est repérée côté client (play.html, refreshRuntimeData) diff --git a/screens/widgets/registry.py b/screens/widgets/registry.py index 99a68fc7..076ba9c0 100644 --- a/screens/widgets/registry.py +++ b/screens/widgets/registry.py @@ -8,6 +8,7 @@ from .controls.c_toggle_token import c_toggle_token from .controls.c_align import c_align from .controls.c_select import c_select from .control_groups.text_style_controls_extra import TEXT_STYLE_CONTROLS_EXTRA +from .font_options import FONT_OPTIONS from ..flow.constants import CONDITION_OPERATORS WIDGETS = { @@ -247,6 +248,16 @@ WIDGETS = { c_color("couleur_basse", "Couleur côté mini", "attr:_couleur_basse", "#e05252", group="Apparence"), c_color("couleur_haute", "Couleur côté maxi", "attr:_couleur_haute", "#4caf50", group="Apparence"), c_checkbox("afficher_valeur", "Afficher la valeur en chiffres sur la jauge", "attr:_afficher_valeur", "1", "", default=False, group="Apparence"), + c_select("nom_position", "Position du nom", "attr:_nom_position", + [("dessus", "Au-dessus de la jauge"), ("cote", "À côté (à gauche) de la jauge")], + default="dessus", group="Apparence"), + c_select("nom_alignement", "Alignement du nom (si au-dessus)", "attr:_nom_alignement", + [("centre", "Centré"), ("gauche", "Aligné à gauche")], + default="centre", group="Apparence"), + c_select("nom_police", "Police du nom", "attr:_nom_police", FONT_OPTIONS, default="inherit", group="Apparence"), + c_slider("nom_taille", "Taille du texte du nom", "attr:_nom_taille", unit="", min=8, max=32, default=12, group="Apparence"), + c_checkbox("nom_gras", "Nom en gras", "attr:_nom_gras", "1", "", default=False, group="Apparence"), + c_checkbox("nom_italique", "Nom en italique", "attr:_nom_italique", "1", "", default=False, group="Apparence"), ], }, "superposition": { diff --git a/tests/test_jauge.py b/tests/test_jauge.py index 3f97c9cc..5dbd6dfe 100644 --- a/tests/test_jauge.py +++ b/tests/test_jauge.py @@ -190,6 +190,62 @@ def test_gauge_bar_stays_visible_when_nested_in_a_container_with_a_name(client, assert "min-height:14px" in wrap_style +def test_gauge_name_position_beside_uses_row_layout(client, game): + def_id, row_ids = _create_jauge_object_with_rows(client, game) + screen_id = _create_screen(client, game) + resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False) + jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1)) + client.post(f"/game/{game}/elements/{jauge_id}/save", data={ + "ctrl_definition_id": str(def_id), "ctrl_row_id": str(row_ids["Confiance"]), + "ctrl_champ": "value", "ctrl_champ_nom": "name", "ctrl_min": "0", "ctrl_max": "100", + "ctrl_nom_position": "cote", + }) + html = client.get(f"/game/{game}/play").data.decode() + idx = html.find(f'data-element-id="{jauge_id}"') + style = html[html.find('style="', idx):html.find('>', idx)] + assert "flex-direction:row" in style + + +def test_gauge_name_appearance_controls_render_on_the_label(client, game): + """Police, taille, gras et italique du nom doivent se retrouver dans le + style inline du libellé — et la taille (contrôle slider sans unité + stockée, voir registry.py) ne doit pas finir dupliquée ("18pxpx").""" + def_id, row_ids = _create_jauge_object_with_rows(client, game) + screen_id = _create_screen(client, game) + resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False) + jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1)) + client.post(f"/game/{game}/elements/{jauge_id}/save", data={ + "ctrl_definition_id": str(def_id), "ctrl_row_id": str(row_ids["Confiance"]), + "ctrl_champ": "value", "ctrl_champ_nom": "name", "ctrl_min": "0", "ctrl_max": "100", + "ctrl_nom_police": "Georgia, serif", "ctrl_nom_taille": "18", + "ctrl_nom_gras": "1", "ctrl_nom_italique": "1", + }) + html = client.get(f"/game/{game}/play").data.decode() + idx = html.find(">Confiance<") + style = html[html.rfind('style="', 0, idx):idx] + assert "font-family:Georgia, serif" in style + assert "font-size:18px" in style + assert "18pxpx" not in style + assert "font-weight:700" in style + assert "font-style:italic" in style + + +def test_gauge_name_left_alignment_only_applies_when_above(client, game): + def_id, row_ids = _create_jauge_object_with_rows(client, game) + screen_id = _create_screen(client, game) + resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False) + jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1)) + client.post(f"/game/{game}/elements/{jauge_id}/save", data={ + "ctrl_definition_id": str(def_id), "ctrl_row_id": str(row_ids["Confiance"]), + "ctrl_champ": "value", "ctrl_champ_nom": "name", "ctrl_min": "0", "ctrl_max": "100", + "ctrl_nom_position": "dessus", "ctrl_nom_alignement": "gauche", + }) + html = client.get(f"/game/{game}/play").data.decode() + idx = html.find(">Confiance<") + style = html[html.rfind('style="', 0, idx):idx] + assert "text-align:left" in style + + # ---------- Régression : clics multipliés après un rafraîchissement de # données (bouton +10/-10 relié à une jauge) ----------