From 03ca2723c523ccc32e733eba819246b95704225c Mon Sep 17 00:00:00 2001 From: william Date: Mon, 24 Aug 2026 15:12:50 +0200 Subject: [PATCH] Make the screen editor's properties panel floating, draggable, and closable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The right-hand properties panel was a fixed 320px flex column, permanently shrinking the canvas — on a screen with real content (tabs, gauges), this made the editor's preview visibly narrower than the actual /play rendering, to the point of wrapping tab labels differently. It's now position:fixed with a drag handle header, moved out of .builder3's flex flow so .builderCanvasArea automatically reclaims the space; closing it (✕, replaced by a small "⚙️ Propriétés" reopen button) frees the full width for a more faithful preview. Position and open/closed state persist per screen in localStorage and get reapplied after every selection change (which replaces #builder3 wholesale). Falls back to a normal stacked column below 1300px width, where a floating panel wouldn't fit usefully. --- static/style.css | 34 ++++++++++++- templates/screen_edit.html | 97 +++++++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/static/style.css b/static/style.css index d50208ef..7f772bd7 100644 --- a/static/style.css +++ b/static/style.css @@ -151,9 +151,33 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d disponible), avec leur propre défilement interne si leur contenu dépasse. */ .builder3{ display:flex; gap:20px; align-items:stretch; margin-top:14px; flex:1 1 auto; min-height:0; } .builderPanel{ flex:0 0 260px; min-width:0; height:100%; overflow-y:auto; } -.builderPanelRight{ flex:0 0 320px; min-width:0; height:100%; overflow-y:auto; } .builderCanvasArea{ flex:1 1 auto; min-width:0; height:100%; overflow-y:auto; display:flex; flex-direction:column; } +/* ---------- Panneau de propriétés flottant ---------- */ +/* Sorti du flux flex de .builder3 (position:fixed) : .builderCanvasArea + (flex:1 1 auto) récupère automatiquement l'espace libéré, sans réglage + supplémentaire — un panneau flottant repositionné par l'utilisateur + n'agrandit/ne rétrécit donc jamais le canevas de travers. Position et + état ouvert/fermé mémorisés par écran (voir restorePropsPanelState dans + screen_edit.html) pour survivre à un changement de sélection ou un + rechargement. */ +.builderPanelRight{ + position:fixed; top:90px; right:24px; width:340px; max-height:calc(100vh - 120px); + min-width:0; display:flex; flex-direction:column; z-index:60; + background:var(--panel); border:1px solid var(--border); border-radius:10px; + box-shadow:0 12px 40px rgba(0,0,0,.55); +} +.builderPanelRight.hidden{ display:none; } +.propsPanelHeader{ + display:flex; align-items:center; justify-content:space-between; gap:8px; + padding:8px 10px; border-bottom:1px solid var(--border); background:var(--panel2); + border-radius:10px 10px 0 0; cursor:move; flex:0 0 auto; user-select:none; +} +.propsPanelHeader .propsPanelTitle{ font-size:11px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em; } +.propsPanelBody{ padding:12px; overflow-y:auto; min-height:0; flex:1 1 auto; } +#propsPanelReopenBtn{ position:fixed; top:90px; right:24px; z-index:60; display:none; } +#propsPanelReopenBtn.visible{ display:inline-flex; } + .widgetGrid{ display:grid; grid-template-columns:1fr 1fr; gap:8px; } .widgetBtnForm{ margin:0; } .widgetBtn{ @@ -306,7 +330,13 @@ code{ background:var(--panel2); border:1px solid var(--border); border-radius:4p body.builderBody{ height:auto; overflow:visible; } body.builderBody > main.content{ overflow:visible; } .builder3{ flex-direction:column; flex:none; } - .builderPanel, .builderPanelRight, .builderCanvasArea{ flex:none; width:100%; height:auto; max-height:none; overflow:visible; } + .builderPanel, .builderCanvasArea{ flex:none; width:100%; height:auto; max-height:none; overflow:visible; } + /* Trop peu de largeur pour qu'un panneau flottant ait un sens (il + couvrirait tout l'écran) : il rejoint la pile normale comme les deux + autres colonnes, redimensionnement/déplacement désactivés (voir le + garde useFloatingPanel() dans screen_edit.html). */ + .builderPanelRight{ position:static; flex:none; width:100%; height:auto; max-height:none; box-shadow:none; top:auto; right:auto; } + #propsPanelReopenBtn{ display:none !important; } } /* ---------- Logique de la scène : éditeur de flow à nœuds ---------- */ diff --git a/templates/screen_edit.html b/templates/screen_edit.html index dbdb503f..733f3702 100644 --- a/templates/screen_edit.html +++ b/templates/screen_edit.html @@ -142,8 +142,16 @@

Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner.

- -
+ + +
+
+ ⋮⋮ Propriétés + +
+
{% if not selected_id %}
Sélectionne un élément (sur l'écran ou dans la liste) pour voir et modifier ses propriétés ici.
{% else %} @@ -370,6 +378,7 @@ {% endif %} {% endif %} +
@@ -729,6 +738,89 @@ function restoreTreeCollapsedState() { }); } +// ---------- Panneau de propriétés : flottant, déplaçable, fermable ---------- +// +// Sorti du flux (position:fixed en CSS) pour ne jamais rétrécir le canevas +// selon où on le pose — le fermer libère toute cette largeur pour une +// prévisualisation plus fidèle à ce que "Jouer" affiche réellement (voir +// bug remonté : un panneau trop large réduisait le canevas au point de +// casser la mise en page des onglets). Position et état ouvert/fermé +// mémorisés par écran pour survivre à un changement de sélection (qui +// remplace tout #builder3, voir refreshBuilderPanel) ou un rechargement. +function propsPanelStateKey() { + return 'forgePropsPanel:' + GAME_SLUG + ':' + SCREEN_ID; +} + +function savePropsPanelState(state) { + try { + var current = JSON.parse(localStorage.getItem(propsPanelStateKey()) || '{}'); + localStorage.setItem(propsPanelStateKey(), JSON.stringify(Object.assign(current, state))); + } catch (e) {} +} + +function loadPropsPanelState() { + try { return JSON.parse(localStorage.getItem(propsPanelStateKey()) || '{}'); } catch (e) { return {}; } +} + +function restorePropsPanelState() { + var panel = document.getElementById('propsPanelFloat'); + var reopenBtn = document.getElementById('propsPanelReopenBtn'); + if (!panel) return; + var state = loadPropsPanelState(); + if (state.left != null && state.top != null) { + panel.style.left = state.left + 'px'; + panel.style.top = state.top + 'px'; + panel.style.right = 'auto'; + } + var open = state.open !== false; // ouvert par défaut tant que rien n'a été fermé + panel.classList.toggle('hidden', !open); + if (reopenBtn) reopenBtn.classList.toggle('visible', !open); +} + +function closePropsPanel() { + var panel = document.getElementById('propsPanelFloat'); + var reopenBtn = document.getElementById('propsPanelReopenBtn'); + if (panel) panel.classList.add('hidden'); + if (reopenBtn) reopenBtn.classList.add('visible'); + savePropsPanelState({ open: false }); +} + +function openPropsPanel() { + var panel = document.getElementById('propsPanelFloat'); + var reopenBtn = document.getElementById('propsPanelReopenBtn'); + if (panel) panel.classList.remove('hidden'); + if (reopenBtn) reopenBtn.classList.remove('visible'); + savePropsPanelState({ open: true }); +} + +if (!window.__forgePropsPanelDragBound) { + window.__forgePropsPanelDragBound = true; + document.addEventListener('mousedown', function (e) { + if (!e.target.closest('#propsPanelDragHandle') || e.target.closest('button')) return; + var panel = document.getElementById('propsPanelFloat'); + if (!panel) return; + var rect = panel.getBoundingClientRect(); + var startX = e.clientX, startY = e.clientY; + var startLeft = rect.left, startTop = rect.top; + panel.style.right = 'auto'; + function onMove(ev) { + var maxLeft = window.innerWidth - panel.offsetWidth - 4; + var maxTop = window.innerHeight - 40; + var left = Math.max(4, Math.min(maxLeft, startLeft + (ev.clientX - startX))); + var top = Math.max(4, Math.min(maxTop, startTop + (ev.clientY - startY))); + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + } + function onUp() { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + savePropsPanelState({ left: parseFloat(panel.style.left), top: parseFloat(panel.style.top) }); + } + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }); +} + // ---------- Surbrillance sur le canevas de l'élément sélectionné ---------- // // Un élément posé DIRECTEMENT sur l'écran a déjà son cadre de sélection via @@ -1132,6 +1224,7 @@ function initBuilderPanel() { bindJaugeDefinitionSelect(); restoreTreeCollapsedState(); applySelectionHighlight(CURRENT_SELECTED_ID); + restorePropsPanelState(); } // Jauge : quand on change l'objet de données à suivre SANS quitter le