Files
Forge-Engine/screens/rendering/style_string.py
T
williamandClaude Sonnet 5 94f1414506 Corrige un bug important : le style de TOUT élément neuf était cassé depuis l'ajout de l'Ombre
Bug remonté ("toutes les propriétés ne sont pas prises en compte") : la
vraie cause n'avait rien à voir avec les éléments de jeu — le contrôle
"Ombre" (voir le tour précédent) a une valeur par défaut composite (un
dict Python, {"x":0,"y":4,...}, pas une chaîne CSS). default_style_for_
widget.py, qui fige les réglages "dont la valeur par défaut a un effet
visuel voulu dès la création" pour chaque widget neuf, n'excluait pas ce
nouveau type de contrôle — il écrivait donc ce dict TEL QUEL (repr Python)
dans le style de CHAQUE élément fraîchement créé depuis ce commit, quel
que soit son widget. Une valeur CSS invalide au milieu du style pouvait
donner l'impression que "plein de propriétés" ne s'appliquaient plus.

Deux correctifs :
1. default_style_for_widget.py exclut maintenant "shadow" du gel à la
   création (même raisonnement déjà appliqué à "color" juste au-dessus :
   la valeur par défaut n'est qu'une suggestion affichée dans le panneau,
   pas un réglage neutre à figer - le neutre CSS est "pas d'ombre").
2. style_string.py ignore désormais toute valeur non scalaire (dict/liste)
   au moment de construire l'attribut style - filet de sécurité pour les
   éléments déjà créés AVANT ce correctif, qui portent encore ce dict figé
   en base et continueraient sinon à s'afficher cassés.

Ajoute deux tests de régression dans test_shadow_controls.py (aucune ombre
au premier rendu d'un élément neuf ; un élément déjà corrompu avant ce
correctif continue de s'afficher normalement).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 15:54:06 +02:00

81 lines
4.7 KiB
Python

from ..widgets.layout_capable_widgets import LAYOUT_CAPABLE_WIDGETS
def _style_string(el, extra_base="", parent_flex_direction=None, widget_key=None):
style = dict(el.get("style") or {})
if widget_key in LAYOUT_CAPABLE_WIDGETS and "display" not in style:
# "Empilement libre" (valeur par défaut de "Disposition interne")
# doit quand même permettre l'espacement (gap) entre les enfants —
# or le CSS "gap" n'a aucun effet sur un display bloc classique, il
# lui faut un conteneur flex/grid. Tant qu'aucune disposition n'a
# été choisie explicitement, on bascule donc ce widget sur un flex
# en colonne : visuellement identique à un simple empilement, mais
# l'espacement fonctionne dès la création, sans avoir à changer la
# disposition interne au préalable.
style["display"] = "flex"
style.setdefault("flex-direction", "column")
is_child = bool(el.get("parent_id"))
if is_child:
# Élément physiquement posé à l'intérieur d'un conteneur/répéteur/
# groupe de champs : une hauteur fixée à 100% se calcule contre la
# hauteur du parent et écraserait/déborderait les autres enfants
# (le conteneur coupe alors ce qui dépasse -> l'enfant "disparaît").
# La hauteur s'adapte donc à son contenu, comme dans une vraie page.
# Largeur : dans une disposition en LIGNE (flex-direction row), la
# largeur est l'axe principal — si chaque enfant réclame 100% de
# cette largeur, ils entrent en collision (l'algorithme flex les
# comprime alors tous, jusqu'à les faire quasi disparaître). Dans les
# autres cas (empilement libre, ou disposition en COLONNE où la
# largeur est l'axe transversal), 100% reste le bon défaut.
width_default = "auto" if parent_flex_direction in ("row", "row-reverse") else "100%"
base_style = f"width:{width_default}; height:auto; box-sizing:border-box; margin:0;" + extra_base
else:
base_style = "width:100%; height:100%; box-sizing:border-box; margin:0;" + extra_base
has_size_override = False
def _emit(k, v):
nonlocal has_size_override
if v in (None, ""):
return False
if isinstance(v, (dict, list)):
# Ne devrait jamais arriver (chaque contrôle composite — ex.
# "shadow", voir c_shadow.py — est censé combiner ses sous-
# valeurs en une seule chaîne CSS avant d'être stocké), mais
# sert de filet de sécurité : un dict/une liste écrits tels
# quels ici donneraient une valeur CSS invalide (repr Python)
# qui cassait TOUT le style de l'élément (bug déjà corrigé côté
# écriture — voir default_style_for_widget.py — pour des
# éléments déjà créés avant ce correctif).
return False
if k == "transform" and not is_child:
# Un élément posé directement sur un écran a déjà ce "transform"
# posé sur son cadre .canvasElementInner / .playElementInner
# (voir filters/element_style_filter.py:_element_transform_style)
# — l'écrire ici aussi (sur la balise intérieure, à l'intérieur
# d'un cadre qui reste fixe) donnait l'impression que l'échelle/
# l'animation restait "coincée à l'intérieur" au lieu de
# s'appliquer à l'élément entier. Un élément posé à l'intérieur
# d'un conteneur/répéteur/groupe de champs n'a pas ce cadre
# séparé (sa balise EST déjà tout l'élément) : pour lui,
# "transform" reste ici.
return False
if k in ("width", "height") and str(v).strip() in ("0px", "0"):
# 0 = réglage "Taille dans le conteneur" laissé à sa valeur par
# défaut (voir SIZE_OVERRIDE_CONTROLS) : ne pas l'écrire, pour
# garder le comportement automatique de base_style ci-dessus.
return False
if k in ("width", "height"):
has_size_override = True
return True
user_style = "; ".join(f"{k}:{v}" for k, v in style.items() if _emit(k, v))
if has_size_override:
# Une taille fixe explicite (voir "Taille dans le conteneur") ne doit
# pas être écrasée par la compression automatique d'une disposition
# flex (flex-shrink vaut 1 par défaut sur tout enfant) — sinon un
# élément à qui on a donné une largeur/hauteur précise peut quand
# même se retrouver comprimé si ses voisins manquent de place.
user_style = (user_style + "; " if user_style else "") + "flex-shrink:0"
return base_style + (" " + user_style + ";" if user_style else "")