Phase 5 : position/déplacement d'élément + condition de collision
Ajoute le positionnement absolu (pos_x/pos_y, réutilise left/top en % déjà en place) et relatif (pos_x_relatif/pos_y_relatif, ajoute un delta à la position actuelle plutôt que de l'écraser) comme nouvelles propriétés de l'action "Modifier un élément". Ajoute une nouvelle source de condition "collision" (aux côtés de "objet"/"variable") : deux éléments (cond_element_a/cond_element_b, ALTER TABLE sans contrainte FK, même patron que block_id/ trigger_custom_event_id) dont on compare les rectangles à l'écran via getBoundingClientRect() côté client (elementsOverlap(), dans conditions.js). Pas d'opérateur/valeur à choisir : le chevauchement EST directement le booléen vrai/faux du nœud — le créateur relie le port "Faux" pour "ne se touchent pas", exactement comme pour n'importe quelle autre condition (design plus simple que réinterpréter égal/différent, qui n'a pas de sens pour superieur/inferieur). delete_element.py et flow_nodes_referencing_element.py nettoient désormais aussi les nœuds de collision référençant un élément supprimé (ou l'un de ses descendants), pour rester cohérents avec le nettoyage déjà en place pour trigger_element_id/target_element_id. Combiné à la Phase 3 (minuteur récurrent) et au déplacement au clavier, ça couvre des jeux type casse-briques/Pong/ramasse-objets sans construire un vrai moteur physique (pas de vélocité/accélération/ gravité continues, cadrage volontairement limité). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
92fbfbc9dd
commit
9e263435e6
@@ -14,20 +14,29 @@ def delete_element(slug, element_id):
|
||||
ids = element_descendant_ids(conn, row["screen_id"], element_id)
|
||||
placeholders = ",".join("?" * len(ids))
|
||||
# Un nœud de la Logique de la scène (déclencheur "clic sur cet
|
||||
# élément"/action "Modifier cet élément"...) ou une ancienne action du
|
||||
# système _actions (conservé pour compatibilité) qui référence
|
||||
# l'élément supprimé OU L'UN DE SES DESCENDANTS n'a plus aucun sens une
|
||||
# fois l'élément disparu — et ces colonnes (trigger_element_id/
|
||||
# target_element_id) n'ont volontairement PAS de ON DELETE CASCADE (un
|
||||
# élément ne doit pas pouvoir être supprimé "par erreur" en cascade
|
||||
# depuis un nœud de logique qu'on modifie). Sans ce nettoyage préalable,
|
||||
# PRAGMA foreign_keys=ON (voir db/connection.py) fait échouer la
|
||||
# suppression elle-même avec "FOREIGN KEY constraint failed". Voir
|
||||
# element_delete_impact.py pour prévenir l'utilisateur AVANT qu'il
|
||||
# confirme, plutôt que de supprimer ces nœuds en silence.
|
||||
# élément"/action "Modifier cet élément"/condition de collision — voir
|
||||
# ci-dessous...) ou une ancienne action du système _actions (conservé
|
||||
# pour compatibilité) qui référence l'élément supprimé OU L'UN DE SES
|
||||
# DESCENDANTS n'a plus aucun sens une fois l'élément disparu — et ces
|
||||
# colonnes (trigger_element_id/target_element_id) n'ont volontairement
|
||||
# PAS de ON DELETE CASCADE (un élément ne doit pas pouvoir être
|
||||
# supprimé "par erreur" en cascade depuis un nœud de logique qu'on
|
||||
# modifie). Sans ce nettoyage préalable, PRAGMA foreign_keys=ON (voir
|
||||
# db/connection.py) fait échouer la suppression elle-même avec
|
||||
# "FOREIGN KEY constraint failed". Voir element_delete_impact.py pour
|
||||
# prévenir l'utilisateur AVANT qu'il confirme, plutôt que de supprimer
|
||||
# ces nœuds en silence.
|
||||
#
|
||||
# cond_element_a/cond_element_b (Phase 5, condition de collision) : ni
|
||||
# contrainte FK ni ON DELETE CASCADE (voir ensure_flow_schema.py), mais
|
||||
# un nœud qui compare le rectangle de CET élément à un autre n'a pas
|
||||
# plus de sens que si c'était trigger_element_id/target_element_id —
|
||||
# supprimé ici pour la même raison, pas parce que la base l'exigerait.
|
||||
conn.execute(
|
||||
f"DELETE FROM _flow_nodes WHERE trigger_element_id IN ({placeholders}) OR target_element_id IN ({placeholders})",
|
||||
ids + ids,
|
||||
f"""DELETE FROM _flow_nodes WHERE trigger_element_id IN ({placeholders})
|
||||
OR target_element_id IN ({placeholders})
|
||||
OR cond_element_a IN ({placeholders}) OR cond_element_b IN ({placeholders})""",
|
||||
ids + ids + ids + ids,
|
||||
)
|
||||
conn.execute(f"DELETE FROM _actions WHERE target_element_id IN ({placeholders})", ids)
|
||||
conn.execute("DELETE FROM _screen_elements WHERE id = ?", (element_id,))
|
||||
|
||||
@@ -6,12 +6,12 @@ from .element_descendant_ids import element_descendant_ids
|
||||
|
||||
def flow_nodes_referencing_element(slug, element_id):
|
||||
"""Nombre de nœuds de la Logique de la scène (déclencheur "clic sur cet
|
||||
élément", action "Modifier cet élément"/"Activer cet onglet"...) qui
|
||||
référencent cet élément OU L'UN DE SES DESCENDANTS — donc qui seraient
|
||||
supprimés SI on supprimait cet élément (voir delete_element.py). Utilisé
|
||||
pour prévenir l'utilisateur AVANT qu'il confirme la suppression (voir
|
||||
la modale de confirmation dans screen_edit.html), plutôt que de les
|
||||
supprimer en silence."""
|
||||
élément", action "Modifier cet élément"/"Activer cet onglet", condition
|
||||
de collision — Phase 5...) qui référencent cet élément OU L'UN DE SES
|
||||
DESCENDANTS — donc qui seraient supprimés SI on supprimait cet élément
|
||||
(voir delete_element.py). Utilisé pour prévenir l'utilisateur AVANT
|
||||
qu'il confirme la suppression (voir la modale de confirmation dans
|
||||
screen_edit.html), plutôt que de les supprimer en silence."""
|
||||
ensure_flow_schema(slug)
|
||||
conn = db.connect(slug)
|
||||
row = conn.execute("SELECT screen_id FROM _screen_elements WHERE id = ?", (element_id,)).fetchone()
|
||||
@@ -21,8 +21,10 @@ def flow_nodes_referencing_element(slug, element_id):
|
||||
ids = element_descendant_ids(conn, row["screen_id"], element_id)
|
||||
placeholders = ",".join("?" * len(ids))
|
||||
count = conn.execute(
|
||||
f"SELECT COUNT(*) AS c FROM _flow_nodes WHERE trigger_element_id IN ({placeholders}) OR target_element_id IN ({placeholders})",
|
||||
ids + ids,
|
||||
f"""SELECT COUNT(*) AS c FROM _flow_nodes WHERE trigger_element_id IN ({placeholders})
|
||||
OR target_element_id IN ({placeholders})
|
||||
OR cond_element_a IN ({placeholders}) OR cond_element_b IN ({placeholders})""",
|
||||
ids + ids + ids + ids,
|
||||
).fetchone()["c"]
|
||||
conn.close()
|
||||
return count
|
||||
|
||||
@@ -118,4 +118,9 @@ FLOW_NODE_FIELDS = {
|
||||
# Phase 3 — déclencheur clavier ("clavier") et minuteur récurrent
|
||||
# ("minuteur"), voir TRIGGER_EVENTS ci-dessus.
|
||||
"trigger_key", "trigger_interval_ms",
|
||||
# Phase 5 — condition de collision (cond_source="collision", aux
|
||||
# côtés de "objet"/"variable") : les deux éléments dont on compare les
|
||||
# rectangles — voir evaluateConditionClause() dans
|
||||
# static/js/play/conditions.js.
|
||||
"cond_element_a", "cond_element_b",
|
||||
}
|
||||
|
||||
@@ -105,5 +105,16 @@ def ensure_flow_schema(slug):
|
||||
conn.execute("ALTER TABLE _flow_nodes ADD COLUMN trigger_key TEXT")
|
||||
if "trigger_interval_ms" not in cols:
|
||||
conn.execute("ALTER TABLE _flow_nodes ADD COLUMN trigger_interval_ms INTEGER")
|
||||
if "cond_element_a" not in cols:
|
||||
# Phase 5 — condition de collision (cond_source="collision") : les
|
||||
# deux éléments dont on compare les rectangles à l'écran. Pas de
|
||||
# contrainte FK — même patron que block_id/trigger_custom_event_id
|
||||
# ci-dessus (colonne ajoutée par ALTER TABLE, jamais contrainte
|
||||
# dans ce fichier) : screens/elements/delete_element.py nettoie
|
||||
# lui-même les nœuds qui référencent un élément supprimé (voir ce
|
||||
# fichier), une contrainte ferait double emploi.
|
||||
conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_element_a INTEGER")
|
||||
if "cond_element_b" not in cols:
|
||||
conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_element_b INTEGER")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -28,6 +28,17 @@ ELEMENT_ACTION_PROPERTIES = [
|
||||
# play.html (pointer-events:none empêche aussi le déclencheur "Au clic"
|
||||
# de se redéclencher tant que l'élément est désactivé).
|
||||
("desactive", "Désactivé — grisé, ignore les clics"),
|
||||
# Phase 5 (moteur) — déplacement d'élément : "absolu" fixe la position
|
||||
# (en % du cadre, comme Largeur/Hauteur ci-dessus — les éléments sont
|
||||
# déjà positionnés ainsi, voir filters/element_style_filter.py) ;
|
||||
# "relatif" AJOUTE la valeur à la position actuelle plutôt que de
|
||||
# l'écraser (voir applyElementProperty() dans static/js/play/actions.js)
|
||||
# — combiné à un déclencheur clavier (Phase 3), ça permet un
|
||||
# personnage/objet qui se déplace pas à pas à chaque appui de touche.
|
||||
("pos_x", "Position horizontale en % (absolue)"),
|
||||
("pos_y", "Position verticale en % (absolue)"),
|
||||
("pos_x_relatif", "Déplacer horizontalement de... % (relatif)"),
|
||||
("pos_y_relatif", "Déplacer verticalement de... % (relatif)"),
|
||||
]
|
||||
ELEMENT_ACTION_PROPERTY_LABELS = dict(ELEMENT_ACTION_PROPERTIES)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user