Improve virtual FlexControl wheel tuning#3029
Conversation
There was a problem hiding this comment.
Pull request overview
Enhances the virtual FlexControl wheel’s captured pointer tuning behavior and UI controls by adding a sensitivity setting and refining the capture/coast mechanics to reduce jumpiness and unintended coasting.
Changes:
- Added a persisted “Mouse Sensitivity” slider and applied sensitivity scaling to captured wheel rotation.
- Reduced jumpy tuning by clamping per-event pointer deltas, adding anchor logic near center, and separating pointer timing from coast timing.
- Reorganized the control strip layout and updated labels/tooltips for wheel tightness and sensitivity.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/gui/FlexControlDialog.h | Adds member storage for the new sensitivity slider. |
| src/gui/FlexControlDialog.cpp | Implements sensitivity persistence + tuning behavior changes and updates the control strip UI layout/tooltips. |
| AppSettings::instance().value("FlexControlVirtualWheelLooseness", "45").toInt(), | ||
| 0, 100); | ||
| m_wheel->setLooseness(looseness); | ||
| const int sensitivity = loadVirtualWheelSensitivity(); | ||
| m_wheel->setSensitivity(sensitivity); |
There was a problem hiding this comment.
Addressed in f09e8695: virtual wheel looseness and sensitivity are now stored together under FlexControlVirtualWheel, with a migration path that reads the legacy FlexControlVirtualWheelLooseness value, writes it into the JSON object, removes the legacy key, and saves the migrated settings.
ddad460 to
f09e869
Compare
f09e869 to
13d7940
Compare
There was a problem hiding this comment.
Thanks @rfoust — nice quality-of-life improvement. The wheel feels much better with the pointer-delta clamp + lazy re-anchoring, and decoupling m_pointerClock from m_spinClock is the right fix for the coast-restart problem.
A few small observations, nothing blocking:
Settings migration (good — Copilot concern resolved)
The consolidated FlexControlVirtualWheel JSON object with legacy FlexControlVirtualWheelLooseness migration matches the pattern used in src/models/AntennaAliasStore.cpp (raw → QJsonDocument::fromJson → toUtf8(), save via QJsonDocument(obj).toJson(Compact)). Looks consistent with project conventions.
Minor: load/save churn on slider drag
Every QSlider::valueChanged tick calls saveVirtualWheel{Looseness,Sensitivity}, which in turn re-parses the JSON, edits one field, re-serializes, and calls AppSettings::save(). During a slider drag this can be hundreds of parse/serialize/save cycles per second. The values are tiny so it's probably fine in practice, but you could either:
- coalesce writes (e.g. via
QTimer::singleShot(0, ...)or save only onsliderReleased), or - cache the parsed
QJsonObjectin the helper.
Up to you whether it's worth bothering with for two ints.
Minor: loadVirtualWheelSettings() called twice during construction
loadVirtualWheelLooseness() and loadVirtualWheelSensitivity() each re-parse the JSON (and each one re-runs the legacy-key migration check). Functionally correct, but trivially you could read both fields in one pass during the constructor. Again, micro-scale.
Tooltip consistency
The Wheel Tightness tooltip now mentions "Primarily intended for trackpads", but the new Mouse Sensitivity tooltip doesn't. If the trackpad guidance applies to both controls, worth mirroring; if not, ignore.
Scope / correctness
- Files limited to
FlexControlDialog.{cpp,h}— matches stated scope. ✓ AppSettings(notQSettings) used throughout. ✓m_wheelis null-guarded in both slider lambdas. ✓m_hasPointerAnchoris reset inreleaseCapture(). ✓sensitivityScale()atm_sensitivity = 50returns1.0, preserving the prior behavior as the midpoint. ✓
LGTM otherwise.
🤖 aethersdr-agent · cost: $4.2299 · model: claude-opus-4-7
… comments (#3047). Principle XI. PR #3034 introduced two comment blocks in src/gui/MainWindow.cpp tagged (#3029). That number refers to an unrelated FlexControl wheel-tuning PR that merged the same night; the meaningful reference for the panadapter dBm-range prime fix is #3034 itself. Correct both citations so code archaeology lands on the actual fix PR. Comment-only change; no behavior, no symbols touched.
Elevates two community contributors to Tier 3 owner status across the architecture & infrastructure paths: - @NF0T (Ryan) — shipped Windows-installer RADE integration; deep Windows-build expertise and FreeDV / digital-voice protocol background. Distinct from @tmiw (Mooneer, FreeDV project lead). - @rfoust (Robbie Foust) — shipped the FlexControl wheel tuning overhaul (#3029, merged today) with thorough adherence to project conventions; demonstrated solid grasp of the input/UX layer. Tier 3 paths covered (no change to which paths are at Tier 3): - src/gui/MainWindow.{cpp,h} (visual/UX direction) - src/models/RadioModel.{cpp,h} (central state, threading) - src/core/AudioEngine.{cpp,h} (audio pipeline) - src/core/PanadapterStream.{cpp,h} (VITA-49 protocol bedrock) - CMakeLists.txt (build configuration) - .github/codeql/ (security tooling) - .claude/commands/ (agent configuration) Tier 3 means: can approve PRs touching these paths; the bot (@AetherClaude) explicitly cannot. Tier 4 (maintainer-only) remains @ten9876-only — Ryan and Robbie do NOT gain approval rights on project-policy / repo-canon files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… T1, infra in T2 (#3053) ## Summary Final CODEOWNERS structure aligned to the maintainer-direction three-tier framework, with progressively narrower owner sets at higher tiers. All approvals human-only; @AetherClaude absent from every owner set. Final state in commit \`1aa9a446\` (8 commits total on the branch documenting the iterative refinement). ## Final tier structure | Tier | Owners | Coverage | |---|---|---| | **Tier 1** | @ten9876 | Governance/direction, security, bot policy, CI workflows | | **Tier 2** | @ten9876 + @jensenpat | Project infrastructure (docs, tests, GH tooling, build config) | | **Tier 3** | @ten9876 + @jensenpat + @NF0T + @rfoust | Source code (broad default) | Each tier has a progressively wider roster: - **T1 is single-approver** for governance/security/bot policy where direction needs to be authoritative. - **T2 narrows the source-review pool to 2** because infrastructure (docs, tests, CI, build config) needs deeper repo context than routine source review. - **T3 has the full 4-human roster** because routine source review benefits from more eyes. ## Detailed path coverage **Tier 1 — @ten9876:** - Governance/direction: \`AGENTS.md\`, \`CLAUDE.md\`, \`GEMINI.md\`, \`CONSTITUTION.md\`, \`GOVERNANCE.md\`, \`CONTRIBUTING.md\`, \`CODE_OF_CONDUCT.md\`, \`LICENSE\`, \`ROADMAP.md\` - Security/compliance: \`SECURITY.md\`, \`SECURITY-AUDIT.md\`, \`THIRD_PARTY_LICENSES\`, \`.github/CODEOWNERS\`, \`.github/codeql/\`, \`docs/RELEASE-SIGNING-KEY.pub.asc\` - CI runtime (kept here despite the framework's CI-in-T2 default — workflow changes can exfiltrate secrets or publish unsigned releases): \`.github/workflows/\` - Bot/agent instruction: \`.claude/commands/\` **Tier 2 — @ten9876 + @jensenpat:** - Documentation & tests: \`tests/\`, \`docs/\`, \`*.md\` (catches README.md, CHANGELOG.md, SUPPORT.md by fall-through) - GitHub-specific tooling: \`.github/dependabot.yml\`, \`.github/docker/\`, \`.github/ISSUE_TEMPLATE/\` - Build configuration: \`CMakeLists.txt\` **Tier 3 — @ten9876 + @jensenpat + @NF0T + @rfoust:** - \`*\` catches source and anything not enumerated: \`src/\`, \`third_party/\`, \`plugins/\`, \`hal-plugin/\`, \`resources/\`, \`packaging/\`, \`scripts/\` ## How last-match-wins resolves (post-merge) | File | Pattern that matches | Tier | Owner set | |---|---|---|---| | \`CLAUDE.md\` | T1: \`CLAUDE.md\` (overrides \`*.md\`) | T1 | @ten9876 | | \`README.md\` | T2: \`*.md\` (no T1 override) | T2 | @ten9876 + @jensenpat | | \`CHANGELOG.md\` | T2: \`*.md\` | T2 | @ten9876 + @jensenpat | | \`docs/install.md\` | T2: \`docs/\` (last match) | T2 | @ten9876 + @jensenpat | | \`.github/workflows/ci.yml\` | T1: \`.github/workflows/\` | T1 | @ten9876 | | \`.github/codeql/codeql-config.yml\` | T1: \`.github/codeql/\` | T1 | @ten9876 | | \`.claude/commands/loop.md\` | T1: \`.claude/commands/\` (overrides \`*.md\`) | T1 | @ten9876 | | \`src/gui/MainWindow.cpp\` | T3: \`*\` | T3 | 4 humans | | \`tests/foo.cpp\` | T2: \`tests/\` | T2 | @ten9876 + @jensenpat | | \`CMakeLists.txt\` | T2: \`CMakeLists.txt\` | T2 | @ten9876 + @jensenpat | ## Co-maintainer roster - **@NF0T (Ryan)** — Windows-installer RADE integration; Windows-build + FreeDV protocol background. Source-code Tier 3 only. - **@rfoust (Robbie Foust)** — FlexControl wheel tuning overhaul ([#3029](#3029)); input/UX layer expertise. Source-code Tier 3 only. ## Stats - 1 file change to \`.github/CODEOWNERS\` - 8 commits total on the branch documenting the iterative refinement ## Test plan - [x] Diff review confirms only \`.github/CODEOWNERS\` is touched - [x] \`grep -c AetherClaude .github/CODEOWNERS\` returns 1 (only the documentary inline mention) - [x] \`grep "@NF0T" .github/CODEOWNERS\` returns 2 matches (header doc + Tier 3 \`*\` line only) - [ ] GitHub validates CODEOWNERS syntax on push (red banner on PR if invalid) - [ ] After merge: PR touching \`CLAUDE.md\` requires \`@ten9876\` (T1) - [ ] After merge: PR touching \`README.md\` is approvable by @ten9876 or @jensenpat (T2 — narrower than before) - [ ] After merge: PR touching \`docs/foo.md\` requires @ten9876 or @jensenpat (T2) - [ ] After merge: PR touching \`.github/workflows/ci.yml\` requires \`@ten9876\` (T1) - [ ] After merge: PR touching \`src/gui/MainWindow.cpp\` approvable by any of 4 humans (T3) - [ ] After merge: bot-authored PRs cannot self-approve via CODEOWNERS regardless of path 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… comments (#3047). Principle XI. (#3048) ## Summary Fixes #3047 ### What was changed cleanup(panadapter): fix stale #3029 references to #3034 in dBm-prime comments (#3047). Principle XI. ### Files modified - `src/gui/MainWindow.cpp` ``` src/gui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $1.7186 · model: claude-opus-4-7</sub> Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
…ESC asymmetry) (#3103) ## Summary Changes AetherControl's virtual FlexControl knob from a click-to-latch / Escape-to-unlatch UX to a **double-click for both directions**, with Escape kept as a secondary safety release. ## Why Operators report losing track of the release path. First-time users miss the small "Press ESC to release" hint at the bottom of the dialog and feel stuck with the cursor captured. ## What changed | Action | Before | After | |---|---|---| | **Latch** | Single left-click on wheel | **Double-click** on wheel | | **Unlatch** | ESC key | **Double-click** on wheel (primary) or ESC (secondary safety) | | Single click on wheel | Latched | No-op (event accepted to suppress accidental window drag) | Updated user-visible strings: - Resting hint: `"Double-click the knob to capture circular tuning."` - Armed hint: `"Mouse locked to FlexControl. Double-click the knob to release (or press ESC)."` - Accessible description mirrors the new gesture. ESC stays as a backup for the edge case where the cursor ended up offscreen and can't get back over the knob to double-click. ## Attribution AetherControl original implementation by [@rfoust](https://github.com/rfoust) ([#2888](#2888)); FlexControl wheel UX overhaul also by @rfoust ([#3029](#3029)). This change preserves the existing wheel-feel work and only touches the capture/release gesture. ## Test plan - [x] Linux build clean - [ ] CI: build / check-paths / check-windows / CodeQL - [ ] Manual: open AetherControl (Settings → FlexControl), single-click on wheel does nothing, double-click captures, double-click again releases, ESC still releases as backup 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary - add a Mouse Sensitivity slider for captured virtual FlexControl wheel movement - reorganize the virtual wheel controls with centered Wheel Tightness and Mouse Sensitivity labels plus Tight/Loose and Less/More endpoint labels - reduce jumpy mouse tuning by clamping large single-event pointer deltas, lazily re-anchoring after near-center movement, and separating pointer timing from coast timing - prevent slow mouse movement from starting free coasting and clarify the tight/loose tooltip is primarily intended for trackpads ## Validation - git diff --check - cmake --build build --parallel
… T1, infra in T2 (aethersdr#3053) ## Summary Final CODEOWNERS structure aligned to the maintainer-direction three-tier framework, with progressively narrower owner sets at higher tiers. All approvals human-only; @AetherClaude absent from every owner set. Final state in commit \`1aa9a446\` (8 commits total on the branch documenting the iterative refinement). ## Final tier structure | Tier | Owners | Coverage | |---|---|---| | **Tier 1** | @ten9876 | Governance/direction, security, bot policy, CI workflows | | **Tier 2** | @ten9876 + @jensenpat | Project infrastructure (docs, tests, GH tooling, build config) | | **Tier 3** | @ten9876 + @jensenpat + @NF0T + @rfoust | Source code (broad default) | Each tier has a progressively wider roster: - **T1 is single-approver** for governance/security/bot policy where direction needs to be authoritative. - **T2 narrows the source-review pool to 2** because infrastructure (docs, tests, CI, build config) needs deeper repo context than routine source review. - **T3 has the full 4-human roster** because routine source review benefits from more eyes. ## Detailed path coverage **Tier 1 — @ten9876:** - Governance/direction: \`AGENTS.md\`, \`CLAUDE.md\`, \`GEMINI.md\`, \`CONSTITUTION.md\`, \`GOVERNANCE.md\`, \`CONTRIBUTING.md\`, \`CODE_OF_CONDUCT.md\`, \`LICENSE\`, \`ROADMAP.md\` - Security/compliance: \`SECURITY.md\`, \`SECURITY-AUDIT.md\`, \`THIRD_PARTY_LICENSES\`, \`.github/CODEOWNERS\`, \`.github/codeql/\`, \`docs/RELEASE-SIGNING-KEY.pub.asc\` - CI runtime (kept here despite the framework's CI-in-T2 default — workflow changes can exfiltrate secrets or publish unsigned releases): \`.github/workflows/\` - Bot/agent instruction: \`.claude/commands/\` **Tier 2 — @ten9876 + @jensenpat:** - Documentation & tests: \`tests/\`, \`docs/\`, \`*.md\` (catches README.md, CHANGELOG.md, SUPPORT.md by fall-through) - GitHub-specific tooling: \`.github/dependabot.yml\`, \`.github/docker/\`, \`.github/ISSUE_TEMPLATE/\` - Build configuration: \`CMakeLists.txt\` **Tier 3 — @ten9876 + @jensenpat + @NF0T + @rfoust:** - \`*\` catches source and anything not enumerated: \`src/\`, \`third_party/\`, \`plugins/\`, \`hal-plugin/\`, \`resources/\`, \`packaging/\`, \`scripts/\` ## How last-match-wins resolves (post-merge) | File | Pattern that matches | Tier | Owner set | |---|---|---|---| | \`CLAUDE.md\` | T1: \`CLAUDE.md\` (overrides \`*.md\`) | T1 | @ten9876 | | \`README.md\` | T2: \`*.md\` (no T1 override) | T2 | @ten9876 + @jensenpat | | \`CHANGELOG.md\` | T2: \`*.md\` | T2 | @ten9876 + @jensenpat | | \`docs/install.md\` | T2: \`docs/\` (last match) | T2 | @ten9876 + @jensenpat | | \`.github/workflows/ci.yml\` | T1: \`.github/workflows/\` | T1 | @ten9876 | | \`.github/codeql/codeql-config.yml\` | T1: \`.github/codeql/\` | T1 | @ten9876 | | \`.claude/commands/loop.md\` | T1: \`.claude/commands/\` (overrides \`*.md\`) | T1 | @ten9876 | | \`src/gui/MainWindow.cpp\` | T3: \`*\` | T3 | 4 humans | | \`tests/foo.cpp\` | T2: \`tests/\` | T2 | @ten9876 + @jensenpat | | \`CMakeLists.txt\` | T2: \`CMakeLists.txt\` | T2 | @ten9876 + @jensenpat | ## Co-maintainer roster - **@NF0T (Ryan)** — Windows-installer RADE integration; Windows-build + FreeDV protocol background. Source-code Tier 3 only. - **@rfoust (Robbie Foust)** — FlexControl wheel tuning overhaul ([aethersdr#3029](aethersdr#3029)); input/UX layer expertise. Source-code Tier 3 only. ## Stats - 1 file change to \`.github/CODEOWNERS\` - 8 commits total on the branch documenting the iterative refinement ## Test plan - [x] Diff review confirms only \`.github/CODEOWNERS\` is touched - [x] \`grep -c AetherClaude .github/CODEOWNERS\` returns 1 (only the documentary inline mention) - [x] \`grep "@NF0T" .github/CODEOWNERS\` returns 2 matches (header doc + Tier 3 \`*\` line only) - [ ] GitHub validates CODEOWNERS syntax on push (red banner on PR if invalid) - [ ] After merge: PR touching \`CLAUDE.md\` requires \`@ten9876\` (T1) - [ ] After merge: PR touching \`README.md\` is approvable by @ten9876 or @jensenpat (T2 — narrower than before) - [ ] After merge: PR touching \`docs/foo.md\` requires @ten9876 or @jensenpat (T2) - [ ] After merge: PR touching \`.github/workflows/ci.yml\` requires \`@ten9876\` (T1) - [ ] After merge: PR touching \`src/gui/MainWindow.cpp\` approvable by any of 4 humans (T3) - [ ] After merge: bot-authored PRs cannot self-approve via CODEOWNERS regardless of path 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#3034 in dBm-prime comments (aethersdr#3047). Principle XI. (aethersdr#3048) ## Summary Fixes aethersdr#3047 ### What was changed cleanup(panadapter): fix stale aethersdr#3029 references to aethersdr#3034 in dBm-prime comments (aethersdr#3047). Principle XI. ### Files modified - `src/gui/MainWindow.cpp` ``` src/gui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $1.7186 · model: claude-opus-4-7</sub> Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
…ESC asymmetry) (aethersdr#3103) ## Summary Changes AetherControl's virtual FlexControl knob from a click-to-latch / Escape-to-unlatch UX to a **double-click for both directions**, with Escape kept as a secondary safety release. ## Why Operators report losing track of the release path. First-time users miss the small "Press ESC to release" hint at the bottom of the dialog and feel stuck with the cursor captured. ## What changed | Action | Before | After | |---|---|---| | **Latch** | Single left-click on wheel | **Double-click** on wheel | | **Unlatch** | ESC key | **Double-click** on wheel (primary) or ESC (secondary safety) | | Single click on wheel | Latched | No-op (event accepted to suppress accidental window drag) | Updated user-visible strings: - Resting hint: `"Double-click the knob to capture circular tuning."` - Armed hint: `"Mouse locked to FlexControl. Double-click the knob to release (or press ESC)."` - Accessible description mirrors the new gesture. ESC stays as a backup for the edge case where the cursor ended up offscreen and can't get back over the knob to double-click. ## Attribution AetherControl original implementation by [@rfoust](https://github.com/rfoust) ([aethersdr#2888](aethersdr#2888)); FlexControl wheel UX overhaul also by @rfoust ([aethersdr#3029](aethersdr#3029)). This change preserves the existing wheel-feel work and only touches the capture/release gesture. ## Test plan - [x] Linux build clean - [ ] CI: build / check-paths / check-windows / CodeQL - [ ] Manual: open AetherControl (Settings → FlexControl), single-click on wheel does nothing, double-click captures, double-click again releases, ESC still releases as backup 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Validation