Pannello visione: zoom, Adatta e trascinamento sull'immagine
Refs #115 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,13 @@ function visionExecute() {
|
||||
looping: false, // an in-line loop is running
|
||||
gen: 0, // bumped by «Registra»: answers of older requests are dropped
|
||||
|
||||
// #115: zoom del riquadro immagine (stesso schema del wizard DXF, #113). zoom = px schermo per px immagine;
|
||||
// fit = zoom di «Adatta»; w/h = dimensioni naturali; auto = la vista e' ad Adatta (segue il resize del riquadro)
|
||||
z: { zoom: 1, fit: 1, w: 0, h: 0, auto: true },
|
||||
zPanning: false, // #115: trascinamento in corso (cursore grabbing)
|
||||
_zPan: null,
|
||||
_zRO: null,
|
||||
|
||||
// #97: start on a frame already acquired and analysed; hook visibility and exit.
|
||||
init() {
|
||||
if (!this.hasCamera) return;
|
||||
@@ -263,6 +270,115 @@ function visionExecute() {
|
||||
});
|
||||
},
|
||||
|
||||
// #115: load dell'immagine (anche a ogni frame in linea): dimensioni naturali, fit solo se serve, poi overlay
|
||||
onImgLoad() {
|
||||
const img = this.$refs.img;
|
||||
if (img && img.naturalWidth) {
|
||||
const z = this.z;
|
||||
const changed = z.w !== img.naturalWidth || z.h !== img.naturalHeight;
|
||||
z.w = img.naturalWidth;
|
||||
z.h = img.naturalHeight;
|
||||
// dimensioni uguali e vista ingrandita: il frame nuovo resta ingrandito (in linea ogni frame ricarica l'immagine)
|
||||
if (changed || z.auto) this.zFit();
|
||||
this._zObserve();
|
||||
}
|
||||
this.drawOverlay();
|
||||
},
|
||||
|
||||
// #115: osservatore di resize sul viewport: se la vista e' ad Adatta (o il riquadro era nascosto) rifa il fit
|
||||
_zObserve() {
|
||||
const vp = this.$refs.vp;
|
||||
if (!vp || this._zRO || typeof ResizeObserver === 'undefined') return;
|
||||
this._zRO = new ResizeObserver(() => { if (this.z.w > 0 && this.z.auto) this.zFit(); });
|
||||
this._zRO.observe(vp);
|
||||
},
|
||||
|
||||
// #115: area utile del viewport; offset meno i bordi (client* escludono le barre di scorrimento che spariranno dopo il fit, come #113)
|
||||
_zArea() {
|
||||
const vp = this.$refs.vp;
|
||||
if (!vp) return { cw: 0, ch: 0 };
|
||||
let cw = vp.clientWidth, ch = vp.clientHeight;
|
||||
if (vp.offsetWidth && typeof getComputedStyle === 'function') {
|
||||
const cs = getComputedStyle(vp);
|
||||
cw = vp.offsetWidth - (parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth) || 0);
|
||||
ch = vp.offsetHeight - (parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth) || 0);
|
||||
}
|
||||
return { cw, ch };
|
||||
},
|
||||
|
||||
// #115: Adatta = tutta l'immagine nel riquadro (come il fit di #96)
|
||||
zFit() {
|
||||
const vp = this.$refs.vp, z = this.z;
|
||||
if (!vp || !z.w || !z.h) return;
|
||||
const a = this._zArea();
|
||||
if (!(a.cw > 0) || !(a.ch > 0)) return; // riquadro nascosto o vuoto: niente, ci pensa l'osservatore
|
||||
const f = Math.min(a.cw / z.w, a.ch / z.h);
|
||||
z.fit = f;
|
||||
z.zoom = f;
|
||||
z.auto = true;
|
||||
vp.scrollLeft = 0;
|
||||
vp.scrollTop = 0;
|
||||
},
|
||||
|
||||
// #115: zoom di un fattore tenendo fermo il punto (cx, cy) del viewport (default: il centro)
|
||||
zoomBy(f, cx, cy) {
|
||||
const vp = this.$refs.vp, z = this.z;
|
||||
if (!vp || !z.w) return;
|
||||
const old = z.zoom;
|
||||
const nz = Math.min(Math.max(8, z.fit), Math.max(z.fit * 0.5, old * f));
|
||||
if (nz === old) return;
|
||||
if (cx == null) cx = vp.clientWidth / 2;
|
||||
if (cy == null) cy = vp.clientHeight / 2;
|
||||
const left = vp.scrollLeft, top = vp.scrollTop;
|
||||
z.zoom = nz;
|
||||
z.auto = false;
|
||||
const apply = () => {
|
||||
vp.scrollLeft = (left + cx) * nz / old - cx;
|
||||
vp.scrollTop = (top + cy) * nz / old - cy;
|
||||
};
|
||||
if (typeof this.$nextTick === 'function') this.$nextTick(apply); else apply();
|
||||
},
|
||||
|
||||
// #115: Ctrl+rotella = zoom sul punto del cursore; senza Ctrl la pagina scorre come sempre
|
||||
zWheel(ev) {
|
||||
if (!ev.ctrlKey) return;
|
||||
ev.preventDefault();
|
||||
const vp = this.$refs.vp;
|
||||
let cx, cy;
|
||||
if (vp && vp.getBoundingClientRect) {
|
||||
const r = vp.getBoundingClientRect();
|
||||
cx = ev.clientX - r.left;
|
||||
cy = ev.clientY - r.top;
|
||||
}
|
||||
this.zoomBy(ev.deltaY < 0 ? 1.25 : 1 / 1.25, cx, cy);
|
||||
},
|
||||
|
||||
zCanPan() { const z = this.z; return z.w > 0 && z.zoom > z.fit * 1.0001; },
|
||||
zCursor() { return this.zCanPan() ? (this.zPanning ? 'grabbing' : 'grab') : 'default'; },
|
||||
zPct() { return Math.round(this.z.zoom * 100) + '%'; },
|
||||
// #115: dimensione del wrapper: naturale x zoom; prima del load riempie il riquadro
|
||||
zSize() {
|
||||
const z = this.z;
|
||||
return z.w ? 'width:' + (z.w * z.zoom) + 'px;height:' + (z.h * z.zoom) + 'px' : 'width:100%;height:100%';
|
||||
},
|
||||
|
||||
// #115: trascinamento = scorrimento del viewport (solo se ingrandito)
|
||||
zPanDown(ev) {
|
||||
const vp = this.$refs.vp;
|
||||
if (!vp || !this.zCanPan() || (ev.pointerType === 'mouse' && ev.button)) return;
|
||||
if (ev.preventDefault) ev.preventDefault();
|
||||
this._zPan = { x: ev.clientX, y: ev.clientY, left: vp.scrollLeft, top: vp.scrollTop };
|
||||
this.zPanning = true;
|
||||
try { vp.setPointerCapture(ev.pointerId); } catch (e) { /* puntatore gia' rilasciato */ }
|
||||
},
|
||||
zPanMove(ev) {
|
||||
const vp = this.$refs.vp, p = this._zPan;
|
||||
if (!p || !vp) return;
|
||||
vp.scrollLeft = p.left - (ev.clientX - p.x);
|
||||
vp.scrollTop = p.top - (ev.clientY - p.y);
|
||||
},
|
||||
zPanEnd() { this._zPan = null; this.zPanning = false; },
|
||||
|
||||
statusClass(s) {
|
||||
// Same palette as the measurement markers of the manual flow.
|
||||
if (s === 'pass') return 'bg-measure-pass/20 text-measure-pass border border-measure-pass/40';
|
||||
|
||||
Reference in New Issue
Block a user