Skip to content

fix(spectrum): always draw bandwidth indicator at full brightness for non-active slices#3547

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
mvanhorn:fix/3484-bandwidth-indicator-visibility
Jun 14, 2026
Merged

fix(spectrum): always draw bandwidth indicator at full brightness for non-active slices#3547
ten9876 merged 2 commits into
aethersdr:mainfrom
mvanhorn:fix/3484-bandwidth-indicator-visibility

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

Summary

Fixes #3484. Bandwidth affordances (passband fill and filter-edge lines) in SpectrumWidget::drawSliceMarkers now always use the full-brightness active colour, regardless of which slice is currently active.

Previously both used sliceColorForOverlay(), which returns dimColor() on non-active slices - combining ~40% RGB with the low-alpha draw produced approximately 5.5% effective brightness, making the bandwidth indicator nearly invisible on inactive slices.

The fix introduces bandCol = SliceColorManager::activeColor(colourIdx) alongside the existing col = sliceColorForOverlay(so) and uses bandCol only for the two passband draw calls. Focus affordances (VFO centre line, triangle marker, RIT/XIT lines) retain the dimmed col to preserve the active-slice visual cue. Change is confined to src/gui/SpectrumWidget.cpp.

Constitution principle honored

Principle VIII - Evidence Over Assertion. The change traces directly to a user-reported regression (#3484). The color API used (SliceColorManager::activeColor(colourIdx)) matches the existing pattern inside sliceColorForOverlay() itself - no new API surface, no assertion-without-evidence.

Test plan

  • Local build passes (cmake --build build)
  • Open two slices on the same panadapter; switch focus to the second slice and verify the first slice's passband shading and filter-edge lines remain clearly visible at full brightness
  • Verify the non-active slice's VFO centre line and triangle marker remain visually dimmed
  • Existing tests pass (CI)

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls - use nested-JSON-under-one-key (Principle V)
  • All meter UI uses MeterSmoother (Principle II)
  • Documentation updated if user-visible behavior changed
  • Security-sensitive changes reference a GHSA if applicable

@mvanhorn mvanhorn requested a review from a team as a code owner June 13, 2026 07:25

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the change against the code. This is a clean, well-scoped fix — thanks @mvanhorn. 👍

Correctness verified. bandCol is reconstructed exactly the way sliceColorForOverlay() computes the active colour internally (SpectrumWidget.cpp:2642):

const int colourIdx = SliceLabel::displayColorIndex(so.sliceId, so.perClientLetter);
if (so.isActive) return SliceColorManager::instance().activeColor(colourIdx);
return SliceColorManager::instance().dimColor(colourIdx);

So SliceColorManager::instance().activeColor(colourIdx) reliably returns the full-brightness colour irrespective of so.isActive — the passband fill and filter-edge lines now stay visible on inactive slices, while the VFO centre line / triangle / RIT-XIT lines keep col (dimmed) to preserve the active-slice focus cue. That matches both the stated intent and #3484.

Conventions / safety: no AppSettings/QSettings involvement, no new API surface (reuses the same singleton already in scope), no null/resource concerns, and the change is confined to the single file as described. The explanatory comment is a nice touch for the next reader.

Minor (non-blocking nit): colourIdx is now computed twice for the same slice — once here and once inside sliceColorForOverlay(so). If you wanted, a tiny sliceBandColorForOverlay() helper alongside the existing one would centralize the index math, but it's genuinely optional and not worth churning the PR over.

Looks good to merge once CI is green.


🤖 aethersdr-agent · cost: $4.5458 · model: claude-opus-4-8

@NF0T NF0T self-assigned this Jun 13, 2026
@NF0T

NF0T commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

@jensenpat — tagging you because we turned up prior discussion that's directly relevant to this PR before it moves forward.

While reviewing #3547 I went back through the issue history and found two things worth surfacing:

1. This exact request was raised before and you weighed in on it

Issue #2389 ("Bandwidth barely visible on not active panadapter", filed 2026-05-05 by @kn7k) is the same user-facing complaint as #3484 and makes the same request. You responded there with an explicit design rationale:

"The VFO card is meant to be used to notify active status, the panadapter isn't used to signal that as it could cause the user to be confused and transmit into the wrong slice. This is why the bandwidth is greyed out today."

That context doesn't appear anywhere in this PR — mvanhorn likely wasn't aware of it. #2389 is still open and unresolved, and no design decision was ever formally recorded there.

2. The premultiplied-alpha bug (#3294 / #3297) complicates the picture

Some of the "barely visible" reports in the wild — including the macOS regression in #3419 — were actually caused by a separate GPU compositor bug that double-multiplied alpha, crushing effective passband brightness from ~13.7% to ~1.9%. That was fixed in af68d3df (PR #3297, merged 2026-06-02). After that fix the active-slice passband is clearly visible (~13.7% effective brightness), but inactive slices remain at ~5.5% because dimColor() (~40% RGB) is still applied on top of the low-alpha fill.

So the current behaviour post-#3297 is: the design intent (dim inactive slices) is working as intended, but the resulting ~5.5% brightness is arguably below the threshold where "dim" becomes "invisible." The question is whether that's an acceptable expression of the design intent or whether it's an unintended severity.

3. Governance note

Per GOVERNANCE.md, visual design changes and default UX behaviour changes require an [RFC] issue and maintainer sign-off before implementation. Given that you've previously stated a specific UX safety rationale for the current behaviour (TX-slice confusion), this feels like it warrants that process rather than a direct code change.

Happy to defer to your judgment here. If the design intent has evolved since #2389 and full-brightness bandwidth is now acceptable, that's great — but it would be helpful to capture that decision in #2389 (or a new RFC) so the rationale is on record. If the concern still stands, the author should be directed there before this merges.

mvanhorn and others added 2 commits June 13, 2026 21:08
… non-active slices

Split the colour used for bandwidth affordances (passband fill and
filter-edge lines) from the colour used for focus affordances in the
per-slice overlay draw path.  Previously both used sliceColorForOverlay(),
which applies dimColor() on non-active slices, making the passband shading
and filter edges nearly invisible at ~5.5% effective brightness.

Introduce bandCol = SliceColorManager::activeColor(colourIdx) so passband
fill and filter-edge lines always render at full slice colour regardless of
active state, while the VFO centre line, triangle marker, and RIT/XIT lines
retain the dimmed col to preserve the active-slice focus cue.

Fixes aethersdr#3484
…le + clearly inactive (aethersdr#3484/aethersdr#2389)

Refines the aethersdr#3547 approach per maintainer direction. The original fix drew the
inactive-slice passband/filter-edge affordances in the slice's full-brightness
colour, which fixed visibility (aethersdr#3484) but erased the active/inactive
distinction — reintroducing the TX-into-wrong-slice confusion @jensenpat
designed the dim against (aethersdr#2389).

Now colour, not brightness, signals inactive: an inactive slice's bandwidth
renders at full brightness in the neutral secondary colour
(`color.text.secondary`), while the active slice keeps its own colour. So the
passband is clearly visible AND obviously not the active/TX slice. Focus
affordances (VFO centre line, triangle, RIT/XIT) keep the dimmed `col`.

Uses the existing theme token (re-themes; no hardcoded hex, per the aethersdr#3102
token migration) — the same `color.text.secondary` already used for the other
neutral markers in this widget.

Co-authored-by: mvanhorn <mvanhorn@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 force-pushed the fix/3484-bandwidth-indicator-visibility branch from fd6d046 to 36d15f7 Compare June 14, 2026 04:10
@ten9876

ten9876 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

@mvanhorn — thanks for this, and for the clean tracing to #3484. I pushed a refinement on top (36d15f7c) and rebased onto current main (it was 17 behind; SpectrumWidget had been refactored under it — rebase was conflict-free, your authorship preserved).

The refinement — colour, not brightness, signals inactive. @NF0T surfaced that the dim on inactive-slice bandwidth is intentional (#2389): @jensenpat designed it so an inactive slice doesn't look TX-selectable. Drawing it in the slice's full-brightness colour (the original fix) fixed visibility but erased that cue. So instead: an inactive slice's bandwidth now renders at full brightness in the neutral secondary colour (color.text.secondary), while the active slice keeps its own colour. Net: the passband is clearly visible and obviously not the active/TX slice — both #3484 and #2389's concern are satisfied.

Implementation note: used the existing theme token (re-themes, no hardcoded hex per the #3102 migration) — the same color.text.secondary already used for the other neutral markers in this widget. Focus affordances (VFO line, triangle, RIT/XIT) keep the dimmed colour.

Verified: clean build against current main. Design decision recorded in #2389.

@ten9876 ten9876 merged commit 7ca6bac into aethersdr:main Jun 14, 2026
6 checks passed
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Thanks @ten9876 for taking this in. The bandwidth affordances staying full brightness should make the passband easier to read.

G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…ur, not dimming (aethersdr#3484/aethersdr#2389) (aethersdr#3547)

Inactive-slice bandwidth affordances (passband fill + filter edges) now render at full brightness in the neutral secondary colour instead of dimmed slice-colour. Restores visibility (aethersdr#3484) while colour — not brightness — signals inactive, preserving the TX-into-wrong-slice guard (aethersdr#2389). Theme-token based. Design decision recorded in aethersdr#2389.

Authored by @mvanhorn with a maintainer refinement (colour instead of brightness) + rebase.

Co-authored-by: mvanhorn <mvanhorn@users.noreply.github.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.

Make a bandwidth limits visible all the time

3 participants