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>
68 lines
2.5 KiB
Python
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", ""))
|