Files
Forge-Engine/static/js/scenes/scene-editor.js
T
williamandClaude Sonnet 5 7c9b2c9b25
Build and deploy / test-python (push) Failing after 1m13s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Fix "impossible d'aller plus loin vers la droite" — défilement automatique du canevas
Cause confirmée (tous les objets bloquaient au même endroit, pas
spécifique à un widget) : une scène à taille fixe (960×540 par ex.)
peut être plus LARGE que la zone visible de .canvasFrame (overflow:auto)
selon la largeur de fenêtre — sa partie droite/basse défile alors HORS
de vue, et le curseur atteignait le bord de cette zone VISIBLE bien
avant celui de la scène elle-même : la souris ne pouvait tout simplement
plus bouger physiquement plus loin, sans aucun rapport avec la limite
réelle (960px) de la scène.

onSceneObjectMouseDown/onSceneObjectResizeMouseDown font maintenant
défiler .canvasFrame automatiquement quand le curseur approche un bord
pendant un glisser/redimensionnement — et continuent de déplacer
l'objet même si la souris reste immobile près du bord (mirror du
comportement standard d'un glisser-déposer dans une zone scrollable),
grâce à une position ACCUMULÉE plutôt que recalculée depuis le point de
départ fixe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 16:27:51 +02:00

