diff --git a/screens/elements/save_element_controls.py b/screens/elements/save_element_controls.py index 7df352b4..6e3d3842 100644 --- a/screens/elements/save_element_controls.py +++ b/screens/elements/save_element_controls.py @@ -64,6 +64,25 @@ def save_element_controls(slug, element_id, form): style["transform"] = f"scale({val})" continue + if ctype == "size": + # Un seul champ CSS ("width"/"height") mais deux entrées de + # formulaire (valeur + unité) — voir size_override_controls.py + # pour pourquoi ce n'est PAS deux contrôles slider séparés. + _kind, name = control["target"].split(":", 1) + try: + num = float(form.get(field_name + "_value") or 0) + except (TypeError, ValueError): + num = 0.0 + unit = form.get(field_name + "_unit", "%") + if unit not in ("px", "%"): + unit = "%" + if num <= 0: + style.pop(name, None) + else: + num_str = str(int(num)) if num == int(num) else str(num) + style[name] = f"{num_str}{unit}" + continue + target = control["target"] if ctype == "checkbox": checked = form.get(field_name) is not None diff --git a/screens/widgets/control_groups/size_override_controls.py b/screens/widgets/control_groups/size_override_controls.py index b46e53f8..8329efd6 100644 --- a/screens/widgets/control_groups/size_override_controls.py +++ b/screens/widgets/control_groups/size_override_controls.py @@ -1,16 +1,29 @@ -from ..controls.c_slider import c_slider +from ..controls.c_size import c_size -# Taille fixe (en pixels), surtout utile pour un élément posé À L'INTÉRIEUR -# d'un conteneur/répéteur/groupe de champs : par défaut, ces éléments -# remplissent la largeur disponible et s'ajustent en hauteur à leur contenu -# (voir _style_string) — 0 garde ce comportement automatique ; une valeur -# non nulle impose une taille fixe (ex: une image de 120px de large dans une -# ligne, pour que "Espacement égal entre eux"/"autour"/"uniforme" sur le -# conteneur parent ait un effet visible, ce qui n'est pas le cas tant que -# chaque élément remplit 100% de la largeur). +# Taille d'un élément posé À L'INTÉRIEUR d'un conteneur/répéteur/groupe de +# champs : par défaut, ces éléments remplissent la largeur disponible et +# s'ajustent en hauteur à leur contenu (voir _style_string) — 0 garde ce +# comportement automatique ; une valeur non nulle impose une taille fixe, +# dans l'unité choisie par l'utilisateur (% du parent, qui suit +# naturellement n'importe quelle taille d'écran — ou px, pour une taille +# précise, ex: une image de 120px de large dans une ligne, pour que +# "Espacement égal entre eux"/"autour"/"uniforme" sur le conteneur parent +# ait un effet visible, ce qui n'est pas le cas tant que chaque élément +# remplit 100% de la largeur). +# +# UN SEUL contrôle par axe (pas un slider % + un slider px séparés) : les +# deux cibleraient la même propriété CSS "width"/"height", et le mécanisme +# de save_element_controls.py qui préserve un réglage "jamais personnalisé" +# ne protège que le PREMIER contrôle à écrire une clé donnée — un second +# contrôle non touché par l'utilisateur mais ciblant la même clé +# l'écraserait quand même à chaque sauvegarde (bug constaté en pratique). +# +# Ces réglages ne sont PAS rendus via le mécanisme générique de groupes du +# panneau de propriétés (voir screen_edit.html) : ils sont affichés à la +# main dans le bloc "Position & taille", à la place de X/Y (non applicables +# à un élément qui suit le flux de son parent plutôt qu'une position +# absolue) — voir selected_controls / size_ctrls dans le template. SIZE_OVERRIDE_CONTROLS = [ - c_slider("child-width", "Largeur fixe", "style:width", - min=0, max=1000, default=0, group="Taille dans le conteneur"), - c_slider("child-height", "Hauteur fixe", "style:height", - min=0, max=1000, default=0, group="Taille dans le conteneur"), + c_size("child-width", "Largeur", "style:width", default=0, group="Position & taille"), + c_size("child-height", "Hauteur", "style:height", default=0, group="Position & taille"), ] diff --git a/screens/widgets/control_groups/universal_controls.py b/screens/widgets/control_groups/universal_controls.py index c18bfb3d..2dcc8d68 100644 --- a/screens/widgets/control_groups/universal_controls.py +++ b/screens/widgets/control_groups/universal_controls.py @@ -3,6 +3,12 @@ from .visibility_control import VISIBILITY_CONTROL from .size_override_controls import SIZE_OVERRIDE_CONTROLS from .spacing_controls import SPACING_CONTROLS from .border_controls import BORDER_CONTROLS -from .hover_controls import HOVER_CONTROLS -UNIVERSAL_CONTROLS = [VISIBILITY_CONTROL] + SIZE_OVERRIDE_CONTROLS + SPACING_CONTROLS + BORDER_CONTROLS + [c_scale()] + HOVER_CONTROLS +# Le panneau "Survol" (HOVER_CONTROLS) a été retiré du panneau de propriétés : +# survoler un élément est conceptuellement un DÉCLENCHEUR (voir la Logique de +# la scène), pas une propriété statique de l'élément — il sera réintroduit +# là-bas. Le mécanisme data-hover-text sous-jacent (voir render_element_html +# et bindHoverTexts() dans play.html) reste fonctionnel pour les éléments qui +# en avaient déjà un, seul le moyen de le RÉGLER depuis ce panneau disparaît +# pour l'instant. +UNIVERSAL_CONTROLS = [VISIBILITY_CONTROL, c_scale()] + SIZE_OVERRIDE_CONTROLS + SPACING_CONTROLS + BORDER_CONTROLS diff --git a/screens/widgets/control_value.py b/screens/widgets/control_value.py index ad9dddc0..d6418b07 100644 --- a/screens/widgets/control_value.py +++ b/screens/widgets/control_value.py @@ -23,6 +23,14 @@ def _control_value(el, control): m = re.search(r"scale\(([\d.]+)\)", transform) return float(m.group(1)) if m else control.get("default", 1) + if ctype == "size": + _kind, name = control["target"].split(":", 1) + raw = style.get(name) + m = re.match(r"^([\d.]+)(px|%)$", str(raw or "")) + if m: + return {"value": float(m.group(1)), "unit": m.group(2)} + return {"value": control.get("default", 0), "unit": "%"} + target = control["target"] if target == "content": return el.get("content") or control.get("default", "") diff --git a/screens/widgets/controls/c_size.py b/screens/widgets/controls/c_size.py new file mode 100644 index 00000000..7adbbb7d --- /dev/null +++ b/screens/widgets/controls/c_size.py @@ -0,0 +1,5 @@ +def c_size(key, label, target, default=0, group=None): + """Taille avec unité choisie par l'utilisateur (% ou px) — voir + size_override_controls.py. 0 = comportement automatique, quelle que soit + l'unité affichée.""" + return {"key": key, "label": label, "type": "size", "target": target, "default": default, "group": group} diff --git a/screens/widgets/default_style_for_widget.py b/screens/widgets/default_style_for_widget.py index 37d1f7a6..89255f31 100644 --- a/screens/widgets/default_style_for_widget.py +++ b/screens/widgets/default_style_for_widget.py @@ -1,14 +1,5 @@ from .widget_meta import widget_meta -# Ces deux réglages utilisent 0 comme valeur sentinelle "automatique" (voir -# size_override_controls.py / _style_string) : contrairement aux autres -# sliders, 0 n'y est PAS une vraie valeur par défaut à figer, donc on ne les -# fige jamais à la création — les autres sliders à 0 par défaut (ex: -# épaisseur de bordure) doivent au contraire être figés explicitement, sinon -# un border-style baké sans border-width associé afficherait une bordure par -# défaut du navigateur (~3px, "medium") au lieu de rester invisible. -_AUTO_SIZE_KEYS = {"child-width", "child-height"} - # Réglages "par côté" (padding/margin/bordure individuels) : contrairement # aux réglages "tous les côtés" équivalents (qui, eux, DOIVENT être figés à # la création — voir plus bas), ceux-ci ne doivent jamais l'être. Une fois @@ -47,6 +38,11 @@ def default_style_and_attributes(widget_key): ctype = control["type"] if ctype in ("checkbox", "toggle_token", "preset", "scale", "color"): continue + if ctype == "size": + # 0 est TOUJOURS la valeur sentinelle "automatique" pour ce + # type de réglage (voir size_override_controls.py) — jamais une + # vraie valeur par défaut à figer à la création. + continue target = control.get("target") if not target or target == "content": continue @@ -54,8 +50,6 @@ def default_style_and_attributes(widget_key): if default in (None, ""): continue if ctype == "slider": - if control["key"] in _AUTO_SIZE_KEYS and str(default) in ("0",): - continue # 0 = comportement automatique, rien à figer if control["key"] in _PER_SIDE_KEYS: continue # réglage par côté : jamais figé (voir commentaire plus haut) unit = control.get("unit", "") diff --git a/static/style.css b/static/style.css index 4d46fd8d..a6b89ccc 100644 --- a/static/style.css +++ b/static/style.css @@ -176,6 +176,10 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d .posGrid{ display:grid; grid-template-columns:1fr 1fr; gap:8px 12px; margin-bottom:14px; } .posGrid label{ font-size:11px; color:var(--text-dim); display:flex; flex-direction:column; gap:4px; } .posGrid input{ background:var(--panel2); border:1px solid var(--border); border-radius:6px; color:var(--text); padding:6px 8px; } +.posGrid input:disabled{ opacity:0.4; cursor:not-allowed; } +.sizeField{ display:flex; gap:4px; } +.sizeField input{ flex:1; min-width:0; } +.sizeField select{ width:auto; flex:0 0 auto; padding:6px 4px; } .controlRow{ margin-bottom:12px; } .controlRow > label{ display:block; font-size:12px; color:var(--text-dim); margin-bottom:4px; } diff --git a/templates/screen_edit.html b/templates/screen_edit.html index 27f3ad79..41d9558d 100644 --- a/templates/screen_edit.html +++ b/templates/screen_edit.html @@ -188,21 +188,49 @@ {% endif %} + {% set size_ctrl_keys = ['child-width', 'child-height'] %} + {% set size_ctrls = {} %} + {% for c in selected_controls if c.key in size_ctrl_keys %}{% set _ = size_ctrls.update({c.key: c}) %}{% endfor %} + +