Deux fonctionnalités demandées, développées et corrigées dans cet
échange :
1. "Donnée liée" (Texte/Titre) : le réglage à 2 filtres fixes (toujours
combinés en ET) devient une liste de conditions ILLIMITÉE, avec un
choix ET/OU pour les combiner (screens/widgets/controls/c_clause_list.py,
screens/clause_list_codec.py). Rétrocompatible avec les anciens
éléments (_data_filtre_champ/_data_filtre2_champ), convertis à la
volée à la lecture, sans migration. Après un premier essai à la
présentation trop compacte et technique (retour utilisateur : "pas de
champ technique, pas de notation bizarre {{ }}"), la présentation
finale reprend EXACTEMENT l'ancien style (labels "Champ"/"...est"/
"...cette valeur", même sélecteur de valeur fixe/dynamique/variable
déjà existant, jamais la syntaxe brute), simplement répétée par
condition (templates/partials/clause_row.html), avec un bouton
"+ Ajouter une condition" bien visible et une liste scrollable
(static/style.css, .clauseListWrap). Le même moteur (filter_repeater_
rows.py généralisé) profite aussi au Répéteur de données en interne.
2. Nœud Condition de la Logique de la scène : peut désormais tester une
VARIABLE GLOBALE en plus d'un champ d'objet (cond_source/cond_variable/
cond_variable_chemin — screens/flow/ensure_flow_schema.py), sur la
clause principale ET chaque clause supplémentaire (ET/OU). Évalué côté
CLIENT (templates/play.html, evaluateConditionClause), contre un
nouveau gameData.variables exposé par full_game_payload.py — tenu à
jour par refreshRuntimeData() après toute action qui modifie une
variable, sans changement supplémentaire nécessaire. Le panneau de
condition reste utilisable même sans aucun objet défini dans le jeu
(avant, il disparaissait entièrement).
Vérifié : 153 tests pytest (nouveaux : test_data_binding_clause_list.py,
test_condition_variable.py) + logique JS d'évaluation des conditions
vérifiée isolément avec Node (variable scalaire, objet avec chemin
chaîné, tableau par index, variable introuvable, booléen, rétrocompatibilité
legacy) + rendu des deux pages (éditeur/jeu) vérifié sur le vrai projet
"test" en plus des jeux de test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
88 lines
4.5 KiB
Python
88 lines
4.5 KiB
Python
import json
|
|
|
|
import db
|
|
|
|
from ..widgets.registry import WIDGETS
|
|
from ..widgets.widget_meta import widget_meta
|
|
from ..widgets.default_style_for_widget import default_style_and_attributes
|
|
from ..element_types.list_element_types import list_element_types
|
|
from ..element_types.is_overlay_only import _is_overlay_only_element_type
|
|
|
|
|
|
def add_element(slug, screen_id, widget_key, element_type_id=None, parent_id=None):
|
|
"""parent_id : si fourni, l'élément est posé PHYSIQUEMENT à l'intérieur
|
|
de cet élément parent (qui doit être un conteneur/répéteur/groupe de
|
|
champs) plutôt que directement sur l'écran — sa position/taille ne sert
|
|
alors plus (il suit le flux du parent), mais on garde des valeurs par
|
|
défaut pour rester cohérent si jamais il en sort un jour.
|
|
|
|
element_type_id : si fourni, un SEUL élément est créé ici (ligne plate,
|
|
comme n'importe quel widget), qui garde juste une référence vers cet
|
|
élément de jeu — son contenu (l'arbre de son écran-modèle) n'est PAS
|
|
copié en base : il est rechargé EN DIRECT à chaque affichage (voir
|
|
render_element_html.py), exactement comme un modèle de ligne de
|
|
Répéteur (voir _load_template_tree/_render_repeater). Modifier
|
|
l'élément de jeu dans son propre éditeur met donc à jour tous ses
|
|
exemplaires déjà posés, sur n'importe quel écran, sans avoir à les
|
|
retoucher un par un — au prix de ne plus pouvoir personnaliser
|
|
individuellement le CONTENU d'un exemplaire précis (sa position/taille
|
|
sur l'écran, elles, restent propres à chaque exemplaire, comme
|
|
d'habitude)."""
|
|
widget_key = widget_key if widget_key in WIDGETS else "conteneur"
|
|
if element_type_id:
|
|
types = {t["id"]: t for t in list_element_types(slug)}
|
|
et = types.get(int(element_type_id))
|
|
if et:
|
|
widget_key = et.get("default_widget") or widget_key
|
|
meta = widget_meta(widget_key)
|
|
# Réglages dont la valeur par défaut a un effet visuel voulu dès la
|
|
# création (ex: une image démarre avec "cover" pour se recadrer
|
|
# proprement) — hors couleurs, qui restent volontairement transparentes/
|
|
# sans bordure tant qu'on ne les choisit pas explicitement.
|
|
attributes, style = default_style_and_attributes(widget_key)
|
|
if element_type_id and _is_overlay_only_element_type(slug, int(element_type_id)):
|
|
# Un exemplaire dont le modèle N'EST QU'une "Superposition / boîte
|
|
# de dialogue" (render_element_html.py décide alors, au rendu, de
|
|
# faire porter le masquage à CET EXEMPLAIRE plutôt qu'au modèle) :
|
|
# démarre masqué par défaut, exactement comme une superposition
|
|
# posée directement (voir default_style_for_widget.py) — sinon un
|
|
# dialogue fraîchement déposé couvrirait tout l'écran en jeu dès
|
|
# sa pose, avant même qu'une action ne l'affiche.
|
|
style["display"] = "none"
|
|
content = meta.get("default_content", "")
|
|
width, height = meta.get("w", 30), meta.get("h", 10)
|
|
|
|
conn = db.connect(slug)
|
|
max_z = conn.execute(
|
|
"SELECT MAX(z_index) AS m FROM _screen_elements WHERE screen_id = ?", (screen_id,)
|
|
).fetchone()["m"]
|
|
# Décalage en cascade : sans ça, chaque nouvel élément posé DIRECTEMENT
|
|
# SUR L'ÉCRAN atterrit exactement au même endroit (10%, 10%) que le
|
|
# précédent et le recouvre entièrement — ce qui peut donner l'impression
|
|
# trompeuse qu'un "élément indésirable" vient d'apparaître et d'en
|
|
# cacher un autre (le plus souvent après une confusion entre "Ajouter un
|
|
# élément sur l'écran" et "Ajouter DANS ce conteneur", qui posent tous
|
|
# les deux un nouvel élément mais pas au même endroit dans l'arbre).
|
|
x = y = 10
|
|
if not parent_id:
|
|
top_level_count = conn.execute(
|
|
"SELECT COUNT(*) AS c FROM _screen_elements WHERE screen_id = ? AND parent_id IS NULL",
|
|
(screen_id,),
|
|
).fetchone()["c"]
|
|
offset = (top_level_count % 8) * 4
|
|
x = y = 10 + offset
|
|
conn.execute(
|
|
"""INSERT INTO _screen_elements
|
|
(screen_id, parent_id, widget, element_type_id, content, attributes, x, y, width, height, z_index, style)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
(
|
|
screen_id, parent_id or None, widget_key, element_type_id or None, content, json.dumps(attributes),
|
|
x, y, width, height, (max_z or 0) + 1,
|
|
json.dumps(style),
|
|
),
|
|
)
|
|
element_id = conn.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
|
|
conn.commit()
|
|
conn.close()
|
|
return element_id
|