Files
Forge-Engine/screens/widgets/control_value.py
T
williamandClaude Sonnet 5 bda082bffd Ajoute une propriété "Ombre portée" (box-shadow), disponible sur tout widget
Nouveau groupe "Ombre" dans le panneau de propriétés, à côté de "Bordure"
(même universalité — voir UNIVERSAL_CONTROLS) : décalage X/Y, flou,
étendue, couleur et opacité, combinés en une seule valeur CSS box-shadow
(couleur+opacité fusionnées en rgba(), un <input type="color"> seul ne
portant pas de canal alpha). Décalages/flou/étendue tous à 0 = pas
d'ombre, même convention que border-width à 0 = pas de bordure.

Nouveau ctype "shadow" (c_shadow.py), suit exactement le même principe
que le ctype "size" déjà existant (size_override_controls.py) : plusieurs
entrées de formulaire pour un seul réglage, composées/décomposées dans
save_element_controls.py et control_value.py plutôt que passées par le
chemin générique clé->valeur.

Ajoute tests/test_shadow_controls.py (présence dans le panneau,
composition de la valeur CSS, aller-retour dans le formulaire, remise à
zéro qui retire l'ombre).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 14:37:38 +02:00

68 lines
2.5 KiB
Python

import re
def _control_value(el, control):
"""Lit la valeur actuelle d'un réglage à partir de l'élément stocké
(content / attributs / style), avec repli sur sa valeur par défaut."""
ctype = control["type"]
style = el.get("style") or {}
if ctype == "preset":
for key, _label, props in control["options"]:
if all((style.get(k) or "") == (v or "") for k, v in props.items()):
return key
return control.get("default")
if ctype == "toggle_token":
_kind, name = control["target"].split(":", 1)
current = style.get(name, "") or ""
return control["token"] in current.split()
if ctype == "scale":
transform = style.get("transform", "") or ""
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": "%"}
if ctype == "shadow":
_kind, name = control["target"].split(":", 1)
raw = style.get(name)
m = re.match(
r"^(-?\d+)px (-?\d+)px (\d+)px (\d+)px rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)$",
str(raw or ""),
)
if m:
x, y, blur, spread, r, g, b, a = m.groups()
return {
"x": int(x), "y": int(y), "blur": int(blur), "spread": int(spread),
"color": "#{:02x}{:02x}{:02x}".format(int(r), int(g), int(b)), "opacity": float(a),
}
return control.get("default", {})
target = control["target"]
if target == "content":
return el.get("content") or control.get("default", "")
kind, name = target.split(":", 1)
store = el.get("attributes") if kind == "attr" else style
store = store or {}
if ctype == "checkbox":
if name not in store:
return bool(control.get("default", False))
return store[name] not in (None, "", control.get("false_value"))
if ctype == "slider":
raw = store.get(name)
if raw is None:
return control.get("default", 0)
try:
return float(str(raw).replace(control.get("unit", ""), "").strip() or 0)
except ValueError:
return control.get("default", 0)
return store.get(name, control.get("default", ""))