Skip to content

Lock split-pair VFO panels to opposite sides (#2663). Principle III.#2744

Merged
ten9876 merged 1 commit into
mainfrom
aetherclaude/issue-2663
May 16, 2026
Merged

Lock split-pair VFO panels to opposite sides (#2663). Principle III.#2744
ten9876 merged 1 commit into
mainfrom
aetherclaude/issue-2663

Conversation

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Summary

Fixes #2663

What was changed

Lock split-pair VFO panels to opposite sides (#2663). Principle III.

Files modified

  • src/gui/SpectrumWidget.cpp
  • src/gui/VfoWidget.cpp
  • src/gui/VfoWidget.h
 src/gui/SpectrumWidget.cpp | 33 ++++++++++++++++++---------------
 src/gui/VfoWidget.cpp      | 19 +++++++++++++------
 src/gui/VfoWidget.h        |  9 ++++++++-
 3 files changed, 39 insertions(+), 22 deletions(-)

Generated by AetherClaude (automated agent for AetherSDR)

When VFO Split is active and the pair sits near a pan edge, the
RX/TX VFO badge panels overlapped each other. Two layers of
edge-clip flipping caused the collapse:

1. SpectrumWidget's split-pair branch flipped each partner
   independently when its marker came within panelW of an edge.
   At the right edge the right partner flipped ForceLeft, so both
   partners faced left and their panels overlapped (mirror image
   at the left edge).

2. VfoWidget::updatePosition's 20 px dead-band flipped the panel
   to the other side when ForceLeft/ForceRight would overrun the
   parent edge, defeating any caller that wanted strict locking.

Split pair side-locking is the user-visible spatial affordance —
RX on one side of its marker, TX on the other, never overlapping.
Both layers now honor that contract:

- Added VfoWidget::FlagDir::LockLeft / LockRight (strict — no
  dead-band flip). Existing ForceLeft/ForceRight/Auto callers are
  unchanged.
- Both render paths (GPU renderGpuFrame, software paintEvent) now
  drive split partners with LockLeft/LockRight and drop the
  panelW edge-clamp blocks.

The outward-facing panel may clip the pan edge at the band's far
edge; the user pans toward center to read it, as the reporter
already does today. This matches the maintainer's stated
preference (issue thread) — deconfliction belongs in a pan, not
in a side flip.

Constitution: Principle III (User-Facing Names Match The Visible
UI Labels) — the visible RX/TX spatial side-locking is part of
the user's mental model for SPLIT, and the prior flip violated it
silently.

Blast radius: codegraph impact on VfoWidget::updatePosition shows
risk_score=0.0095, paintEvent the sole caller. SpectrumWidget
paint-path edits are localized to the split-pair branch; the
header-file PreToolUse cascade reflects include reach, not
behavioral reach (the FlagDir change is strictly additive — new
enum values, all existing callers unchanged).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@aethersdr-agent aethersdr-agent Bot enabled auto-merge (squash) May 16, 2026 18:22
@aethersdr-agent aethersdr-agent Bot mentioned this pull request May 16, 2026
2 tasks

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looks good. Clean, focused fix:

  • LockLeft/LockRight cleanly extend FlagDir with a single lockedSide predicate gating the existing edge-clip flips in VfoWidget::updatePosition — no behavior change for Auto/ForceLeft/ForceRight callers.
  • Both the GPU (renderGpuFrame) and software (paintEvent) paths in SpectrumWidget are updated symmetrically for split partners, and the second block explicitly notes it mirrors the first.
  • The header comment on the enum captures the intent (edge-clip flip vs. hard lock) and the rationale (preventing RX/TX panels from collapsing onto the same side near a pan edge), which is exactly the kind of thing that would otherwise be re-derived during the next bug hunt.

No AppSettings/RAII concerns — this is pure widget layout. No null pointer risks introduced (parentWidget() is still guarded). Files are all within scope.

One small thing worth confirming visually when this lands: when a split pair is at the extreme pan edge, the outward-facing panel will now clip rather than flip. The comment says "the user pans toward center to read it" — make sure that's the desired behavior on a narrow panadapter where the clipping could be substantial. Not blocking; just a UX consideration.

Thanks @aethersdr-agent.

@ten9876 ten9876 merged commit f401779 into main May 16, 2026
5 checks passed
@ten9876 ten9876 deleted the aetherclaude/issue-2663 branch May 16, 2026 22:12
ten9876 added a commit that referenced this pull request May 17, 2026
…2784)

## Summary

Implements [the plan from
#2761](#2761). Replaces
incomplete agent attempt #2778 (which committed the API surface without
the implementation wiring).

When a slice tunes near a pan edge, the existing **Pan Follows VFO**
machinery slides the pan center so the slice stays visible. But the VFO
flag panel hanging off the slice marker is fixed-width — the slice can
be well inside the trigger boundary while the flag panel is already
clipping the pan edge. Split pairs (LockLeft + LockRight on the same
marker, #2663 / #2744) are particularly affected: PR #2744 intentionally
accepted that the outward-facing flag clips and documented \"the user
pans toward center to read it\" as the workaround.

This change removes that workaround by treating the **flag's outer
edge** as the trigger reference, not the slice frequency.

## What changes

- \`revealFrequencyIfNeeded\` gains \`leftFlagEdgeOffsetMhz\` /
\`rightFlagEdgeOffsetMhz\` defaulted to \`0.0\`. Only consumed on the
\`IncrementalTune\` path — \`CommandedTargetCenter\` /
\`RevealOffscreen\` ignore them.
- Offsets are clamped to 95% of \`triggerDistanceFromCenter\` so tiny
panadapters where flag-width > trigger-margin don't oscillate the pan
around the slice.
- \`panFollowVfo\` now computes flag-width-in-MHz from the bound
\`VfoWidget\` (\`width() * (bandwidthMhz / specPixelW)\`) and threads it
through:
- **Split pair** (\`SpectrumWidget::sliceHasSplitPartner\` returns
true): both offsets non-zero — extends the trigger on both sides since
LockLeft + LockRight flags render on both sides simultaneously.
- **Single-flag slice**: only the side where the flag currently renders
(per \`VfoWidget::onLeft()\`).
- **Collapsed / compact-mode flag**: both offsets stay 0.0 — compact
mode doesn't render a wide flag.

## Files

| File | Change |
|---|---|
| \`src/gui/MainWindow.h\` | \`revealFrequencyIfNeeded\` signature +
comment |
| \`src/gui/MainWindow.cpp\` | Implementation: clamp + apply offsets in
the trigger comparison; \`panFollowVfo\` computes offsets from VfoWidget
state |
| \`src/gui/SpectrumWidget.h\` | \`sliceHasSplitPartner(int)\` accessor
|
| \`src/gui/SpectrumWidget.cpp\` | \`sliceHasSplitPartner\`
implementation (scans \`m_sliceOverlays\` for the slice + checks
\`splitPartnerId\`) |
| \`src/gui/VfoWidget.h\` | \`onLeft()\` accessor exposing the existing
\`m_lastOnLeft\` state set by \`updatePosition\` |

Net: **85 insertions / 9 deletions** across 5 files.

## Local verification

- [x] Build clean (\`cmake --build build --target AetherSDR\`) — 401/401
link, only pre-existing warnings
- [x] Test-target link audit: none of the touched headers are linked by
isolated test targets — the new \`onLeft()\` accessor and
\`sliceHasSplitPartner()\` accessor are GUI-only and only consumed from
MainWindow.cpp. No CMakeLists changes needed.

## Test plan (manual, post-merge)

- [ ] Tune a slice toward a pan edge with a wide VFO flag visible — pan
should slide *before* the flag clips
- [ ] In split mode (LockLeft + LockRight on the same marker) — pan
should slide before *either* flag clips
- [ ] Non-flag callers (Memory recall, click-to-tune absolute jump) — no
behavior change (CommandedTargetCenter ignores the offsets)
- [ ] Compact-mode (collapsed) flag — falls back to original
slice-frequency comparison
- [ ] Toggle \`View → Pan Follows VFO\` off — pan does not slide at all
(existing override preserved)

## Closes

- Closes #2761
- Closes-as-replacement for #2778 (incomplete recovery)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Overflowing windows in split mode

1 participant