Phase 5 : position/déplacement d'élément + condition de collision
Build and deploy / test-python (push) Successful in 1m29s
Build and deploy / test-js (push) Successful in 7s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

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:
william
2026-08-30 17:34:51 +02:00
co-authored by Claude Sonnet 5
parent 92fbfbc9dd
commit 9e263435e6
10 changed files with 271 additions and 41 deletions
+22 -13
View File
@@ -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
+5
View File
@@ -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",
}
+11
View File
@@ -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)