Fix "impossible d'aller plus loin vers la droite" — défilement automatique du canevas
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

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>
This commit is contained in:
william
2026-09-03 16:27:51 +02:00
co-authored by Claude Sonnet 5
parent 046c38b4dc
commit 7c9b2c9b25
+72 -20
View File
@@ -36,16 +36,53 @@ function updateScenePosFields(x, y, w, h) {
if (hEl) hEl.value = Math.round(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 // Glisser-déposer en pixels, clampé à la taille FIXE de la scène
// (SCENE_WIDTH/SCENE_HEIGHT, voir templates/scene_edit.html) — mirror // (SCENE_WIDTH/SCENE_HEIGHT, voir templates/scene_edit.html) — mirror
// d'onElementMouseDown() (tree-panels.js), mais sans conversion en % : // 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) { function onSceneObjectMouseDown(e, id) {
if (e.target.classList.contains('resizeHandle')) return; if (e.target.classList.contains('resizeHandle')) return;
e.preventDefault(); e.preventDefault();
var el = document.getElementById('el-' + id); var el = document.getElementById('el-' + id);
var startX = e.clientX, startY = e.clientY; 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 width = parseFloat(el.style.width) || 0, height = parseFloat(el.style.height) || 0;
var moved = false; var moved = false;
// Les panneaux flottants "🧩 Objets"/"⚙️ Propriétés" (voir static/style.css // 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. // à sa valeur d'origine à la fin.
var previousZIndex = el.style.zIndex; 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) { function onMove(ev) {
var dx = ev.clientX - startX, dy = ev.clientY - startY; var dx = ev.clientX - lastClientX, dy = ev.clientY - lastClientY;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { 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; if (!moved) el.style.zIndex = 9999;
moved = true; moved = true;
} }
var newLeft = Math.max(0, Math.min(SCENE_WIDTH - width, startLeft + dx)); applyDelta(dx, dy);
var newTop = Math.max(0, Math.min(SCENE_HEIGHT - height, startTop + dy));
el.style.left = newLeft + 'px';
el.style.top = newTop + 'px';
} }
var stopAutoScroll = forgeStartCanvasAutoScroll(
function () { return { x: lastClientX, y: lastClientY }; }, applyDelta
);
function onUp() { function onUp() {
document.removeEventListener('mousemove', onMove); document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp); document.removeEventListener('mouseup', onUp);
stopAutoScroll();
if (moved) { if (moved) {
el.style.zIndex = previousZIndex; el.style.zIndex = previousZIndex;
var newLeft = parseFloat(el.style.left), newTop = parseFloat(el.style.top); updateScenePosFields(curLeft, curTop, width, height);
updateScenePosFields(newLeft, newTop, width, height); saveSceneObjectGeometry(id, curLeft, curTop, width, height);
saveSceneObjectGeometry(id, newLeft, newTop, width, height);
} else { } else {
window.location.href = SCREEN_EDIT_URL + '?selected=' + id; window.location.href = SCREEN_EDIT_URL + '?selected=' + id;
} }
@@ -90,8 +134,8 @@ function onSceneObjectResizeMouseDown(e, id) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
var el = document.getElementById('el-' + id); var el = document.getElementById('el-' + id);
var startX = e.clientX, startY = e.clientY; var lastClientX = e.clientX, lastClientY = e.clientY;
var startWidth = parseFloat(el.style.width) || 0, startHeight = parseFloat(el.style.height) || 0; 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; var left = parseFloat(el.style.left) || 0, top = parseFloat(el.style.top) || 0;
// Voir la même note dans onSceneObjectMouseDown ci-dessus : au-dessus // Voir la même note dans onSceneObjectMouseDown ci-dessus : au-dessus
// des panneaux flottants le temps du geste, pour rester visible même // 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; var previousZIndex = el.style.zIndex;
el.style.zIndex = 9999; el.style.zIndex = 9999;
function onMove(ev) { function applyDelta(dx, dy) {
var dx = ev.clientX - startX, dy = ev.clientY - startY; curWidth = Math.max(8, Math.min(SCENE_WIDTH - left, curWidth + dx));
var newWidth = Math.max(8, Math.min(SCENE_WIDTH - left, startWidth + dx)); curHeight = Math.max(8, Math.min(SCENE_HEIGHT - top, curHeight + dy));
var newHeight = Math.max(8, Math.min(SCENE_HEIGHT - top, startHeight + dy)); el.style.width = curWidth + 'px';
el.style.width = newWidth + 'px'; el.style.height = curHeight + 'px';
el.style.height = newHeight + '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() { function onUp() {
document.removeEventListener('mousemove', onMove); document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp); document.removeEventListener('mouseup', onUp);
stopAutoScroll();
el.style.zIndex = previousZIndex; 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); updateScenePosFields(left, top, newWidth, newHeight);
saveSceneObjectGeometry(id, left, top, newWidth, newHeight); saveSceneObjectGeometry(id, left, top, newWidth, newHeight);
} }