feat(spectrum): Ctrl+wheel zoom anchored on cursor frequency (#1518)#2869
Conversation
…dr#1518) Ctrl+wheel now zooms the panadapter bandwidth in/out by 1.5× per step, keeping the frequency under the cursor fixed (same anchor math as the existing pinch-to-zoom gesture). Plain wheel scroll continues to tune the VFO unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Looks good — thanks @NF0T. The implementation cleanly mirrors the existing NativeGesture pinch-to-zoom path (SpectrumWidget.cpp:3948) and reuses the same reproject/repaint state updates as the existing zoom code paths, so behavior should be visually identical to pinch-zoom but driven by Ctrl+wheel.
A few small things I checked and like:
- The
steps == 0early return at line 4072 still runs first, so the existing pixelDelta/angleDelta debounce and ±1 clamp apply to Ctrl+wheel too — runaway zoom on inflated KDE/Cinnamon wheel deltas is already mitigated for free. - The inline
std::max(..., newBw / 2.0)clamp keeps this PR independent of #2867 — works correctly whether that lands first or not. qFuzzyCompare(newBw, m_bandwidthMhz)correctly handles the "already at BW limit" no-op.- Ctrl-modifier check happens before the VFO tune path, so plain wheel scroll is unchanged — no regression risk for the common case.
The UX-consistency note in the PR body is the right call: cursor-anchored zoom for continuous interactions, VFO-centered for discrete button clicks. Matches every spectrum/map app convention.
Nothing to change.
jensenpat
left a comment
There was a problem hiding this comment.
Doesn't touch slice 0 RX flow, audio, or command ordering.
Main-thread only (wheelEvent → main-thread member writes). No cross-thread atomicity concern.
No protocol changes; radio sees the same frequencyRangeChangeRequested it already receives from the buttons/pinch.
No QSettings introduced.
Test plan covers golden path + edge cases (band edge clamp, BW limits, regression check of plain wheel and pinch).
Thanks Ryan
Summary
anchoring on the frequency under the mouse cursor.
Implementation
Single change in
SpectrumWidget::wheelEvent: after the debounce/stepcalculation, an early branch catches
Qt::ControlModifierand runs thecursor-anchored zoom, then returns before the existing VFO tune path.
The zoom math mirrors the existing NativeGesture pinch-to-zoom exactly:
The
std::max(..., newBw / 2.0)clamp prevents the left edge from goingbelow 0 Hz, consistent with PR #2867.
UX consistency note
The existing
+/−zoom buttons use a different anchor policy: zoom-inre-centers on the active VFO frequency (added in #1932 to prevent repeated
clicks pushing the slice off-screen), zoom-out keeps the pan center fixed.
That behavior makes sense for a discrete click where the cursor position
carries no intent.
Ctrl+wheel is a continuous, cursor-positioned interaction — the user is
looking at a specific part of the spectrum when they scroll. Cursor-anchored
zoom is therefore the correct policy here, matching the NativeGesture
pinch-to-zoom and every major spectrum/map application that implements
scroll-to-zoom. The button inconsistency is deliberate and acceptable.
Ctrl is already the modifier used for dBm-scale drag (PR #2717) and
waterfall time-scale drag (PR #2783), so Ctrl+wheel is consistent with
the existing convention of Ctrl = “modify the view, not the VFO.”
Testing
m_minBwMhz/m_maxBwMhz, no crash.Fixes #1518