Skip to content

feat(esc): click-to-adjust polar display for phase and gain (#3091). Principle III.#3134

Merged
ten9876 merged 1 commit into
mainfrom
aetherclaude/issue-3091
May 25, 2026
Merged

feat(esc): click-to-adjust polar display for phase and gain (#3091). Principle III.#3134
ten9876 merged 1 commit into
mainfrom
aetherclaude/issue-3091

Conversation

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Summary

Fixes #3091

What was changed

feat(esc): click-to-adjust polar display for phase and gain (#3091). Principle III.

Files modified

  • src/gui/PhaseKnob.cpp
  • src/gui/PhaseKnob.h
  • src/gui/VfoWidget.cpp
 src/gui/PhaseKnob.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++++---
 src/gui/PhaseKnob.h   | 24 +++++++++++--
 src/gui/VfoWidget.cpp | 25 +++++++++++++
 3 files changed, 140 insertions(+), 7 deletions(-)

Generated by AetherClaude (automated agent for AetherSDR)


🤖 aethersdr-agent · cost: $13.9261 · model: claude-opus-4-7

…Principle III.

Make PhaseKnob interactive: clicking or dragging anywhere inside the
ESC polar circle now sets phase (angle) and gain (radius) in a single
action, matching SmartSDR's UX pattern. The widget emits
phaseChanged/gainChanged so VfoWidget can mirror the value into the
existing phase/gain sliders and push the un-quantized radian/gain to
the SliceModel. Slider signals are blocked during the mirror so the
slider→knob path doesn't bounce back.

Coordinate conversion is exposed as a static helper (deltaToPolar) for
unit testing; it is the algebraic inverse of the painted dot position
(angle = phase − π/2, radius = gain/2 · maxRadius).

Blast radius: risk_score=0.025, 2 high-risk affected
(top: VfoWidget::buildTabContent, MainWindow::onSliceAdded) — both are
the established integration points and only see additive signal wiring.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@aethersdr-agent aethersdr-agent Bot requested a review from a team as a code owner May 25, 2026 14:37
@aethersdr-agent aethersdr-agent Bot enabled auto-merge (squash) May 25, 2026 14:37

@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.

Nice, focused change — thanks for the work here. The polar↔(phase, gain) inverse math checks out against the paint code (angle = phase − π/2 forward, phase = atan2(dy, dx) + π/2 inverse), the static deltaToPolar is well-isolated for testing, and the slider/model wiring in VfoWidget correctly uses QSignalBlocker plus the existing m_updatingFromModel guard to avoid feedback loops. RAII, C++20 idioms, and Qt6 mouse-event API all look right.

A couple of small observations, none of them blockers:

  1. Center-click sets phase to π/2. When the user clicks (or releases) exactly at the center, atan2(0, 0) is 0, so phase snaps to π/2 even though the click conveys no angular intent. The visual is identical (gain == 0 puts the dot at center), but the persisted model phase changes. A tiny pixel deadband in updateFromMouse would preserve the prior phase in that case:

    constexpr double kDeadbandPx = 2.0;
    const double r = std::sqrt(dx*dx + dy*dy);
    float phase = m_phase;  // keep prior on center-click
    if (r > kDeadbandPx) {
        phase = std::atan2(dy, dx) + M_PI/2.0;
        // … normalize …
    }

    Worth deciding consciously; either choice is defensible.

  2. qFuzzyCompare(m_gain, gain) near zero. qFuzzyCompare's tolerance scales with the magnitude of the inputs, so it returns false for (0.0f, tiny) and can spuriously emit gainChanged when dragging across center. Quantization downstream makes this mostly benign, but an absolute epsilon (std::abs(a-b) > 1e-4f) would be more predictable for both phase and gain. Same point for m_phase near 0 / wraparound.

  3. deltaToPolar is described in the header as "exposed for unit tests" but no test was added. Worth a short follow-up under tests/ covering the four cardinal angles plus the clamp, since the function is pure and trivial to exercise.

LGTM as-is — the items above are polish that can land in a follow-up if you prefer.


🤖 aethersdr-agent · cost: $5.9672 · model: claude-opus-4-7

@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.

Clean implementation — the static deltaToPolar is a nice touch for testability, the QSignalBlocker pattern around the slider write is the right way to avoid the bounce-back, and the slight dot-size bump during drag is a tasteful affordance. Mouse handling uses the modern event->position() Qt6 API and accepts unhandled clicks correctly. Files in scope match the stated change.

A couple of small things worth a look — none blocking:

  1. Model update rate during drag. mouseMoveEventupdateFromMousesetEscPhaseShift / setEscGain fires on every mouse-move event, which on a fast device can be a lot of calls per second to the slice (and onward to the radio). The slider path was already step-quantized to 5° / 0.01 increments so was naturally throttled. Worth considering whether the knob should debounce — e.g. only push to the model when the value changes beyond a small epsilon, or rate-limit emissions. Probably fine in practice, but easy to verify by watching CAT/VITA traffic while dragging.

  2. Click at exact center → phase jumps to 90°. atan2(0, 0) returns 0, so phase = atan2(0,0) + π/2 = π/2. A user clicking dead-center to "reset" gain to zero will also rotate phase to 90°. A short-circuit at r ≈ 0 to leave phase unchanged would avoid the surprise:

if (r < some_small_threshold) {
    outPhase = /* keep existing */;  // or skip emit entirely
}

Thanks for the clear comment block in VfoWidget.cpp explaining the slider-blocker rationale and the raw-vs-quantized model write — makes the intent obvious on review.


🤖 aethersdr-agent · cost: $5.1334 · model: claude-opus-4-7

@ten9876 ten9876 merged commit f04a360 into main May 25, 2026
5 checks passed
@ten9876 ten9876 deleted the aetherclaude/issue-3091 branch May 25, 2026 16:00
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…r#3091). Principle III. (aethersdr#3134)

## Summary

Fixes aethersdr#3091

### What was changed

feat(esc): click-to-adjust polar display for phase and gain (aethersdr#3091).
Principle III.

### Files modified

- `src/gui/PhaseKnob.cpp`
- `src/gui/PhaseKnob.h`
- `src/gui/VfoWidget.cpp`

```
 src/gui/PhaseKnob.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++++---
 src/gui/PhaseKnob.h   | 24 +++++++++++--
 src/gui/VfoWidget.cpp | 25 +++++++++++++
 3 files changed, 140 insertions(+), 7 deletions(-)
```

---
Generated by AetherClaude (automated agent for AetherSDR)

---
<sub>🤖 aethersdr-agent · cost: $13.9261 · model: claude-opus-4-7</sub>

Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <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.

ESC Polar Display: Add Click-to-Adjust Interaction for Phase and Gain

1 participant