diff --git a/screens/elements/save_element_controls.py b/screens/elements/save_element_controls.py index a5c66542..2b16837b 100644 --- a/screens/elements/save_element_controls.py +++ b/screens/elements/save_element_controls.py @@ -3,6 +3,7 @@ import json import db from ..widgets.widget_meta import widget_meta +from ..widgets.layout_capable_widgets import LAYOUT_CAPABLE_WIDGETS from .get_element import get_element @@ -61,6 +62,24 @@ def save_element_controls(slug, element_id, form): style.pop(k, None) else: style[k] = v + if ( + control["key"] == "visibilite" and chosen == "visible" + and el["widget"] not in LAYOUT_CAPABLE_WIDGETS + ): + # "Visible" ne touche volontairement pas "display" (voir + # visibility_control.py) : sur un widget qui A un + # réglage "Disposition interne" (LAYOUT_CONTROLS, + # traité avant celui-ci sur le MÊME enregistrement, + # voir widget_meta.py), ce contrôle réaffirme lui-même + # un "display" non-"none" (flex/etc), donc "Visible" n'a + # rien à faire. Mais un widget SANS ce contrôle (ex. + # "superposition") n'a PERSONNE d'autre pour l'effacer : + # un "display:none" laissé par un "Masqué" précédent (ou + # par le réglage par défaut à la création, voir + # default_style_for_widget.py) restait donc bloqué + # masqué pour toujours, quel que soit le nombre de fois + # où "Visible" était ensuite choisi. + style.pop("display", None) continue if ctype == "scale": diff --git a/screens/rendering/render_overlay.py b/screens/rendering/render_overlay.py index d5a55814..a0f1342a 100644 --- a/screens/rendering/render_overlay.py +++ b/screens/rendering/render_overlay.py @@ -7,60 +7,61 @@ from .render_children import _render_children def _render_overlay(el, meta, slug, children_map=None, ctx=None, parent_flex_direction=None): """3.4 (Confort) — overlay/modale réutilisable : une boîte de dialogue - prête à l'emploi, par-dessus TOUT le reste de l'écran, fermeture - manuelle uniquement (aucun clic-en-dehors-pour-fermer volontairement, - conformément au manque documenté). Contrairement aux autres widgets, sa - position ne dépend PAS de x/y/width/height (glissé-déposé sur le - canevas) : `position:fixed; inset:0` la fait toujours couvrir tout - l'écran, quel que soit l'endroit où elle a été posée dans l'éditeur — - seul un voile semi-transparent + une boîte centrée, contenant les - éléments posés à l'intérieur (comme un conteneur normal). + prête à l'emploi, par-dessus TOUT le reste de l'écran EN MODE JOUABLE + UNIQUEMENT (`position:fixed; inset:0` + voile semi-transparent, ignore + x/y/width/height) — fermeture manuelle uniquement (aucun + clic-en-dehors-pour-fermer volontairement, conformément au manque + documenté). - Ouverture/fermeture : PAS de mécanisme dédié — elle réutilise l'action - existante "Modifier un élément → Visibilité" (masquer/rendre visible), - exactement comme n'importe quel autre élément. C'est pour ça qu'on - fusionne le style de positionnement fixe ci-dessous AVEC le style normal - de l'élément (_style_string) : si l'élément est réglé "Masqué" dans ses - propriétés, ou si une action "Modifier un élément" le cache plus tard, - ce masquage continue de fonctionner normalement. + Dans l'ÉDITEUR (ctx sans "_forge_play_mode" — posé uniquement en mode + jouable, voir list_elements.py), ce widget se comporte comme un + CONTENEUR NORMAL : position/taille selon x/y/width/height comme + n'importe quel widget, respect normal de son réglage "Visibilité" + (masqué = invisible dans l'éditeur aussi, comme tout autre widget), + AUCUN voile plein écran. Deux essais précédents corrigés à partir des + retours utilisateur : + - Fond assombri visible pendant l'édition ("je veux que rien ne soit + assombri, l'assombrissement ne se fait que quand la scène est + jouée") : le voile (background:rgba(...)) ne fait donc plus partie + du style de base, il n'est ajouté qu'en mode jouable. + - Visibilité forcée en permanence dans l'éditeur ("je veux avoir la + main sur la visibilité, sinon elle s'affiche toujours sur la scène + et court-circuite ma logique") : le forçage display:flex a donc été + retiré — l'éditeur respecte de nouveau fidèlement le réglage + "Visibilité" (masqué par défaut à la création, pour ne plus couvrir + tout l'écran EN JEU dès qu'on la pose — sans plus aucun rapport avec + l'éditeur, qui ne couvre plus jamais rien). + + Ouverture/fermeture (en JEU) : PAS de mécanisme dédié — elle réutilise + l'action existante "Modifier un élément → Visibilité" (masquer/rendre + visible), exactement comme n'importe quel autre élément. Classes Bulma ("modal is-active" / "box") posées en PLUS du style - inline existant, jamais à sa place : tout le positionnement/masquage - critique (position:fixed, z-index, display) reste en inline, qui gagne - toujours sur une règle de classe — si Bulma (chargé depuis un CDN, voir - play.html) ne se charge pas (hors-ligne), la boîte de dialogue continue - de fonctionner exactement pareil, ces classes n'ajoutant qu'un habillage - visuel (ombre, base de police) qui se dégrade sans casser quoi que ce - soit. Pas de ".modal-background" séparé : le voile semi-transparent est - déjà posé en inline sur cette même balise (background:rgba(...)) — un - second calque tout aussi transparent par-dessus n'ajouterait rien.""" + inline, jamais à sa place, et UNIQUEMENT en mode jouable (voir + ci-dessus) : tout le positionnement/masquage critique (position:fixed, + z-index, display) reste en inline, qui gagne toujours sur une règle de + classe — si Bulma (chargé depuis un CDN, voir play.html) ne se charge + pas (hors-ligne), la boîte de dialogue continue de fonctionner + exactement pareil. Pas de ".modal-background" séparé : le voile + semi-transparent est déjà posé en inline sur cette même balise + (background:rgba(...)) — un second calque tout aussi transparent + par-dessus n'ajouterait rien.""" + is_play_mode = bool(ctx and ctx.get("_forge_play_mode")) attrs_raw = el.get("attributes") or {} box_color = attrs_raw.get("_couleur_boite") or "#1f2430" radius = attrs_raw.get("_arrondi") or "12" attrs = _visible_attrs(el, meta, ctx) - base_overlay_style = ( - "position:fixed; inset:0; z-index:9999; display:flex; " - "align-items:center; justify-content:center; background:rgba(0,0,0,0.6);" - ) - style = base_overlay_style + " " + _style_string(el, parent_flex_direction=parent_flex_direction) - if not (ctx and ctx.get("_forge_play_mode")): - # Dans l'ÉDITEUR (ctx sans _forge_play_mode — voir list_elements.py, - # posé uniquement en mode JOUABLE), ce widget doit rester visible - # quel que soit son réglage de visibilité statique — "Masqué" par - # défaut à SA création (default_style_for_widget.py), pour ne pas - # couvrir tout l'écran dès qu'on le pose. Sans ce forçage, la boîte - # de dialogue reste display:none dans le canevas de l'éditeur, donc - # invisible dès sa création : impossible d'y voir/positionner son - # contenu tant qu'on n'a pas basculé "Visibilité" sur "Visible" à la - # main (puis pensé à la remettre sur "Masqué" avant de tester). Le - # "display:flex;" ajouté ICI, en dernier dans la chaîne de style, - # gagne sur le "display:none" éventuellement posé plus tôt par - # _style_string (CSS : même propriété déclarée deux fois -> la - # dernière l'emporte). Seul le mode JOUABLE respecte réellement ce - # réglage (ou une action "Modifier un élément → Visibilité" qui le - # change en cours de partie).""" - style += " display:flex;" + if is_play_mode: + base_overlay_style = ( + "position:fixed; inset:0; z-index:9999; display:flex; " + "align-items:center; justify-content:center; background:rgba(0,0,0,0.6);" + ) + style = base_overlay_style + " " + _style_string(el, parent_flex_direction=parent_flex_direction) + wrapper_class = "modal is-active" + else: + style = _style_string(el, parent_flex_direction=parent_flex_direction) + wrapper_class = "" attr_parts = [f'style="{html_lib.escape(style)}"'] for k, v in attrs.items(): @@ -91,8 +92,9 @@ def _render_overlay(el, meta, slug, children_map=None, ctx=None, parent_flex_dir "color:#e8eaf0; padding:24px; width:100%; max-width:min(560px, 90%); max-height:90%; overflow:auto; box-sizing:border-box;" ) child_html = _render_children(el, slug, children_map, ctx) + wrapper_class_attr = f'class="{wrapper_class}" ' if wrapper_class else "" return ( - f'