From 38f33d88d9178a6fc85e84c0c45c60255595e0d9 Mon Sep 17 00:00:00 2001 From: william Date: Mon, 24 Aug 2026 12:54:50 +0200 Subject: [PATCH] Make Bulma the default style engine for Bouton/Titre/Conteneur, layered under existing inline customization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new "class:" control-target kind (save_element_controls.py, _visible_attrs.py) alongside the existing content/attr/style ones, so a widget can carry CSS classes built from independent named slots (color, size, shape...) without them overwriting each other. Bouton and Titre get their Bulma base class (button/title) via fixed_attrs; Conteneur gets an opt-in "Carte (Bulma)" preset instead of a forced default, since it's also used as an invisible layout wrapper. Bouton's font-size/border-radius sliders no longer freeze their default value into inline style at creation (new c_slider no_freeze flag), so Bulma's own button styling shows through until a user actually customizes it — inline style still wins over any class the moment it's set, exactly like today. Loads bulma.min.css via CDN in screen_edit.html and play.html, same pattern as animate.css. --- screens/elements/save_element_controls.py | 10 +++++++- screens/rendering/visible_attrs.py | 14 ++++++++++- .../widgets/control_groups/bulma_controls.py | 25 +++++++++++++++++++ screens/widgets/controls/c_slider.py | 13 ++++++++-- screens/widgets/default_style_for_widget.py | 4 +-- screens/widgets/registry.py | 23 ++++++++++++++--- templates/play.html | 1 + templates/screen_edit.html | 7 ++++++ 8 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 screens/widgets/control_groups/bulma_controls.py diff --git a/screens/elements/save_element_controls.py b/screens/elements/save_element_controls.py index 6e3d3842..142d34b7 100644 --- a/screens/elements/save_element_controls.py +++ b/screens/elements/save_element_controls.py @@ -97,7 +97,15 @@ def save_element_controls(slug, element_id, form): content = raw_value else: kind, name = target.split(":", 1) - store = attributes if kind == "attr" else style + if kind == "class": + # "class:xxx" : un jeton de classe CSS externe (ex. un + # modificateur Bulma) parmi d'autres, chacun mémorisé sous sa + # propre clé "_class_xxx" pour ne pas s'écraser entre eux — + # voir _visible_attrs.py qui les rassemble avec la classe de + # base du widget (fixed_attrs) au moment du rendu. + store, name = attributes, "_class_" + name + else: + store = attributes if kind == "attr" else style if raw_value in (None, ""): store.pop(name, None) elif ctype != "checkbox" and name not in store and _is_default_value(control, raw_value): diff --git a/screens/rendering/visible_attrs.py b/screens/rendering/visible_attrs.py index 4cd96370..02293b17 100644 --- a/screens/rendering/visible_attrs.py +++ b/screens/rendering/visible_attrs.py @@ -3,11 +3,23 @@ from .auto_id import _auto_id def _visible_attrs(el, meta, ctx=None): + raw_attrs = el.get("attributes") or {} attrs = { k: (_apply_ctx(v, ctx) if isinstance(v, str) else v) - for k, v in (el.get("attributes") or {}).items() if not k.startswith("_") + for k, v in raw_attrs.items() if not k.startswith("_") } attrs.update(meta.get("fixed_attrs", {})) + # Classes externes (ex. Bulma) réglées via un contrôle "class:xxx" — voir + # save_element_controls.py — chacune mémorisée sous "_class_xxx" pour ne + # pas s'écraser entre elles ; assemblées ici avec la classe de base du + # widget (posée via fixed_attrs, ex. "button"/"box"/"title") pour former + # l'attribut class final. Une classe personnalisée éventuellement tapée + # à la main (peu probable ici, mais possible via fixed_attrs) est gardée + # en tête. + extra_classes = [v for k, v in raw_attrs.items() if k.startswith("_class_") and v] + if extra_classes: + base_class = attrs.get("class", "") + attrs["class"] = " ".join(([base_class] if base_class else []) + extra_classes) attrs.setdefault("id", _auto_id(el)) attrs["data-element-id"] = el["id"] if el.get("parent_id"): diff --git a/screens/widgets/control_groups/bulma_controls.py b/screens/widgets/control_groups/bulma_controls.py new file mode 100644 index 00000000..fe93bca7 --- /dev/null +++ b/screens/widgets/control_groups/bulma_controls.py @@ -0,0 +1,25 @@ +from ..controls.c_select import c_select +from ..controls.c_checkbox import c_checkbox + +# Réglages Bulma (https://bulma.io) posés en plus des réglages de style +# "libres" déjà existants (couleur, taille de police...) — chacun ajoute un +# jeton de classe CSS (target "class:xxx", voir save_element_controls.py et +# _visible_attrs.py) à la classe de base du widget (posée via fixed_attrs, +# ex. "button"). Laissé à "" (par défaut) = look Bulma nu, sans modificateur. +# Les réglages de couleur/taille "libres" du widget restent utilisables par- +# dessus : un style en ligne gagne toujours contre une classe CSS, donc +# personnaliser une couleur ou une taille continue de fonctionner exactement +# comme avant, Bulma ne servant alors que de base par défaut. +BULMA_BUTTON_CONTROLS = [ + c_select("bulma_couleur", "Style Bulma — couleur", "class:couleur", [ + ("", "Aucun (couleurs ci-dessous)"), + ("is-primary", "Primaire"), ("is-link", "Lien"), ("is-info", "Info"), + ("is-success", "Succès"), ("is-warning", "Attention"), ("is-danger", "Danger"), + ("is-light", "Clair"), ("is-dark", "Sombre"), + ], default="", group="Style Bulma"), + c_select("bulma_taille", "Style Bulma — taille", "class:taille", [ + ("", "Normale"), ("is-small", "Petite"), ("is-medium", "Moyenne"), ("is-large", "Grande"), + ], default="", group="Style Bulma"), + c_checkbox("bulma_arrondi", "Coins bien arrondis (Bulma)", "class:arrondi", "is-rounded", "", group="Style Bulma"), + c_checkbox("bulma_contour", "Contour seulement (Bulma)", "class:contour", "is-outlined", "", group="Style Bulma"), +] diff --git a/screens/widgets/controls/c_slider.py b/screens/widgets/controls/c_slider.py index fffd4e0f..0cc3c934 100644 --- a/screens/widgets/controls/c_slider.py +++ b/screens/widgets/controls/c_slider.py @@ -1,2 +1,11 @@ -def c_slider(key, label, target, unit="px", min=0, max=100, step=1, default=16, group=None): - return {"key": key, "label": label, "type": "slider", "target": target, "unit": unit, "min": min, "max": max, "step": step, "default": default, "group": group} +def c_slider(key, label, target, unit="px", min=0, max=100, step=1, default=16, group=None, no_freeze=False): + return { + "key": key, "label": label, "type": "slider", "target": target, "unit": unit, + "min": min, "max": max, "step": step, "default": default, "group": group, + # no_freeze : n'écrit PAS ce réglage en style au moment de la création + # de l'élément malgré sa valeur par défaut non-vide (voir + # default_style_and_attributes) — utilisé quand un widget porte déjà + # une classe CSS externe (ex. Bulma) qui doit rester visible tant que + # ce réglage n'a pas été explicitement personnalisé. + "no_freeze": no_freeze, + } diff --git a/screens/widgets/default_style_for_widget.py b/screens/widgets/default_style_for_widget.py index 89255f31..d47dabde 100644 --- a/screens/widgets/default_style_for_widget.py +++ b/screens/widgets/default_style_for_widget.py @@ -50,8 +50,8 @@ def default_style_and_attributes(widget_key): if default in (None, ""): continue if ctype == "slider": - if control["key"] in _PER_SIDE_KEYS: - continue # réglage par côté : jamais figé (voir commentaire plus haut) + if control["key"] in _PER_SIDE_KEYS or control.get("no_freeze"): + continue # réglage par côté, ou explicitement non figé (voir c_slider) : jamais figé unit = control.get("unit", "") default = f"{default}{unit}" kind, name = target.split(":", 1) diff --git a/screens/widgets/registry.py b/screens/widgets/registry.py index 757de5c8..7c830a72 100644 --- a/screens/widgets/registry.py +++ b/screens/widgets/registry.py @@ -9,6 +9,7 @@ from .controls.c_align import c_align from .controls.c_select import c_select from .control_groups.text_style_controls_extra import TEXT_STYLE_CONTROLS_EXTRA from .control_groups.data_binding_controls import DATA_BINDING_CONTROLS +from .control_groups.bulma_controls import BULMA_BUTTON_CONTROLS from .font_options import FONT_OPTIONS from ..flow.constants import CONDITION_OPERATORS @@ -31,6 +32,10 @@ WIDGETS = { "titre": { "label": "Titre", "icon": "🔠", "tag": None, "has_content": True, "w": 55, "h": 10, "default_content": "Titre", + # class:"title" — poids de police et espacement Bulma par défaut ; la + # taille reste pilotée par le réglage "Taille du texte" ci-dessous + # comme avant (non affecté), Bulma ne fournissant ici que l'habillage. + "fixed_attrs": {"class": "title"}, "controls": [ c_textarea("content", "Texte — {{champ}} affiche la valeur de ce champ sur la ligne trouvée ci-dessous", "content", default="Titre", group="Contenu"), c_select("niveau", "Importance du titre", "attr:_niveau", @@ -46,14 +51,19 @@ WIDGETS = { "bouton": { "label": "Bouton", "icon": "🔘", "tag": "button", "has_content": True, "w": 30, "h": 8, "default_content": "Valider", + # class:"button" — donne au Bouton l'apparence Bulma par défaut + # (padding, poids de police, transitions...) tant qu'aucune couleur/ + # taille "libre" n'est personnalisée ci-dessous — voir + # BULMA_BUTTON_CONTROLS et _visible_attrs.py. + "fixed_attrs": {"class": "button"}, "controls": [ c_text("content", "Texte du bouton", "content", default="Valider", group="Contenu"), c_color("color", "Couleur du texte", "style:color", "#ffffff", group="Texte"), c_color("background", "Couleur de fond", "style:background-color", "#5b8cff", group="Texte"), - c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=40, default=15, group="Texte"), - c_slider("border-radius", "Arrondi des coins", "style:border-radius", min=0, max=40, default=8, group="Texte"), + c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=40, default=15, group="Texte", no_freeze=True), + c_slider("border-radius", "Arrondi des coins", "style:border-radius", min=0, max=40, default=8, group="Texte", no_freeze=True), c_checkbox("submit", "Valide un formulaire", "attr:type", "submit", "button", group="Contenu"), - ] + TEXT_STYLE_CONTROLS_EXTRA, + ] + BULMA_BUTTON_CONTROLS + TEXT_STYLE_CONTROLS_EXTRA, }, "lien": { "label": "Lien", "icon": "🔗", "tag": "a", "has_content": True, @@ -197,6 +207,13 @@ WIDGETS = { "w": 50, "h": 30, "default_content": "", "controls": [ c_text("content", "Texte", "content", group="Contenu"), + # "Carte (Bulma)" reste optionnel (pas une classe posée par + # défaut) : ce widget sert aussi bien de simple habillage + # invisible (empilement, fond de décor plein écran...) que de + # vraie carte — lui imposer le fond blanc/l'ombre de Bulma par + # défaut casserait ces usages existants. Voir _visible_attrs.py. + c_select("bulma_carte", "Style Bulma", "class:carte", + [("", "Aucun"), ("box", "Carte (fond clair, ombre, coins arrondis)")], default="", group="Style Bulma"), c_color("background", "Couleur de fond", "style:background-color", "#1f2430", group="Texte"), c_slider("border-radius", "Arrondi des coins", "style:border-radius", min=0, max=60, default=10, group="Texte"), c_slider("opacity", "Opacité", "style:opacity", unit="", min=0, max=1, step=0.05, default=1, group="Texte"), diff --git a/templates/play.html b/templates/play.html index ab1f2067..ac51625c 100644 --- a/templates/play.html +++ b/templates/play.html @@ -7,6 +7,7 @@ +