From 7c9b2c9b252d82bc50e823fa296fd5687c0540fd Mon Sep 17 00:00:00 2001 From: william Date: Thu, 3 Sep 2026 16:27:51 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20"impossible=20d'aller=20plus=20loin=20ver?= =?UTF-8?q?s=20la=20droite"=20=E2=80=94=20d=C3=A9filement=20automatique=20?= =?UTF-8?q?du=20canevas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- static/js/scenes/scene-editor.js | 92 +++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/static/js/scenes/scene-editor.js b/static/js/scenes/scene-editor.js index dbe51ef5..5a0d36a8 100644 --- a/static/js/scenes/scene-editor.js +++ b/static/js/scenes/scene-editor.js @@ -36,16 +36,53 @@ function updateScenePosFields(x, y, w, h) { 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. +// 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 startLeft = parseFloat(el.style.left) || 0, startTop = parseFloat(el.style.top) || 0; + 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 @@ -59,25 +96,32 @@ function onSceneObjectMouseDown(e, id) { // à 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 - startX, dy = ev.clientY - startY; - if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { + 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; } - 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'; + 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; - var newLeft = parseFloat(el.style.left), newTop = parseFloat(el.style.top); - updateScenePosFields(newLeft, newTop, width, height); - saveSceneObjectGeometry(id, newLeft, newTop, width, height); + updateScenePosFields(curLeft, curTop, width, height); + saveSceneObjectGeometry(id, curLeft, curTop, width, height); } else { window.location.href = SCREEN_EDIT_URL + '?selected=' + id; } @@ -90,8 +134,8 @@ 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 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 @@ -99,18 +143,26 @@ function onSceneObjectResizeMouseDown(e, id) { var previousZIndex = el.style.zIndex; el.style.zIndex = 9999; - 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 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 = parseFloat(el.style.width), newHeight = parseFloat(el.style.height); + var newWidth = curWidth, newHeight = curHeight; updateScenePosFields(left, top, newWidth, newHeight); saveSceneObjectGeometry(id, left, top, newWidth, newHeight); }