// ---------- É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) { var form = document.createElement('form'); form.method = 'post'; form.action = "/game/" + GAME_SLUG + "/screens/" + SCREEN_ID + "/scene-objects/add"; var html = ''; if (forgeCharacter) html += ''; 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); } // 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. 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 startLeft = parseFloat(el.style.left) || 0, startTop = parseFloat(el.style.top) || 0; var width = parseFloat(el.style.width) || 0, height = parseFloat(el.style.height) || 0; var moved = false; function onMove(ev) { var dx = ev.clientX - startX, dy = ev.clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) moved = true; var newLeft = Math.max(0, Math.min(SCENE_WIDTH - width, startLeft + dx)); var newTop = Math.max(0, Math.min(SCENE_HEIGHT - height, startTop + dy)); el.style.left = newLeft + 'px'; el.style.top = newTop + 'px'; } function onUp() { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); if (moved) { var newLeft = parseFloat(el.style.left), newTop = parseFloat(el.style.top); updateScenePosFields(newLeft, newTop, width, height); saveSceneObjectGeometry(id, newLeft, newTop, 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 startX = e.clientX, startY = e.clientY; var startWidth = parseFloat(el.style.width) || 0, startHeight = parseFloat(el.style.height) || 0; var left = parseFloat(el.style.left) || 0, top = parseFloat(el.style.top) || 0; function onMove(ev) { var dx = ev.clientX - startX, dy = ev.clientY - startY; var newWidth = Math.max(8, Math.min(SCENE_WIDTH - left, startWidth + dx)); var newHeight = Math.max(8, Math.min(SCENE_HEIGHT - top, startHeight + dy)); el.style.width = newWidth + 'px'; el.style.height = newHeight + 'px'; } function onUp() { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); var newWidth = parseFloat(el.style.width), newHeight = parseFloat(el.style.height); 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); } }); } // "Commandes" (voir static/js/play/personnage-controller.js) : touches de // déplacement/interaction + blocage d'axe 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); 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, }), }); } (function () { var body = document.getElementById('personnageCommandsBody'); if (!body) return; ['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); }); })();