From a0e24fc925a2f57609b2e2a6123c78b748463569 Mon Sep 17 00:00:00 2001 From: livio Date: Mon, 5 Oct 2026 22:42:18 +0200 Subject: [PATCH] Smooth compass and strengthen map selection --- Components/MenuHelp.razor | 4 +- Components/Pages/Home.razor | 4 +- LMEMappeCatastaliApp.csproj | 4 +- wwwroot/css/app.css | 1 - wwwroot/js/lme-map.js | 93 +++++++++++++++++++++++++++++++++++-- 5 files changed, 96 insertions(+), 10 deletions(-) diff --git a/Components/MenuHelp.razor b/Components/MenuHelp.razor index 98be705..d2e7618 100644 --- a/Components/MenuHelp.razor +++ b/Components/MenuHelp.razor @@ -102,7 +102,7 @@ ], "tools" => [ - "Tabella oggetti: apre una vista ordinata di particelle, file importati e disegni. Usa i flag per selezionare piu righe, il lucchetto per proteggerle e la matita per modificare i metadati.", + "Tabella oggetti: apre una vista ordinata di particelle, file importati e disegni. Usa i flag per selezionare piu righe, evidenziate sulla mappa con contorno magenta e riempimento ciano; il lucchetto le protegge e la matita modifica i metadati.", "Colonna Note: il pulsante Note e sempre attivo e apre tutte le note dell'oggetto. La consultazione resta disponibile anche senza Pro; aggiunta, modifica ed eliminazione richiedono abbonamento o licenza manuale.", "Copia metadati: nella maschera Metadati oggetto scegli Copia da e l'oggetto sorgente. Attiva Importa anche i valori per copiare nomi e contenuti; disattivalo per creare soltanto gli stessi campi, lasciandoli vuoti o mantenendo i valori gia presenti.", "Coordinate WGS84 e UTM: scegli un punto sulla mappa per leggere latitudine e longitudine oppure le coordinate metriche UTM, utili per rilievi e confronti tecnici.", @@ -115,7 +115,7 @@ "settings" => [ "Mappa e sfondo: scegli la cartografia di base e regola Trasparenza catasto per leggere meglio confini e immagini sottostanti. Esempio: riducila quando le linee coprono dettagli del terreno.", - "Bussola: attiva Mostra bussola per visualizzare N, NE, E, SE, S, SW, W e NW. Puoi trascinarla nella posizione preferita. Con nord fisso la mappa non ruota e la lancetta indica il nord; in modalita Navigatore la mappa segue l'orientamento del dispositivo.", + "Bussola: attiva Mostra bussola per visualizzare N, NE, E, SE, S, SW, W e NW. Puoi trascinarla nella posizione preferita. La modalita predefinita mantiene il nord della mappa fisso in alto e la lancetta indica il nord con movimento continuo; in modalita Navigatore la mappa segue l'orientamento del dispositivo.", "Messaggi mappa: scegli dopo quanti secondi devono chiudersi automaticamente; imposta 0 per mantenerli finche non premi la X.", "Autosalvataggio locale dopo ogni modifica: se attivo, ogni disegno, modifica, rinomina o eliminazione aggiorna immediatamente una copia di recupero nel dispositivo. Per usarla apri Dati e disegni e premi Recupera ultimo autosalvataggio.", "Aggiorna automaticamente il progetto aperto: funziona solo dopo aver aperto o salvato un progetto modificabile. Dopo una modifica attende 3 secondi senza altre operazioni e aggiorna quel progetto; un progetto in sola lettura non viene mai sovrascritto.", diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor index 8675ecb..6bce3ed 100644 --- a/Components/Pages/Home.razor +++ b/Components/Pages/Home.razor @@ -681,7 +681,7 @@ @@ -2871,7 +2871,7 @@ { compass.ReadingChanged += OnCompassReadingChanged; CompassSensorSubscribed = true; - compass.Start(Microsoft.Maui.Devices.Sensors.SensorSpeed.UI); + compass.Start(Microsoft.Maui.Devices.Sensors.SensorSpeed.Game, applyLowPassFilter: true); } else if (!shouldMonitor && CompassSensorSubscribed) { diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj index ea742f1..2162b94 100644 --- a/LMEMappeCatastaliApp.csproj +++ b/LMEMappeCatastaliApp.csproj @@ -29,8 +29,8 @@ it.liviomerola.lmemappecatastali.pro - 1.0.22 - 122 + 1.0.23 + 123 $(ApplicationDisplayVersion) diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css index df74280..fb3fca3 100644 --- a/wwwroot/css/app.css +++ b/wwwroot/css/app.css @@ -1868,7 +1868,6 @@ label { transform-origin: 50% 100%; border-radius: 4px 4px 1px 1px; background: #ef4444; - transition: transform 120ms linear; } .map-compass-needle::after { diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js index 98625d3..434fbd8 100644 --- a/wwwroot/js/lme-map.js +++ b/wwwroot/js/lme-map.js @@ -70,6 +70,11 @@ const lmeState = { compassVisible: false, compassMode: "fixed", compassHeading: 0, + compassDisplayRotation: 0, + compassTargetRotation: 0, + compassHeadingInitialized: false, + compassAnimationFrame: null, + compassAnimationTime: 0, compassPosition: null, compassDragCleanup: null, compassResizeHandler: null, @@ -87,6 +92,7 @@ const lmeState = { managerHighlightedParentRecords: null, managerHoveredParentKey: null, managerHoveredParentRecords: null, + managerDirectHighlightStyles: new Map(), managerOutsideSelectionBound: false, managerHiddenObjects: new Map(), managerHiddenObjectIds: new Set(), @@ -496,6 +502,7 @@ function createCompassLayer() { updateCompassDisplay(); }, onRemove(map) { + stopCompassAnimation(); lmeState.compassDragCleanup?.(); lmeState.compassDragCleanup = null; if (lmeState.compassResizeHandler) { @@ -623,6 +630,7 @@ function setCompassOptions(visible, mode = "fixed") { lmeState.compassLayer.addTo(lmeState.map); } } else { + stopCompassAnimation(); lmeState.managerHiddenCategories.add("compass"); if (lmeState.map.hasLayer(lmeState.compassLayer)) { lmeState.map.removeLayer(lmeState.compassLayer); @@ -641,19 +649,67 @@ function setCompassOptions(visible, mode = "fixed") { function setCompassHeading(heading) { const parsed = Number(heading); if (!Number.isFinite(parsed)) return; - lmeState.compassHeading = ((parsed % 360) + 360) % 360; + const normalizedHeading = ((parsed % 360) + 360) % 360; + const requestedRotation = -normalizedHeading; + lmeState.compassHeading = normalizedHeading; + if (!lmeState.compassHeadingInitialized) { + lmeState.compassHeadingInitialized = true; + lmeState.compassDisplayRotation = requestedRotation; + lmeState.compassTargetRotation = requestedRotation; + updateCompassDisplay(); + } else { + const delta = shortestAngleDelta(lmeState.compassTargetRotation, requestedRotation); + if (Math.abs(delta) >= 0.15) { + lmeState.compassTargetRotation += delta; + startCompassAnimation(); + } + } if (lmeState.compassVisible && lmeState.compassMode === "heading") { lmeState.map?.setHeading?.(lmeState.compassHeading, { ease: 0.2, deadzone: 1 }); } +} + +function shortestAngleDelta(from, to) { + return ((((to - from) + 180) % 360) + 360) % 360 - 180; +} + +function startCompassAnimation() { + if (lmeState.compassAnimationFrame !== null) return; + lmeState.compassAnimationTime = 0; + lmeState.compassAnimationFrame = requestAnimationFrame(animateCompass); +} + +function animateCompass(timestamp) { + lmeState.compassAnimationFrame = null; + const elapsed = lmeState.compassAnimationTime + ? Math.min(50, Math.max(1, timestamp - lmeState.compassAnimationTime)) + : 16; + lmeState.compassAnimationTime = timestamp; + const difference = lmeState.compassTargetRotation - lmeState.compassDisplayRotation; + const smoothing = 1 - Math.exp(-elapsed / 85); + lmeState.compassDisplayRotation += difference * smoothing; + if (Math.abs(difference) < 0.05) { + lmeState.compassDisplayRotation = lmeState.compassTargetRotation; + lmeState.compassAnimationTime = 0; + } else { + lmeState.compassAnimationFrame = requestAnimationFrame(animateCompass); + } updateCompassDisplay(); } +function stopCompassAnimation() { + if (lmeState.compassAnimationFrame !== null) { + cancelAnimationFrame(lmeState.compassAnimationFrame); + lmeState.compassAnimationFrame = null; + } + lmeState.compassAnimationTime = 0; +} + function updateCompassDisplay() { const element = lmeState.compassElement; if (!element) return; - const rotation = -lmeState.compassHeading; element.classList.toggle("is-fixed", lmeState.compassMode === "fixed"); - element.style.setProperty("--compass-rotation", `${rotation}deg`); + element.style.setProperty("--compass-rotation", `${lmeState.compassDisplayRotation}deg`); element.setAttribute("aria-label", lmeState.compassMode === "fixed" ? `Bussola: mappa con nord fisso, orientamento ${Math.round(lmeState.compassHeading)} gradi` : `Bussola: nord, orientamento ${Math.round(lmeState.compassHeading)} gradi`); @@ -4085,7 +4141,9 @@ function renderSelectedManagerHighlight() { } function renderManagerHighlight(records) { + restoreDirectManagerHighlights(); lmeState.managerHighlight?.clearLayers(); + records.forEach(record => applyDirectManagerHighlight(record.layer)); const features = records.flatMap(record => { const geojson = record.layer?.toGeoJSON?.(); if (!geojson) { @@ -4112,7 +4170,36 @@ function renderManagerHighlight(records) { }).addTo(lmeState.managerHighlight); } +function applyDirectManagerHighlight(layer) { + if (!layer || typeof layer.setStyle !== "function" || lmeState.managerDirectHighlightStyles.has(layer)) return; + const options = layer.options || {}; + lmeState.managerDirectHighlightStyles.set(layer, { + color: options.color, + weight: options.weight, + opacity: options.opacity, + fillColor: options.fillColor, + fillOpacity: options.fillOpacity, + dashArray: options.dashArray ?? null + }); + layer.setStyle({ + color: "#ff00d4", + weight: Math.max(7, (Number(options.weight) || 2) + 4), + opacity: 1, + fillColor: "#00fff0", + fillOpacity: 0.34, + dashArray: null + }); +} + +function restoreDirectManagerHighlights() { + lmeState.managerDirectHighlightStyles.forEach((style, layer) => { + if (typeof layer?.setStyle === "function") layer.setStyle(style); + }); + lmeState.managerDirectHighlightStyles.clear(); +} + function clearManagerHighlight() { + restoreDirectManagerHighlights(); lmeState.managerHighlight?.clearLayers(); lmeState.managerHighlightedObjectId = null; lmeState.managerHighlightedCategoryKey = null;