Files
Forge-Engine/screens/rendering/render_element_html.py
T
william 0e5db97180 Let Texte/Titre elements bind directly to a single row of another object
Adds a "Donnée liée" settings group to Texte/Titre: pick an object, optionally match it to the current game state via 1-2 filter conditions (same engine as the data-repeater's filter, including {{Objet.champ}} cross-references), and use {{champ}} in the text content to show a field from that one matching row. Unlike the data repeater — built for showing a list of rows — this covers displaying a single computed value (e.g. the objective of the level matching the game's current parcours/level) without wrapping it in a repeater.
2026-08-24 11:02:54 +02:00

84 lines
4.2 KiB
Python

import html as html_lib
from ..widgets.widget_meta import widget_meta
from .visible_attrs import _visible_attrs
from .style_string import _style_string
from .attr_string import _attr_string
from .apply_ctx import _apply_ctx
from .render_children import _render_children
from .render_table import _render_table
from .render_select import _render_select
from .render_fieldset import _render_fieldset
from .render_checkbox_or_radio import _render_checkbox_or_radio
from .render_repeater import _render_repeater
from .render_jauge import _render_jauge
from .render_overlay import _render_overlay
from .render_onglets import _render_onglets
from .resolve_bound_row import _resolve_bound_row_ctx
def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_direction=None):
"""Construit le HTML réel (balise + attributs + style + contenu, y
compris les éléments physiquement posés à l'intérieur pour un
conteneur/répéteur/groupe de champs) — utilisé par l'éditeur (canevas)
et le mode jouable. N'écrit jamais rien sur disque : uniquement du HTML
généré à la volée à partir de ce qui est stocké en base. `slug` est
nécessaire pour le Répéteur de données (il doit lire les données du jeu
au moment de l'affichage). `ctx` porte les valeurs {{champ}} de la ligne
en cours quand cet élément est descendant d'un répéteur. `parent_flex_direction`
est la disposition interne (voir _parent_flex_direction) du conteneur qui
pose physiquement cet élément, s'il y en a un — elle influence la largeur
par défaut de cet élément (voir _style_string)."""
meta = widget_meta(el.get("widget"))
bound_ctx = _resolve_bound_row_ctx(slug, el.get("attributes") or {})
if bound_ctx:
ctx = {**(ctx or {}), **bound_ctx}
special = meta.get("special_render")
if special == "table":
return _render_table(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "select_options":
return _render_select(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "fieldset_legend":
return _render_fieldset(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "checkbox_label":
return _render_checkbox_or_radio(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "repeater":
return _render_repeater(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "jauge":
return _render_jauge(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "overlay":
return _render_overlay(el, meta, slug, children_map, ctx, parent_flex_direction)
if special == "onglets":
return _render_onglets(el, meta, slug, children_map, ctx, parent_flex_direction)
tag = meta.get("tag")
if tag is None: # widget à balise dynamique (ex: "titre" -> h1/h2/h3)
niveau = (el.get("attributes") or {}).get("_niveau", "2")
tag = f"h{niveau}"
attrs = _visible_attrs(el, meta, ctx)
style = _style_string(el, parent_flex_direction=parent_flex_direction, widget_key=el.get("widget"))
# 3.1 (Confort) — interactions au survol : un texte de remplacement
# optionnel, affiché à la place du contenu normal quand le joueur
# survole cet élément à la souris (voir bindHoverTexts() dans
# templates/play.html). Absent par défaut => aucun changement, donc
# aucune régression sur les éléments déjà créés.
hover_text = (el.get("attributes") or {}).get("_survol_texte")
if hover_text:
attrs["data-hover-text"] = _apply_ctx(hover_text, ctx)
if meta.get("self_closing"):
return f"<{tag} {_attr_string(attrs, style)}>"
content = _apply_ctx(el.get("content") or "", ctx)
child_html = _render_children(el, slug, children_map, ctx)
if tag in ("ul", "ol"):
items = [line.strip() for line in content.split("\n") if line.strip()]
inner = "".join(f"<li>{html_lib.escape(item)}</li>" for item in items) + child_html
return f"<{tag} {_attr_string(attrs, style)}>{inner}</{tag}>"
safe_content = html_lib.escape(content).replace("\n", "<br>") if content else ""
return f"<{tag} {_attr_string(attrs, style)}>{safe_content}{child_html}</{tag}>"