fix(capture): Zoom/Pan vs Rotate gesture modes; drop dead ↻ overlay
Pinch/drag were applying zoom and tilt together on Measure. Add an explicit Zoom/Pan | Rotate segmented toggle (default Zoom/Pan). Stage gestures are exclusive per mode; tilt/zoom sliders stay available. Remove the bottom-center ↻ handle that sat on tick B (hitTick won, so the icon looked live but only moved the tick).
This commit is contained in:
@@ -37,6 +37,8 @@
|
||||
let speciesLoadErr = '';
|
||||
|
||||
let cropFill = getCropFill();
|
||||
/** Exclusive stage gestures: Zoom/Pan (default) vs Rotate. Sliders stay available either way. */
|
||||
let gestureMode: 'zoompan' | 'rotate' = 'zoompan';
|
||||
$: maskW = state.source.widthPx > 0 ? state.source.widthPx : 0;
|
||||
$: maskH = maskW > 0 ? state.approvedMask.bitset.length / maskW : 0;
|
||||
$: tape = state.axis.tape;
|
||||
@@ -200,12 +202,39 @@
|
||||
<p class="hint">Orient the slab on the fixed tape line, then enter length, thickness, and species.</p>
|
||||
<p class="nudge">The black line is <b>fixed</b>: tilting, dragging and zooming the slab never move it. It stays in the middle of the rectangle.</p>
|
||||
|
||||
<div class="mode-toggle" role="group" aria-label="Stage gesture mode">
|
||||
<button
|
||||
type="button"
|
||||
class="mode-btn"
|
||||
class:active={gestureMode === 'zoompan'}
|
||||
aria-pressed={gestureMode === 'zoompan'}
|
||||
data-testid="gesture-mode-zoompan"
|
||||
on:click={() => (gestureMode = 'zoompan')}
|
||||
>Zoom / Pan</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mode-btn"
|
||||
class:active={gestureMode === 'rotate'}
|
||||
aria-pressed={gestureMode === 'rotate'}
|
||||
data-testid="gesture-mode-rotate"
|
||||
on:click={() => (gestureMode = 'rotate')}
|
||||
>Rotate</button>
|
||||
</div>
|
||||
<p class="nudge mode-hint">
|
||||
{#if gestureMode === 'zoompan'}
|
||||
Pinch to zoom and drag to pan. Rotation gestures are ignored — switch to Rotate or use the tilt slider.
|
||||
{:else}
|
||||
Two-finger twist to rotate (or use the tilt slider). Pan and pinch-zoom are ignored in this mode.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if maskedPreview}
|
||||
<PhotoStage
|
||||
mode="tape"
|
||||
blob={maskedPreview}
|
||||
{tape}
|
||||
{cropFill}
|
||||
{gestureMode}
|
||||
onTapeChange={onTapeChange}
|
||||
/>
|
||||
{:else}
|
||||
@@ -227,7 +256,7 @@
|
||||
<button type="button" class="thumb" aria-label="Tilt right" on:click={() => nudgeTilt(1)}>+</button>
|
||||
<span class="val">{tiltShown}°</span>
|
||||
</div>
|
||||
<p class="nudge">±90° · ← → tilt 1° · Shift+← → 0.1° · two-finger twist · or drag the ↻ handle</p>
|
||||
<p class="nudge">±90° · slider always works · in Rotate mode: ← → tilt 1° · Shift+← → 0.1° · two-finger twist</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -408,6 +437,17 @@
|
||||
.panel { padding: 16px; color: #eee; }
|
||||
h1 { font-size: 1.3rem; margin: 0 0 8px; }
|
||||
.hint { color: #aaa; font-size: 0.9rem; margin: 0 0 8px; }
|
||||
.mode-toggle {
|
||||
display: flex; gap: 0; margin: 10px 0 4px;
|
||||
border: 1px solid #444; border-radius: 10px; overflow: hidden; width: fit-content; max-width: 100%;
|
||||
}
|
||||
.mode-btn {
|
||||
min-height: 44px; padding: 10px 18px; font-size: 0.95rem; font-weight: 600;
|
||||
border: 0; background: #1d1d1d; color: #aaa; cursor: pointer;
|
||||
}
|
||||
.mode-btn + .mode-btn { border-left: 1px solid #444; }
|
||||
.mode-btn.active { background: #00e5ff; color: #002; }
|
||||
.mode-hint { margin: 0 0 10px; }
|
||||
.nudge { color: #e8a; font-size: 0.74rem; margin: 6px 0 0; line-height: 1.45; }
|
||||
.preview-loading { min-height: 420px; border: 1px solid #333; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #999; text-align: center; padding: 16px; box-sizing: border-box; }
|
||||
.controls { margin-top: 16px; display: flex; flex-direction: column; gap: 14px; }
|
||||
|
||||
Reference in New Issue
Block a user