Consolidate the properties panel: Position & taille, no more Taille/Survol/duplicate Disposition

- Merge "Taille dans le conteneur" into "Position & taille": a child
  element (posé dans un conteneur/répéteur/groupe de champs) now shows
  greyed-out X/Y (not applicable, it follows its parent's layout) and
  a single Largeur/Hauteur field per axis with a unit selector (%/px)
  instead of the old fixed-px-only sliders capped at 1000 — which was
  the root cause of a landscape-mode bug where a child couldn't be
  made wider than 1000px even though the actual screen was wider.
  New c_size control type (one style key per axis, not two competing
  sliders — an earlier px+% two-slider attempt let the untouched
  slider silently clobber the other's value on every autosave).
- Merge the two "Disposition" groups (visibility + scale, previously
  split apart in UNIVERSAL_CONTROLS by unrelated groups) into one.
- Remove the "Survol" panel: hovering is conceptually a flow trigger,
  not a static element property — to be reintroduced there. The
  underlying data-hover-text/bindHoverTexts runtime is untouched.
This commit is contained in:
william
2026-08-23 12:14:00 +02:00
parent d86b1a60c7
commit 4a593d07e4
9 changed files with 120 additions and 33 deletions
+19
View File
@@ -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
@@ -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"),
]
@@ -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
+8
View File
@@ -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", "")
+5
View File
@@ -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}
+5 -11
View File
@@ -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", "")