Skip to content

feat(spectrum): Ctrl+wheel zoom anchored on cursor frequency (#1518)#2869

Merged
jensenpat merged 1 commit into
aethersdr:mainfrom
NF0T:fix/ctrl-wheel-zoom-panadapter
May 21, 2026
Merged

feat(spectrum): Ctrl+wheel zoom anchored on cursor frequency (#1518)#2869
jensenpat merged 1 commit into
aethersdr:mainfrom
NF0T:fix/ctrl-wheel-zoom-panadapter

Conversation

@NF0T
Copy link
Copy Markdown
Collaborator

@NF0T NF0T commented May 19, 2026

Summary

  • Ctrl+wheel now zooms the panadapter bandwidth in/out by ×1.5 per scroll step,
    anchoring on the frequency under the mouse cursor.
  • Plain wheel scroll is unchanged — it continues to tune the VFO.

Implementation

Single change in SpectrumWidget::wheelEvent: after the debounce/step
calculation, an early branch catches Qt::ControlModifier and runs the
cursor-anchored zoom, then returns before the existing VFO tune path.

The zoom math mirrors the existing NativeGesture pinch-to-zoom exactly:

const double mouseXFrac = ev->position().x() / width() - 0.5;
const double anchorMhz  = m_centerMhz + mouseXFrac * m_bandwidthMhz;
const double newCenter  = std::max(anchorMhz - mouseXFrac * newBw, newBw / 2.0);

The std::max(..., newBw / 2.0) clamp prevents the left edge from going
below 0 Hz, consistent with PR #2867.

UX consistency note

The existing +/ zoom buttons use a different anchor policy: zoom-in
re-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

  • Scroll wheel without Ctrl: VFO tunes as before, no regression.
  • Ctrl+scroll up: bandwidth narrows, cursor frequency stays fixed.
  • Ctrl+scroll down: bandwidth widens, cursor frequency stays fixed.
  • Ctrl+scroll at band edge: 0 Hz clamp prevents left edge going negative.
  • Ctrl+scroll at BW limits: clamped to m_minBwMhz/m_maxBwMhz, no crash.
  • Pinch-to-zoom: unaffected.

Fixes #1518

…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>
@NF0T NF0T requested review from jensenpat and ten9876 as code owners May 19, 2026 01:13
Copy link
Copy Markdown
Contributor

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 == 0 early 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.

Copy link
Copy Markdown
Collaborator

@jensenpat jensenpat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@jensenpat jensenpat merged commit aa371ae into aethersdr:main May 21, 2026
5 checks passed
@NF0T NF0T deleted the fix/ctrl-wheel-zoom-panadapter branch May 21, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ctrl + mouse wheel does not adjust zoom in panadapter

2 participants