493 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ---------- Éditeur de scène 2D (fondations multi-éditeurs) ----------
// JS propre à templates/scene_edit.html : ajout/déplacement/redimension-
// nement d'objets de scène EN PIXELS (pas de %, voir ensure_scene_schema.py),
// mirror des fonctions équivalentes de static/js/screen_edit/tree-panels.js
// pour l'éditeur document (onElementMouseDown/onResizeMouseDown/
// saveGeometry), mais sur une scène à taille FIXE plutôt qu'un canevas
// fluide — la logique de flow elle-même (flow-editor.js, tabs-and-blocks.js,
// animation-timeline.js, personnage-panel.js, personnage-preview.js) est
// réutilisée TELLE QUELLE, chargée telle quelle par scene_edit.html.
function addSceneObject(kind, forgeCharacter, backgroundSlug) {
var form = document.createElement('form');
form.method = 'post';
form.action = "/game/" + GAME_SLUG + "/screens/" + SCREEN_ID + "/scene-objects/add";
var html = '<input type="hidden" name="kind" value="' + kind + '">';
if (forgeCharacter) html += '<input type="hidden" name="forge_character" value="' + forgeCharacter + '">';
if (backgroundSlug) html += '<input type="hidden" name="background_slug" value="' + backgroundSlug + '">';
form.innerHTML = html;
document.body.appendChild(form);
form.requestSubmit();
}
function saveSceneObjectGeometry(id, x, y, width, height) {
return fetch("/game/" + GAME_SLUG + "/scene-objects/" + id + "/geometry", {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x: x, y: y, width: width, height: height }),
});
}
function updateScenePosFields(x, y, w, h) {
var xEl = document.getElementById('scene-pos-x'), yEl = document.getElementById('scene-pos-y');
var wEl = document.getElementById('scene-pos-w'), hEl = document.getElementById('scene-pos-h');
if (xEl) xEl.value = Math.round(x);
if (yEl) yEl.value = Math.round(y);
if (wEl) wEl.value = Math.round(w);
if (hEl) hEl.value = Math.round(h);
}
// Défilement automatique de .canvasFrame (overflow:auto, voir static/
// style.css) pendant un glisser/redimensionnement — bug corrigé : une
// scène à taille FIXE (960×540 par ex.) peut être plus LARGE que la zone
// visible de .canvasFrame (fenêtre pas assez large, panneaux ouverts...),
// auquel cas sa partie droite/basse défile HORS de vue. Sans défilement
// automatique, le curseur atteignait le bord de la zone visible (donc de
// .canvasFrame) bien avant celui de la scène elle-même — "impossible
// d'aller plus loin vers la droite" alors qu'il restait pourtant de la
// place dans la scène. `onNudge(dx, dy)` est rappelé à chaque tick tant
// que le curseur reste dans la marge d'un bord, MÊME SI la souris ne
// bouge plus (immobile près du bord) — mirror du comportement standard
// d'un glisser-déposer proche d'un bord de zone scrollable.
function forgeStartCanvasAutoScroll(getMouse, onNudge) {
var MARGIN = 40, SPEED = 14;
var timer = setInterval(function () {
var frame = document.querySelector('.canvasFrame');
if (!frame) return;
var rect = frame.getBoundingClientRect();
var mouse = getMouse();
var dx = 0, dy = 0;
if (mouse.x > rect.right - MARGIN) dx = SPEED;
else if (mouse.x < rect.left + MARGIN) dx = -SPEED;
if (mouse.y > rect.bottom - MARGIN) dy = SPEED;
else if (mouse.y < rect.top + MARGIN) dy = -SPEED;
if (!dx && !dy) return;
frame.scrollLeft = Math.max(0, frame.scrollLeft + dx);
frame.scrollTop = Math.max(0, frame.scrollTop + dy);
onNudge(dx, dy);
}, 30);
return function stop() { clearInterval(timer); };
}
// Glisser-déposer en pixels, clampé à la taille FIXE de la scène
// (SCENE_WIDTH/SCENE_HEIGHT, voir templates/scene_edit.html) — mirror
// d'onElementMouseDown() (tree-panels.js), mais sans conversion en % :
// une scène a des coordonnées pixel absolues, pas fluides. Position
// ACCUMULÉE (curLeft/curTop, jamais recalculée depuis le point de départ
// fixe) : c'est ce qui permet à forgeStartCanvasAutoScroll ci-dessus de
// continuer à la déplacer même quand la souris ne bouge plus (immobile
// près d'un bord).
function onSceneObjectMouseDown(e, id) {
if (e.target.classList.contains('resizeHandle')) return;
e.preventDefault();
var el = document.getElementById('el-' + id);
var startX = e.clientX, startY = e.clientY;
var lastClientX = e.clientX, lastClientY = e.clientY;
var curLeft = parseFloat(el.style.left) || 0, curTop = parseFloat(el.style.top) || 0;
var width = parseFloat(el.style.width) || 0, height = parseFloat(el.style.height) || 0;
var moved = false;
// Les panneaux flottants "🧩 Objets"/"⚙️ Propriétés" (voir static/style.css
// .floatPanel, z-index:60) restent DÉLIBÉRÉMENT en position:fixed
// PAR-DESSUS le canevas, pour maximiser l'espace utile — mais un objet
// glissé sous l'un d'eux, à son z-index habituel (bien plus bas), y
// disparaissait littéralement à l'écran PENDANT le glisser (toujours
// déplacé/enregistré correctement en dessous, juste invisible pendant
// le geste) : impossible de voir où on le dépose. Passé au-dessus des
// panneaux (z-index très élevé) le temps du glisser SEULEMENT, restauré
// à sa valeur d'origine à la fin.
var previousZIndex = el.style.zIndex;
function applyDelta(dx, dy) {
curLeft = Math.max(0, Math.min(SCENE_WIDTH - width, curLeft + dx));
curTop = Math.max(0, Math.min(SCENE_HEIGHT - height, curTop + dy));
el.style.left = curLeft + 'px';
el.style.top = curTop + 'px';
}
function onMove(ev) {
var dx = ev.clientX - lastClientX, dy = ev.clientY - lastClientY;
lastClientX = ev.clientX; lastClientY = ev.clientY;
if (Math.abs(ev.clientX - startX) > 3 || Math.abs(ev.clientY - startY) > 3) {
if (!moved) el.style.zIndex = 9999;
moved = true;
}
applyDelta(dx, dy);
}
var stopAutoScroll = forgeStartCanvasAutoScroll(
function () { return { x: lastClientX, y: lastClientY }; }, applyDelta
);
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
stopAutoScroll();
if (moved) {
el.style.zIndex = previousZIndex;
updateScenePosFields(curLeft, curTop, width, height);
saveSceneObjectGeometry(id, curLeft, curTop, width, height);
} else {
window.location.href = SCREEN_EDIT_URL + '?selected=' + id;
}
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
function onSceneObjectResizeMouseDown(e, id) {
e.stopPropagation();
e.preventDefault();
var el = document.getElementById('el-' + id);
var lastClientX = e.clientX, lastClientY = e.clientY;
var curWidth = parseFloat(el.style.width) || 0, curHeight = parseFloat(el.style.height) || 0;
var left = parseFloat(el.style.left) || 0, top = parseFloat(el.style.top) || 0;
// Voir la même note dans onSceneObjectMouseDown ci-dessus : au-dessus
// des panneaux flottants le temps du geste, pour rester visible même
// en redimensionnant sous l'un d'eux.
var previousZIndex = el.style.zIndex;
el.style.zIndex = 9999;
function applyDelta(dx, dy) {
curWidth = Math.max(8, Math.min(SCENE_WIDTH - left, curWidth + dx));
curHeight = Math.max(8, Math.min(SCENE_HEIGHT - top, curHeight + dy));
el.style.width = curWidth + 'px';
el.style.height = curHeight + 'px';
}
function onMove(ev) {
var dx = ev.clientX - lastClientX, dy = ev.clientY - lastClientY;
lastClientX = ev.clientX; lastClientY = ev.clientY;
applyDelta(dx, dy);
}
var stopAutoScroll = forgeStartCanvasAutoScroll(
function () { return { x: lastClientX, y: lastClientY }; }, applyDelta
);
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
stopAutoScroll();
el.style.zIndex = previousZIndex;
var newWidth = curWidth, newHeight = curHeight;
updateScenePosFields(left, top, newWidth, newHeight);
saveSceneObjectGeometry(id, left, top, newWidth, newHeight);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
// Champs numériques Position & taille du panneau de propriétés — mirror
// du comportement équivalent pour l'éditeur document (voir l'IIFE dans
// initBuilderPanel(), panel-init.js), mais ids/route distincts (pixels).
(function () {
var xEl = document.getElementById('scene-pos-x'), yEl = document.getElementById('scene-pos-y');
var wEl = document.getElementById('scene-pos-w'), hEl = document.getElementById('scene-pos-h');
if (!xEl || !wEl || !CURRENT_SELECTED_ID) return;
function apply() {
var canvasEl = document.getElementById('el-' + CURRENT_SELECTED_ID);
var x = parseFloat(xEl.value) || 0, y = parseFloat(yEl.value) || 0;
var w = parseFloat(wEl.value) || 1, h = parseFloat(hEl.value) || 1;
if (canvasEl) {
canvasEl.style.left = x + 'px'; canvasEl.style.top = y + 'px';
canvasEl.style.width = w + 'px'; canvasEl.style.height = h + 'px';
}
saveSceneObjectGeometry(CURRENT_SELECTED_ID, x, y, w, h);
}
[xEl, yEl, wEl, hEl].forEach(function (el) { el.addEventListener('change', apply); });
})();
// Personnage d'un objet de scène : mirror de swapPersonnageCharacter()
// (personnage-panel.js), mais POST vers /scene-objects/.../personnage
// (voir routes/scenes/scene_object_personnage_data.py) plutôt que
// /elements/.../personnage — même réponse (animations résolues),
// relance l'aperçu animé via restartPersonnageIdlePreview() (personnage-
// preview.js, déjà générique : ne dépend que de data-element-id/
// data-personnage, présents sur un objet de scène comme sur un élément).
function swapSceneObjectCharacter(forgeCharacter) {
var body = document.getElementById('personnagePanelBody');
if (!body) return;
var objectId = parseInt(body.dataset.elementId, 10);
document.querySelectorAll('#personnageGallerySwap .iconGalleryTile').forEach(function (tile) {
tile.classList.toggle('selected', tile.getAttribute('onclick') === "swapSceneObjectCharacter('" + forgeCharacter + "')");
});
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/personnage', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'forge', forge_character: forgeCharacter }),
})
.then(function (r) { return r.json(); })
.then(function (json) {
if (json.animations) {
ELEMENT_ANIMATIONS_MAP[objectId] = json.animations;
restartPersonnageIdlePreview(objectId, json.animations);
}
});
}
// Rôle (voir screens/rendering/personnage_role.py) : seul un personnage
// "Joueur" est déplacé/animé automatiquement au clavier — même patron AJAX
// que swapSceneObjectCharacter() ci-dessus (POST JSON, aucun rechargement
// de page), sur routes/scenes/scene_object_role.py.
function saveSceneObjectRole(role) {
var body = document.getElementById('personnagePanelBody');
if (!body) return;
var objectId = parseInt(body.dataset.elementId, 10);
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/role', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role: role }),
});
}
// Boîte de collision (voir screens/rendering/collision_settings.py,
// static/js/play/conditions.js::elementsOverlap) : disponible pour TOUT
// objet de scène (pas seulement "personnage") — même patron AJAX que les
// fonctions ci-dessus, sur routes/scenes/scene_object_collision.py.
// Recale aussi l'aperçu (#collisionBoxOverlay, seulement présent pour
// l'objet sélectionné) pour rester en phase avec les champs, qu'ils
// viennent d'être tapés à la main ou ajustés au glisser (voir
// onCollisionBoxMouseDown/onCollisionBoxResizeMouseDown plus bas).
function saveSceneObjectCollision() {
var body = document.getElementById('collisionPanelBody');
if (!body) return;
var objectId = parseInt(body.dataset.elementId, 10);
var enabled = document.getElementById('collision-enabled').checked;
var shape = document.getElementById('collision-shape').value;
var width = parseFloat(document.getElementById('collision-width').value) || 0;
var height = parseFloat(document.getElementById('collision-height').value) || 0;
var offsetX = parseFloat(document.getElementById('collision-offset-x').value) || 0;
var offsetY = parseFloat(document.getElementById('collision-offset-y').value) || 0;
var overlay = document.getElementById('collisionBoxOverlay');
var objEl = document.getElementById('el-' + objectId);
if (overlay && objEl) {
var objWidth = parseFloat(objEl.style.width) || 0, objHeight = parseFloat(objEl.style.height) || 0;
overlay.style.width = width + 'px';
overlay.style.height = height + 'px';
overlay.style.left = ((objWidth - width) / 2 + offsetX) + 'px';
overlay.style.top = ((objHeight - height) / 2 + offsetY) + 'px';
overlay.classList.toggle('is-circle', shape === 'cercle');
overlay.classList.toggle('is-disabled', !enabled);
}
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/collision', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: enabled, shape: shape, width: width, height: height, offset_x: offsetX, offset_y: offsetY }),
});
}
function updateCollisionFields(width, height, offsetX, offsetY) {
var wEl = document.getElementById('collision-width'), hEl = document.getElementById('collision-height');
var oxEl = document.getElementById('collision-offset-x'), oyEl = document.getElementById('collision-offset-y');
if (wEl) wEl.value = Math.round(width);
if (hEl) hEl.value = Math.round(height);
if (oxEl) oxEl.value = Math.round(offsetX);
if (oyEl) oyEl.value = Math.round(offsetY);
}
// Glisser le CORPS de l'aperçu ajuste son décalage (offset_x/offset_y) —
// mirror d'onSceneObjectMouseDown ci-dessus, mais sur la boîte de
// collision plutôt que l'objet lui-même (jamais le même geste : cliquer
// l'objet EN DEHORS de la boîte de collision continue de le déplacer
// normalement, la boîte est un enfant plus petit posé par-dessus).
function onCollisionBoxMouseDown(e, id) {
e.stopPropagation();
if (e.target.classList.contains('collisionResizeHandle')) return;
e.preventDefault();
var overlay = document.getElementById('collisionBoxOverlay');
var objEl = document.getElementById('el-' + id);
var objWidth = parseFloat(objEl.style.width) || 0, objHeight = parseFloat(objEl.style.height) || 0;
var startX = e.clientX, startY = e.clientY;
var startLeft = parseFloat(overlay.style.left) || 0, startTop = parseFloat(overlay.style.top) || 0;
var boxWidth = parseFloat(overlay.style.width) || 0, boxHeight = parseFloat(overlay.style.height) || 0;
function onMove(ev) {
overlay.style.left = (startLeft + (ev.clientX - startX)) + 'px';
overlay.style.top = (startTop + (ev.clientY - startY)) + 'px';
}
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
var offsetX = (parseFloat(overlay.style.left) || 0) - (objWidth - boxWidth) / 2;
var offsetY = (parseFloat(overlay.style.top) || 0) - (objHeight - boxHeight) / 2;
updateCollisionFields(boxWidth, boxHeight, offsetX, offsetY);
saveSceneObjectCollision();
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
// Glisser la poignée (coin bas-droit) redimensionne la boîte de collision
// — mirror d'onSceneObjectResizeMouseDown, ancré sur son coin haut-gauche
// (le centre se déplace donc légèrement à chaque redimensionnement, sauf
// à recalculer l'offset pour le garder fixe : on choisit de garder le
// COIN fixe, plus prévisible au glisser qu'un centre qui "fuit").
function onCollisionBoxResizeMouseDown(e, id) {
e.stopPropagation();
e.preventDefault();
var overlay = document.getElementById('collisionBoxOverlay');
var objEl = document.getElementById('el-' + id);
var objWidth = parseFloat(objEl.style.width) || 0, objHeight = parseFloat(objEl.style.height) || 0;
var startX = e.clientX, startY = e.clientY;
var startWidth = parseFloat(overlay.style.width) || 0, startHeight = parseFloat(overlay.style.height) || 0;
var left = parseFloat(overlay.style.left) || 0, top = parseFloat(overlay.style.top) || 0;
function onMove(ev) {
overlay.style.width = Math.max(4, startWidth + (ev.clientX - startX)) + 'px';
overlay.style.height = Math.max(4, startHeight + (ev.clientY - startY)) + 'px';
}
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
var newWidth = parseFloat(overlay.style.width), newHeight = parseFloat(overlay.style.height);
var offsetX = left - (objWidth - newWidth) / 2;
var offsetY = top - (objHeight - newHeight) / 2;
updateCollisionFields(newWidth, newHeight, offsetX, offsetY);
saveSceneObjectCollision();
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
// "Commandes" (voir static/js/play/personnage-controller.js) : touches de
// déplacement/interaction + blocage d'axe + touches supplémentaires d'un
// personnage — un seul formulaire, enregistré en AJAX à chaque
// changement de champ, même patron que swapSceneObjectCharacter()
// ci-dessus (POST JSON vers routes/scenes/scene_object_commands.py,
// aucun rechargement de page).
function saveSceneObjectCommands() {
var body = document.getElementById('personnageCommandsBody');
if (!body) return;
var objectId = parseInt(body.dataset.elementId, 10);
var extra = [];
document.querySelectorAll('#personnageCommandsExtra .personnageCommandExtraRow').forEach(function (row) {
var toucheEl = row.querySelector('.personnageKeyCapture');
var animEl = row.querySelector('.cmdExtraAnim');
if (toucheEl && toucheEl.value && animEl && animEl.value) {
extra.push({ touche: toucheEl.value, animation: animEl.value });
}
});
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/commandes', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
haut: document.getElementById('cmd-haut').value,
bas: document.getElementById('cmd-bas').value,
gauche: document.getElementById('cmd-gauche').value,
droite: document.getElementById('cmd-droite').value,
interagir: document.getElementById('cmd-interagir').value,
axe: document.getElementById('cmd-axe').value,
vitesse: document.getElementById('cmd-vitesse').value,
extra: extra,
}),
});
}
// Détection de touche (bug remonté par l'utilisateur : taper le nom d'une
// touche à la main est source d'erreurs — "Espace" vs " ", "flèche haut"
// vs "ArrowUp"...) : au focus, le champ (readonly, jamais tapé) affiche un
// indice puis se remplit avec la PROCHAINE touche pressée (event.key,
// même valeur que heldKeys/bindKeyboardTriggers, triggers.js — cohérent
// avec ce que le moteur compare réellement au jeu). capture:true sur
// window pour intercepter la touche avant tout autre raccourci de
// l'éditeur (ex. Suppr qui efface l'objet sélectionné).
function bindPersonnageKeyCapture(el) {
if (!el || el.dataset.keyCaptureBound) return;
el.dataset.keyCaptureBound = '1';
el.addEventListener('focus', function () {
var previous = el.value;
el.value = 'Appuie sur une touche...';
el.classList.add('is-warning');
function onKey(ev) {
ev.preventDefault();
ev.stopPropagation();
window.removeEventListener('keydown', onKey, true);
el.classList.remove('is-warning');
if (ev.key === 'Escape') { el.value = previous; el.blur(); return; }
el.value = ev.key;
el.blur();
el.dispatchEvent(new Event('change'));
}
window.addEventListener('keydown', onKey, true);
});
}
// Une ligne = une touche supplémentaire liée à UNE pose au choix parmi
// celles RÉELLEMENT disponibles pour CE personnage (ELEMENT_ANIMATIONS_MAP,
// déjà connu de personnage-panel.js/personnage-preview.js) — jamais un nom
// tapé à la main, qui pourrait ne correspondre à aucune pose existante.
function personnageCommandExtraRowEl(objectId, touche, animation) {
var row = document.createElement('div');
row.className = 'field is-grouped personnageCommandExtraRow';
var animNames = Object.keys(ELEMENT_ANIMATIONS_MAP[objectId] || {});
var options = animNames.map(function (name) {
return '<option value="' + name + '"' + (name === animation ? ' selected' : '') + '>' + name + '</option>';
}).join('');
row.innerHTML =
'<div class="control"><input class="input is-small personnageKeyCapture" readonly placeholder="Touche..." value="' + (touche || '').replace(/"/g, '&quot;') + '"></div>' +
'<div class="control"><div class="select is-small"><select class="cmdExtraAnim">' + options + '</select></div></div>' +
'<div class="control"><button type="button" class="button is-small" onclick="this.closest(\'.personnageCommandExtraRow\').remove(); saveSceneObjectCommands();">×</button></div>';
var keyInput = row.querySelector('.personnageKeyCapture');
bindPersonnageKeyCapture(keyInput);
keyInput.addEventListener('change', saveSceneObjectCommands);
row.querySelector('.cmdExtraAnim').addEventListener('change', saveSceneObjectCommands);
return row;
}
function addPersonnageCommandExtraRow() {
var body = document.getElementById('personnageCommandsBody');
var container = document.getElementById('personnageCommandsExtra');
if (!body || !container) return;
var objectId = parseInt(body.dataset.elementId, 10);
container.appendChild(personnageCommandExtraRowEl(objectId, '', ''));
}
// "ℹ️ Informations" (voir templates/scene_edit.html) : le nom d'un
// personnage, réutilisé comme "qui parle" dans l'éditeur de dialogue de
// quête (voir static/js/quests/quest-editor.js).
function saveSceneObjectName(objectId, name) {
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/name', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name }),
});
}
// "🎨 Style" du widget "💬 Boîte de dialogue" (voir screens/rendering/
// dialogue_box_style.py) — envoie TOUJOURS les 7 champs ensemble (même
// convention "replace-all" que saveSceneObjectCollision), jamais un seul
// champ isolé.
function saveDialogueBoxStyle(objectId) {
var style = {
font_family: document.getElementById('dbox-font-family').value,
font_size: Number(document.getElementById('dbox-font-size').value),
font_weight: document.getElementById('dbox-font-weight').value,
text_color: document.getElementById('dbox-text-color').value,
header_bg: document.getElementById('dbox-header-bg').value,
body_bg: document.getElementById('dbox-body-bg').value,
footer_bg: document.getElementById('dbox-footer-bg').value,
};
var el = document.getElementById('el-' + objectId);
var widget = el && el.querySelector('.dialogueBoxWidget');
if (widget) {
widget.style.fontFamily = style.font_family;
widget.style.fontSize = style.font_size + 'px';
widget.style.fontWeight = style.font_weight;
widget.style.color = style.text_color;
var header = widget.querySelector('.dialogueBoxHeader'); if (header) header.style.background = style.header_bg;
var b = widget.querySelector('.dialogueBoxBody'); if (b) b.style.background = style.body_bg;
var footer = widget.querySelector('.dialogueBoxFooter'); if (footer) footer.style.background = style.footer_bg;
}
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/dialogue-box-style', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ style: style }),
});
}
(function () {
var body = document.getElementById('personnageCommandsBody');
if (!body) return;
var objectId = parseInt(body.dataset.elementId, 10);
['cmd-haut', 'cmd-bas', 'cmd-gauche', 'cmd-droite', 'cmd-interagir'].forEach(function (id) {
bindPersonnageKeyCapture(document.getElementById(id));
});
['cmd-haut', 'cmd-bas', 'cmd-gauche', 'cmd-droite', 'cmd-interagir', 'cmd-axe', 'cmd-vitesse'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.addEventListener('change', saveSceneObjectCommands);
});
var container = document.getElementById('personnageCommandsExtra');
(window.PERSONNAGE_COMMANDS_EXTRA || []).forEach(function (e) {
container.appendChild(personnageCommandExtraRowEl(objectId, e.touche, e.animation));
});
})();