Retire la collision sur les images de fond + les blocs de logique/timeline de l'éditeur 2D
Deux retours utilisateur sur l'éditeur de scène 2D : - Le panneau "🧱 Collision" (réglages + aperçu visuel sur le canevas) n'apparaît plus pour un objet kind="fond" : jamais un obstacle ni une cible de collision possible (déjà exclu de forgeSolidObstacles côté jeu), ce panneau n'aurait donc aucun effet. - Retire les onglets "🧩 Blocs de logique" et "🎬 Timeline d'animation" de CET éditeur (l'éditeur document, templates/screen_edit.html, les garde tel quel) — en prévision d'un éditeur dédié aux scènes 2D (collision/périmètre -> action), pas encore construit. switchBuilderTab()/ toggleDashCreate() (génériques, nécessaires à l'onglet "Événements" restant) extraites dans un nouveau builder-tabs.js partagé par les deux éditeurs, pour ne plus avoir à charger flow-editor.js/tabs-and-blocks.js/ animation-timeline.js (propres aux blocs/timeline) dans l'éditeur 2D. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a231bcf2fa
commit
dfaf85fac1
@@ -0,0 +1,26 @@
|
|||||||
|
// ---------- Onglets du centre (Écran/Logique/Timeline/Événements pour
|
||||||
|
// l'éditeur document ; Scène/Événements pour l'éditeur de scène 2D) — un
|
||||||
|
// seul visible à la fois, chacun occupant tout l'espace disponible.
|
||||||
|
// Extrait de tabs-and-blocks.js (voir ce fichier) : ces deux fonctions
|
||||||
|
// sont GÉNÉRIQUES (aucune dépendance aux blocs de logique), donc chargées
|
||||||
|
// par LES DEUX éditeurs — screen_edit.html (blocs/timeline conservés) ET
|
||||||
|
// scene_edit.html (blocs/timeline retirés, voir le plan "Éditeur de
|
||||||
|
// collision" — ce dernier n'a donc plus besoin de charger
|
||||||
|
// tabs-and-blocks.js/flow-editor.js/animation-timeline.js du tout). ----------
|
||||||
|
function switchBuilderTab(tab) {
|
||||||
|
document.querySelectorAll('.builderTabBtn').forEach(function (btn) {
|
||||||
|
btn.classList.toggle('active', btn.dataset.tab === tab);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.builderTabPanel').forEach(function (panel) {
|
||||||
|
panel.classList.toggle('active', panel.dataset.tabPanel === tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDashCreate(tab) {
|
||||||
|
var bar = document.getElementById('dashCreate-' + tab);
|
||||||
|
bar.style.display = bar.style.display === 'none' ? 'flex' : 'none';
|
||||||
|
if (bar.style.display === 'flex') {
|
||||||
|
var firstInput = bar.querySelector('input, select');
|
||||||
|
if (firstInput) firstInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,7 @@
|
|||||||
// ---------- Onglets du centre : Écran / Logique de la scène / Timeline
|
// switchBuilderTab()/toggleDashCreate() ont déménagé dans builder-tabs.js
|
||||||
// d'animation — un seul visible à la fois, chacun occupant tout l'espace
|
// (générique, chargé par les DEUX éditeurs — voir ce fichier) : tout ce
|
||||||
// disponible (remplace l'ancien panneau du bas rétractable/redimensionnable
|
// qui reste ici est propre aux blocs de logique/panneaux flottants,
|
||||||
// à la souris : un onglet "prend toute la place" par nature, et en changer
|
// jamais chargé par l'éditeur de scène 2D (scene_edit.html).
|
||||||
// équivaut à "fermer" celui qu'on quitte). ----------
|
|
||||||
function switchBuilderTab(tab) {
|
|
||||||
document.querySelectorAll('.builderTabBtn').forEach(function (btn) {
|
|
||||||
btn.classList.toggle('active', btn.dataset.tab === tab);
|
|
||||||
});
|
|
||||||
document.querySelectorAll('.builderTabPanel').forEach(function (panel) {
|
|
||||||
panel.classList.toggle('active', panel.dataset.tabPanel === tab);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDashCreate(tab) {
|
|
||||||
var bar = document.getElementById('dashCreate-' + tab);
|
|
||||||
bar.style.display = bar.style.display === 'none' ? 'flex' : 'none';
|
|
||||||
if (bar.style.display === 'flex') {
|
|
||||||
var firstInput = bar.querySelector('input, select');
|
|
||||||
if (firstInput) firstInput.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleBlockEditRow(blockId) {
|
function toggleBlockEditRow(blockId) {
|
||||||
document.getElementById('blockEditRow' + blockId).classList.toggle('hidden');
|
document.getElementById('blockEditRow' + blockId).classList.toggle('hidden');
|
||||||
|
|||||||
+17
-453
@@ -19,11 +19,11 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Fondations multi-éditeurs — éditeur de scène 2D (jeux "jeu_2d", voir
|
<!-- Fondations multi-éditeurs — éditeur de scène 2D (jeux "jeu_2d", voir
|
||||||
db/games/get_game_type.py). Réutilise TEL QUEL le moteur de logique de
|
db/games/get_game_type.py). Le graphe de logique générique (blocs de
|
||||||
flow partagé (mêmes JS : flow-editor.js/tabs-and-blocks.js/
|
logique/Timeline d'animation, voir templates/screen_edit.html, gardé
|
||||||
animation-timeline.js) — seule la palette de nœuds et le panneau
|
tel quel pour l'éditeur document) a été retiré de CET éditeur à la
|
||||||
gauche/canevas diffèrent de templates/screen_edit.html. Voir le plan
|
demande de l'utilisateur, en prévision d'un éditeur dédié aux scènes
|
||||||
"Fondations d'une plateforme multi-éditeurs". -->
|
2D (collision/périmètre -> action), pas encore construit. -->
|
||||||
<p class="hint" style="background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:10px 14px; margin:10px 0;">
|
<p class="hint" style="background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:10px 14px; margin:10px 0;">
|
||||||
🎮 Jeu 2D — scène à taille fixe ({{ screen.scene_width }}×{{ screen.scene_height }} px).
|
🎮 Jeu 2D — scène à taille fixe ({{ screen.scene_width }}×{{ screen.scene_height }} px).
|
||||||
</p>
|
</p>
|
||||||
@@ -93,8 +93,6 @@
|
|||||||
<div class="builderCanvasArea">
|
<div class="builderCanvasArea">
|
||||||
<div class="builderTabs" id="builderTabs">
|
<div class="builderTabs" id="builderTabs">
|
||||||
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">🖥️ Scène</button>
|
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">🖥️ Scène</button>
|
||||||
<button type="button" class="builderTabBtn" data-tab="blocks" onclick="switchBuilderTab('blocks')">🧩 Blocs de logique</button>
|
|
||||||
<button type="button" class="builderTabBtn" data-tab="anim" onclick="switchBuilderTab('anim')">🎬 Timeline d'animation</button>
|
|
||||||
<button type="button" class="builderTabBtn" data-tab="events" onclick="switchBuilderTab('events')">📣 Événements</button>
|
<button type="button" class="builderTabBtn" data-tab="events" onclick="switchBuilderTab('events')">📣 Événements</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,11 +105,12 @@
|
|||||||
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
|
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
|
||||||
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
|
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
|
||||||
<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, {{ o.id }})"></div>
|
<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, {{ o.id }})"></div>
|
||||||
{% if o.id == selected_id %}
|
{% if o.id == selected_id and o.kind != 'fond' %}
|
||||||
<!-- Boîte de collision (voir screens/rendering/collision_settings.py) :
|
<!-- Boîte de collision (voir screens/rendering/collision_settings.py) :
|
||||||
aperçu visuel + réglable directement au glisser — seulement pour
|
aperçu visuel + réglable directement au glisser — seulement pour
|
||||||
l'objet SÉLECTIONNÉ (celui dont le panneau "🧱 Collision" est ouvert),
|
l'objet SÉLECTIONNÉ (celui dont le panneau "🧱 Collision" est ouvert),
|
||||||
voir initCollisionBoxOverlay() dans scene-editor.js. -->
|
voir initCollisionBoxOverlay() dans scene-editor.js. JAMAIS pour un
|
||||||
|
"fond" (voir la même garde sur le panneau "🧱 Collision" plus bas). -->
|
||||||
<div class="collisionBoxOverlay {{ 'is-circle' if collision_settings.shape == 'cercle' else '' }} {{ 'is-disabled' if not collision_settings.enabled else '' }}"
|
<div class="collisionBoxOverlay {{ 'is-circle' if collision_settings.shape == 'cercle' else '' }} {{ 'is-disabled' if not collision_settings.enabled else '' }}"
|
||||||
id="collisionBoxOverlay"
|
id="collisionBoxOverlay"
|
||||||
style="left:{{ (o.width - collision_settings.width) / 2 + collision_settings.offset_x }}px; top:{{ (o.height - collision_settings.height) / 2 + collision_settings.offset_y }}px; width:{{ collision_settings.width }}px; height:{{ collision_settings.height }}px;"
|
style="left:{{ (o.width - collision_settings.width) / 2 + collision_settings.offset_x }}px; top:{{ (o.height - collision_settings.height) / 2 + collision_settings.offset_y }}px; width:{{ collision_settings.width }}px; height:{{ collision_settings.height }}px;"
|
||||||
@@ -126,445 +125,6 @@
|
|||||||
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner. Positions en pixels (scène à taille fixe).</p>
|
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner. Positions en pixels (scène à taille fixe).</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="builderTabPanel" id="blocksTabPanel" data-tab-panel="blocks">
|
|
||||||
<div class="dashPanelHeader">
|
|
||||||
<h2>Blocs de logique</h2>
|
|
||||||
<button type="button" class="button is-small primary" onclick="toggleDashCreate('blocks')">+ Créer un bloc</button>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="{{ url_for('flow_block_new', slug=game.slug, screen_id=screen.id) }}" class="dashCreateBar" id="dashCreate-blocks" style="display:none;">
|
|
||||||
<input type="text" name="name" placeholder="Nom du bloc (ex. Ouvrir la porte)" required autocomplete="off">
|
|
||||||
<input type="text" name="description" placeholder="Description (optionnel)">
|
|
||||||
<button type="submit" class="primary">Créer</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% if blocks_view %}
|
|
||||||
{% for b in blocks_view %}
|
|
||||||
<form method="post" action="{{ url_for('flow_block_edit', slug=game.slug, screen_id=screen.id, block_id=b.id) }}" id="blockEditForm{{ b.id }}"></form>
|
|
||||||
{% endfor %}
|
|
||||||
<div class="fieldsTableWrap">
|
|
||||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
|
||||||
<thead><tr><th>Nom</th><th>Description</th><th>Objets concernés</th><th>Nœuds</th><th></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for b in blocks_view %}
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{ b.name }}</strong></td>
|
|
||||||
<td>{{ b.description|truncatesentences(3) }}</td>
|
|
||||||
<td>{% if b.element_labels %}{{ b.element_labels|join(', ') }}{% else %}<span class="hint">—</span>{% endif %}</td>
|
|
||||||
<td>{{ b.node_count }}</td>
|
|
||||||
<td class="fieldsTableActions">
|
|
||||||
<button type="button" class="button is-small is-primary" onclick="openLogicBlockPanel({{ b.id }})" title="Ouvrir le graphe de ce bloc">🧩 Ouvrir</button>
|
|
||||||
<button type="button" class="button is-small" onclick="toggleBlockEditRow({{ b.id }})" title="Modifier le nom/la description">✏️</button>
|
|
||||||
<form method="post" action="{{ url_for('flow_block_delete', slug=game.slug, screen_id=screen.id, block_id=b.id) }}"
|
|
||||||
onsubmit="return confirm('Supprimer le bloc « {{ b.name }} » ? Cela supprime aussi ses {{ b.node_count }} nœud(s) et leurs connexions.');" style="display:inline;">
|
|
||||||
<button type="submit" class="button is-small is-danger" title="Supprimer">🗑️</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="hidden" id="blockEditRow{{ b.id }}">
|
|
||||||
<td colspan="5">
|
|
||||||
<div class="dashCreateBar" style="display:flex;">
|
|
||||||
<input type="text" form="blockEditForm{{ b.id }}" name="name" value="{{ b.name }}" required>
|
|
||||||
<input type="text" form="blockEditForm{{ b.id }}" name="description" value="{{ b.description or '' }}" placeholder="Description">
|
|
||||||
<button type="submit" form="blockEditForm{{ b.id }}" class="primary">Enregistrer</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="hint">Aucun bloc pour l'instant — crée le premier ci-dessus, puis ouvre-le pour poser des déclencheurs/conditions/actions.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="floatPanel floatPanel--center hidden" id="logicBlockPanel">
|
|
||||||
<div class="floatPanelHeader" id="logicBlockPanelHeader">
|
|
||||||
<span class="floatPanelTitle">⋮⋮ <span id="logicBlockPanelTitle">Bloc</span></span>
|
|
||||||
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
|
|
||||||
<button type="button" class="button is-small" onclick="closeLogicBlockPanel()">✕</button>
|
|
||||||
</div>
|
|
||||||
<div class="floatPanelBody logicBlockPanelBody">
|
|
||||||
<div class="flowToolbar">
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('trigger')">➕ Déclencheur</button>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('condition')">➕ Condition</button>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('action')">➕ Action</button>
|
|
||||||
<span class="hint">Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nodeForm" class="nodeForm" style="display:none;">
|
|
||||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
|
||||||
<strong id="nodeFormTitle">Nouveau nœud</strong>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="closeNodeForm()">✕</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nodeFormTrigger" style="display:none;">
|
|
||||||
<label>Événement<select id="nf-trigger-event" onchange="toggleFlowTriggerFields()">
|
|
||||||
{% for key, label in trigger_events %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<label id="nf-trigger-element-row" style="display:none;">Objet<select id="nf-trigger-element">
|
|
||||||
{% for o in objects %}
|
|
||||||
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<p class="hint" id="nf-trigger-repeater-hint" style="display:none;"></p>
|
|
||||||
<p class="hint" id="nf-trigger-affichage-hint" style="display:none;"></p>
|
|
||||||
<div id="nf-trigger-event-row" style="display:none;">
|
|
||||||
<label>Événement personnalisé<select id="nf-trigger-custom-event">
|
|
||||||
{% for ev in custom_events %}
|
|
||||||
<option value="{{ ev.id }}">{{ ev.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
{% if not custom_events %}
|
|
||||||
<p class="hint">Aucun événement pour l'instant — <a href="javascript:void(0)" onclick="switchBuilderTab('events'); return false;">crées-en un dans l'onglet Événements</a>.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div id="nf-trigger-key-row" style="display:none;">
|
|
||||||
<label>Touche à surveiller
|
|
||||||
<input type="text" id="nf-trigger-key-display" readonly placeholder="Clique ici puis appuie sur une touche" onkeydown="captureFlowTriggerKey(event)">
|
|
||||||
</label>
|
|
||||||
<input type="hidden" id="nf-trigger-key">
|
|
||||||
</div>
|
|
||||||
<div id="nf-trigger-interval-row" style="display:none;">
|
|
||||||
<label>Toutes les... millisecondes
|
|
||||||
<input type="number" id="nf-trigger-interval" min="50" step="50" value="1000" placeholder="ex. 1000">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nodeFormCondition" style="display:none;">
|
|
||||||
<label>Source<select id="nf-cond-source" onchange="onFlowConditionSourceChange()">
|
|
||||||
<option value="objet">Champ d'un objet de données</option>
|
|
||||||
<option value="variable">Variable globale</option>
|
|
||||||
<option value="collision">Collision entre deux objets de scène</option>
|
|
||||||
</select></label>
|
|
||||||
|
|
||||||
<div id="nf-cond-objet-fields" {% if not definitions %}style="display:none;"{% endif %}>
|
|
||||||
<label>Objet de données<select id="nf-cond-definition" onchange="onFlowDefinitionChange('cond')">
|
|
||||||
{% for d in definitions %}
|
|
||||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<label>Ligne concernée<select id="nf-cond-row"></select></label>
|
|
||||||
<label>Champ<select id="nf-cond-field" onchange="onFlowFieldChange()"></select></label>
|
|
||||||
</div>
|
|
||||||
{% if not definitions %}
|
|
||||||
<p class="hint" id="nf-cond-no-objet-hint">Aucun objet de données défini dans ce jeu pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='objects') }}">crées-en un</a>.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="nf-cond-variable-fields" style="display:none;">
|
|
||||||
<label>Variable<select id="nf-cond-variable">
|
|
||||||
<option value="">(sélectionner une variable)</option>
|
|
||||||
{% for v in global_variables %}
|
|
||||||
<option value="{{ v.name }}">{{ v.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<label>Chemin (optionnel)<input type="text" id="nf-cond-variable-chemin" placeholder="ex. .nom ou [0].valeur"></label>
|
|
||||||
{% if not global_variables %}
|
|
||||||
<p class="hint">Aucune variable globale pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='variables') }}" target="_blank">crées-en une ↗</a></p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-cond-collision-fields" style="display:none;">
|
|
||||||
<label>Premier objet<select id="nf-cond-element-a">
|
|
||||||
{% for o in objects %}
|
|
||||||
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<label>Deuxième objet<select id="nf-cond-element-b">
|
|
||||||
{% for o in objects %}
|
|
||||||
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<p class="hint">Vrai si les deux objets se touchent (leurs rectangles se chevauchent à l'écran) — relie le port "Faux" du nœud pour "ne se touchent pas".</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-cond-operator-value-row">
|
|
||||||
<label>Condition<select id="nf-cond-operator">
|
|
||||||
{% for key, label in condition_operators %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
<label>Valeur de comparaison<input type="text" id="nf-cond-value" placeholder="ex. 1, vrai, 10..."></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-cond-extra-wrap">
|
|
||||||
<label id="nf-cond-combinator-row" style="display:none;">Combiner les conditions avec
|
|
||||||
<select id="nf-cond-combinator">
|
|
||||||
<option value="et">ET (toutes les conditions doivent être vraies)</option>
|
|
||||||
<option value="ou">OU (au moins une condition doit être vraie)</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div id="nf-cond-extra-clauses"></div>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="addFlowConditionClause()">+ Ajouter une condition</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nodeFormAction" style="display:none;">
|
|
||||||
<label>Type d'action<select id="nf-action-type" onchange="toggleFlowActionFields()">
|
|
||||||
{% for key, label in action_type_labels.items() %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select></label>
|
|
||||||
|
|
||||||
<select id="nf-action-target-screen" style="display:none;">
|
|
||||||
{% for s in all_screens %}
|
|
||||||
<option value="{{ s.id }}">{{ s.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<!-- Équivalent scène de "Modifier un élément" — palette réduite
|
|
||||||
(visibilité/position en pixels/orientation), voir
|
|
||||||
OBJECT_ACTION_PROPERTIES (screens/scenes/flow_palette.py). -->
|
|
||||||
<div id="nf-action-object-fields" style="display:none;">
|
|
||||||
<select id="nf-action-target-object">
|
|
||||||
{% for o in objects %}
|
|
||||||
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<select id="nf-action-object-property" onchange="toggleFlowObjectPropertyValue()" style="margin-top:6px;">
|
|
||||||
{% for key, label in object_action_properties %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<select id="nf-action-object-value-visibility" style="margin-top:6px;">
|
|
||||||
{% for key, label in element_visibility_values %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<select id="nf-action-object-value-orientation" style="display:none; margin-top:6px;">
|
|
||||||
{% for key, label in element_orientation_values %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<input type="number" id="nf-action-object-value-number" style="display:none; margin-top:6px;" placeholder="Nouvelle valeur en pixels">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-wait-fields" style="display:none;">
|
|
||||||
<label>Délai en secondes avant de continuer
|
|
||||||
<input type="number" id="nf-action-wait-seconds" min="0" step="0.5" value="3" placeholder="ex. 3">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-data-fields" style="display:none;">
|
|
||||||
{% if not definitions %}
|
|
||||||
<p class="hint">Aucun objet de données défini dans ce jeu pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='objects') }}">crées-en un</a>.</p>
|
|
||||||
{% else %}
|
|
||||||
<select id="nf-action-data-definition" onchange="onFlowDefinitionChange('action')">
|
|
||||||
{% for d in definitions %}
|
|
||||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<select id="nf-action-data-row" style="margin-top:6px;"></select>
|
|
||||||
<select id="nf-action-data-field" style="margin-top:6px;"></select>
|
|
||||||
<select id="nf-action-data-operation" style="margin-top:6px;" onchange="updateDataValuePlaceholder('nf-action-data-operation', 'nf-action-data-value')">
|
|
||||||
{% for key, label in data_operations %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<input type="text" id="nf-action-data-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-sound-fields" style="display:none;">
|
|
||||||
<label>URL du son
|
|
||||||
<input type="text" id="nf-action-sound-url" placeholder="URL du fichier audio">
|
|
||||||
</label>
|
|
||||||
<div style="margin-top:6px;">
|
|
||||||
<label for="nf-action-sound-upload" style="font-size:12px;">Envoyer un fichier :</label>
|
|
||||||
<input type="file" class="nodeFormUploadInput" id="nf-action-sound-upload" data-target="nf-action-sound-url" accept="audio/*" style="font-size:12px;">
|
|
||||||
<span class="uploadStatus" style="font-size:12px; color:var(--text-dim);"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-sprite-fields" style="display:none;">
|
|
||||||
<label>Personnage à animer
|
|
||||||
<select id="nf-action-sprite-target-element" onchange="onSpriteTargetElementChange()">
|
|
||||||
<option value="">— Choisir —</option>
|
|
||||||
{% for o in objects if o.kind == 'personnage' %}
|
|
||||||
<option value="{{ o.id }}">{{ o.name or 'Personnage' }} (obj-{{ o.id }})</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
{% if not objects|selectattr('kind', 'equalto', 'personnage')|list %}
|
|
||||||
<p class="hint">Aucun personnage sur cette scène — ajoute-en un depuis la galerie "🎭 Personnages" du panneau de gauche.</p>
|
|
||||||
{% endif %}
|
|
||||||
<div id="nf-action-sprite-animation-grid" class="iconGallery" style="margin-top:6px;"></div>
|
|
||||||
<input type="hidden" id="nf-action-sprite-animation-name" value="">
|
|
||||||
<div class="fieldRow" style="margin-top:6px;">
|
|
||||||
<label style="flex:1;">Images/seconde
|
|
||||||
<input type="number" id="nf-action-sprite-fps" min="1" step="1" value="8">
|
|
||||||
</label>
|
|
||||||
<label style="flex:1; display:flex; align-items:center; gap:6px;">
|
|
||||||
<input type="checkbox" id="nf-action-sprite-loop" checked style="width:auto;"> En boucle
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-variable-fields" style="display:none;">
|
|
||||||
{% if not global_variables %}
|
|
||||||
<p class="hint">Aucune variable globale pour l'instant — crées-en une depuis le tableau de bord.</p>
|
|
||||||
{% else %}
|
|
||||||
<select id="nf-action-variable-name">
|
|
||||||
{% for v in global_variables %}
|
|
||||||
<option value="{{ v.name }}">{{ v.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<select id="nf-action-variable-operation" style="margin-top:6px;" onchange="updateDataValuePlaceholder('nf-action-variable-operation', 'nf-action-variable-value')">
|
|
||||||
{% for key, label in data_operations %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<input type="text" id="nf-action-variable-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Score/progression (voir db/scoring/) : concept de premier
|
|
||||||
ordre distinct des variables globales ci-dessus — un seul
|
|
||||||
score par joueur, pas de nom à choisir. Réutilise le même
|
|
||||||
vocabulaire d'opérations (DATA_OPERATIONS). -->
|
|
||||||
<div id="nf-action-score-fields" style="display:none;">
|
|
||||||
<select id="nf-action-score-operation" onchange="updateDataValuePlaceholder('nf-action-score-operation', 'nf-action-score-value')">
|
|
||||||
{% for key, label in data_operations %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<input type="text" id="nf-action-score-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-status-fields" style="display:none;">
|
|
||||||
<select id="nf-action-status-value">
|
|
||||||
{% for key, label in score_status_choices %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nf-action-event-fields" style="display:none;">
|
|
||||||
{% if not custom_events %}
|
|
||||||
<p class="hint">Aucun événement pour l'instant — <a href="javascript:void(0)" onclick="switchBuilderTab('events'); return false;">crées-en un dans l'onglet Événements</a>.</p>
|
|
||||||
{% else %}
|
|
||||||
<select id="nf-action-custom-event">
|
|
||||||
{% for ev in custom_events %}
|
|
||||||
<option value="{{ ev.id }}">{{ ev.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actionsRow"><button type="button" class="smallBtn button is-small is-primary" onclick="submitNodeForm()">+ Ajouter ce nœud</button></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flowCanvasWrap">
|
|
||||||
<svg id="flowEdges" class="flowEdgesSvg"></svg>
|
|
||||||
<div id="flowCanvas" class="flowCanvas"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="floatPanelResizeHandle" id="logicBlockPanelResizeHandle"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="builderTabPanel" id="animTabPanel" data-tab-panel="anim">
|
|
||||||
<div class="flowToolbar">
|
|
||||||
<label style="display:flex; align-items:center; gap:6px;">
|
|
||||||
Piste
|
|
||||||
<select id="anim-new-track-element">
|
|
||||||
{% for o in objects %}
|
|
||||||
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="addAnimTrack()">➕ Ajouter un clip</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="animClipForm" class="nodeForm" style="display:none;">
|
|
||||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
|
||||||
<strong id="animClipFormTitle">Clip d'animation</strong>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="closeAnimClipForm()">✕</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label>Type
|
|
||||||
<select id="anim-clip-kind" onchange="toggleAnimClipKind()">
|
|
||||||
<option value="animate_css">Animation Animate.css</option>
|
|
||||||
<option value="custom">Animation personnalisée — images-clés</option>
|
|
||||||
<option value="sprite">Animation de sprites</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div id="anim-clip-animate-css-fields">
|
|
||||||
<label>Animation
|
|
||||||
<select id="anim-clip-animate-name">
|
|
||||||
{% for category, items in animate_css_catalog %}
|
|
||||||
<optgroup label="{{ category }}">
|
|
||||||
{% for key, label in items %}
|
|
||||||
<option value="{{ key }}">{{ label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</optgroup>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="anim-clip-custom-fields" style="display:none;">
|
|
||||||
<div id="anim-clip-keyframes"></div>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="anim-clip-sprite-fields" style="display:none;">
|
|
||||||
<p class="hint" id="anim-clip-sprite-no-animations" style="display:none;">Cet objet n'est pas un personnage, ou n'a encore aucune animation.</p>
|
|
||||||
<div id="anim-clip-sprite-animation-grid" class="iconGallery"></div>
|
|
||||||
<input type="hidden" id="anim-clip-sprite-animation-name" value="">
|
|
||||||
<label style="margin-top:6px; display:block;">Images/seconde
|
|
||||||
<input type="number" id="anim-clip-sprite-fps" min="1" step="1" value="8">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="fieldRow">
|
|
||||||
<label style="flex:1;">Départ en secondes
|
|
||||||
<input type="number" id="anim-clip-start" min="0" step="0.1" value="0">
|
|
||||||
</label>
|
|
||||||
<label style="flex:1;">Durée en secondes
|
|
||||||
<input type="number" id="anim-clip-duration" min="0.1" step="0.1" value="1">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="fieldRow">
|
|
||||||
<label style="flex:1;">Délai en secondes
|
|
||||||
<input type="number" id="anim-clip-delay" min="0" step="0.1" value="0">
|
|
||||||
</label>
|
|
||||||
<label style="flex:1;">Vitesse
|
|
||||||
<select id="anim-clip-easing">
|
|
||||||
<option value="ease">Progressive</option>
|
|
||||||
<option value="linear">Constante</option>
|
|
||||||
<option value="ease-in">Accélère</option>
|
|
||||||
<option value="ease-out">Ralentit</option>
|
|
||||||
<option value="ease-in-out">Accélère puis ralentit</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label>Répétitions
|
|
||||||
<select id="anim-clip-iteration">
|
|
||||||
<option value="1">Une fois</option>
|
|
||||||
<option value="2">2 fois</option>
|
|
||||||
<option value="3">3 fois</option>
|
|
||||||
<option value="infinite">En boucle</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="actionsRow">
|
|
||||||
<button type="button" class="smallBtn button is-small is-primary" onclick="saveAnimClipForm()">Enregistrer</button>
|
|
||||||
<button type="button" class="smallBtn button is-small" onclick="previewAnimClip()">▶ Aperçu</button>
|
|
||||||
<button type="button" class="smallBtn button is-small is-danger" onclick="deleteAnimClipForm()">🗑️ Supprimer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="animTimelineWrap">
|
|
||||||
<div id="animRuler" class="animRuler"></div>
|
|
||||||
<div id="animTracks" class="animTracks"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="builderTabPanel" id="eventsTabPanel" data-tab-panel="events">
|
<div class="builderTabPanel" id="eventsTabPanel" data-tab-panel="events">
|
||||||
<div class="dashPanelHeader">
|
<div class="dashPanelHeader">
|
||||||
<h2>Événements personnalisés</h2>
|
<h2>Événements personnalisés</h2>
|
||||||
@@ -615,7 +175,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="hint">Aucun événement pour l'instant — crée le premier ci-dessus, puis pose un nœud "Sur un événement personnalisé" (Déclencheur) ou "Déclencher un événement" (Action) dans la Logique de la scène.</p>
|
<p class="hint">Aucun événement pour l'instant — crée le premier ci-dessus.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -666,12 +226,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
{% if obj.kind != 'fond' %}
|
||||||
<!-- Boîte de collision (voir screens/rendering/collision_settings.py,
|
<!-- Boîte de collision (voir screens/rendering/collision_settings.py,
|
||||||
static/js/play/conditions.js::elementsOverlap) : posée AUTOMATIQUEMENT
|
static/js/play/conditions.js::elementsOverlap) : posée AUTOMATIQUEMENT
|
||||||
sur tout objet (= sa boîte visuelle par défaut, tant que rien
|
sur tout objet (= sa boîte visuelle par défaut, tant que rien
|
||||||
n'est changé ici) — désactivable, taille/forme/décalage réglables
|
n'est changé ici) — désactivable, taille/forme/décalage réglables
|
||||||
pour un sprite très paddé dont la silhouette réelle est plus
|
pour un sprite très paddé dont la silhouette réelle est plus
|
||||||
petite que son canevas. -->
|
petite que son canevas. JAMAIS pour un "fond" (image de décor,
|
||||||
|
voir add_scene_object.py) : ni obstacle ni cible de collision
|
||||||
|
possible (forgeSolidObstacles l'exclut déjà côté jeu, voir
|
||||||
|
static/js/play/personnage-controller.js), ce panneau n'aurait
|
||||||
|
donc aucun effet. -->
|
||||||
<details class="propGroup">
|
<details class="propGroup">
|
||||||
<summary>🧱 Collision</summary>
|
<summary>🧱 Collision</summary>
|
||||||
<div class="propGroupBody" id="collisionPanelBody" data-element-id="{{ obj.id }}">
|
<div class="propGroupBody" id="collisionPanelBody" data-element-id="{{ obj.id }}">
|
||||||
@@ -708,6 +273,7 @@
|
|||||||
<p class="help">Taille/décalage réglés par rapport au centre de l'objet — laisse la taille par défaut (celle de l'objet) si son image n'a pas de marge inutile.</p>
|
<p class="help">Taille/décalage réglés par rapport au centre de l'objet — laisse la taille par défaut (celle de l'objet) si son image n'a pas de marge inutile.</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if obj.kind == 'personnage' %}
|
{% if obj.kind == 'personnage' %}
|
||||||
<details class="propGroup" open>
|
<details class="propGroup" open>
|
||||||
@@ -841,9 +407,7 @@
|
|||||||
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/personnage-panel.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/personnage-panel.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/flow-editor.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/builder-tabs.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/tabs-and-blocks.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/animation-timeline.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/personnage-preview.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/personnage-preview.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/scenes/scene-editor.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/scenes/scene-editor.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1305,6 +1305,7 @@
|
|||||||
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/personnage-panel.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/personnage-panel.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/screen_edit/builder-tabs.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/flow-editor.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/flow-editor.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/tabs-and-blocks.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/tabs-and-blocks.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/screen_edit/animation-timeline.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/screen_edit/animation-timeline.js') }}"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user