[feat](trx-frontend-http): GPU-composited CSS overlay for instant freq/BW updates
Replace synchronous drawSignalOverlay() calls in freq/BW change handlers with lightweight CSS div elements repositioned via transform: translateX(). This is GPU-composited with zero layout/paint cost, making frequency and bandwidth changes appear instantaneous. The full WebGL overlay catches up on the next requestAnimationFrame. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -1668,6 +1668,71 @@ function resetWfmStereoIndicator() {
|
|||||||
wfmStFlagEl.classList.add("wfm-st-flag-mono");
|
wfmStFlagEl.classList.add("wfm-st-flag-mono");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Fast CSS-based frequency/BW marker positioning ──────────────────────────
|
||||||
|
// These lightweight DOM elements reposition via `transform: translateX()`
|
||||||
|
// which is GPU-composited — zero layout/paint cost. The full WebGL overlay
|
||||||
|
// (drawSignalOverlay) catches up on the next rAF.
|
||||||
|
const _fastFreqMarker = document.getElementById("fast-freq-marker");
|
||||||
|
const _fastBwLeft = document.getElementById("fast-bw-left");
|
||||||
|
const _fastBwRight = document.getElementById("fast-bw-right");
|
||||||
|
|
||||||
|
function positionFastOverlay(freqHz, bwHz) {
|
||||||
|
if (!lastSpectrumData || !signalVisualBlockEl) {
|
||||||
|
if (_fastFreqMarker) _fastFreqMarker.style.display = "none";
|
||||||
|
if (_fastBwLeft) _fastBwLeft.style.display = "none";
|
||||||
|
if (_fastBwRight) _fastBwRight.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cssW = signalVisualBlockEl.clientWidth;
|
||||||
|
if (cssW <= 0) return;
|
||||||
|
const range = spectrumVisibleRange(lastSpectrumData);
|
||||||
|
const hzToFrac = (hz) => (hz - range.visLoHz) / range.visSpanHz;
|
||||||
|
|
||||||
|
if (_fastFreqMarker && Number.isFinite(freqHz)) {
|
||||||
|
const frac = hzToFrac(freqHz);
|
||||||
|
if (frac >= 0 && frac <= 1) {
|
||||||
|
_fastFreqMarker.style.display = "";
|
||||||
|
_fastFreqMarker.style.transform = `translateX(${frac * cssW}px)`;
|
||||||
|
} else {
|
||||||
|
_fastFreqMarker.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_fastBwLeft && _fastBwRight && Number.isFinite(freqHz) && Number.isFinite(bwHz) && bwHz > 0) {
|
||||||
|
const side = sidebandDirectionForMode(modeEl ? modeEl.value : "USB");
|
||||||
|
let loHz, hiHz;
|
||||||
|
if (side < 0) {
|
||||||
|
loHz = freqHz - bwHz; hiHz = freqHz;
|
||||||
|
} else if (side > 0) {
|
||||||
|
loHz = freqHz; hiHz = freqHz + bwHz;
|
||||||
|
} else {
|
||||||
|
loHz = freqHz - bwHz / 2; hiHz = freqHz + bwHz / 2;
|
||||||
|
}
|
||||||
|
const lFrac = hzToFrac(loHz);
|
||||||
|
const rFrac = hzToFrac(hiHz);
|
||||||
|
const cFrac = hzToFrac(freqHz);
|
||||||
|
// Left side of BW
|
||||||
|
if (lFrac < cFrac && cFrac >= 0 && lFrac <= 1) {
|
||||||
|
const x = Math.max(0, lFrac) * cssW;
|
||||||
|
const w = (Math.min(1, cFrac) - Math.max(0, lFrac)) * cssW;
|
||||||
|
_fastBwLeft.style.display = "";
|
||||||
|
_fastBwLeft.style.transform = `translateX(${x}px)`;
|
||||||
|
_fastBwLeft.style.width = `${w}px`;
|
||||||
|
} else {
|
||||||
|
_fastBwLeft.style.display = "none";
|
||||||
|
}
|
||||||
|
// Right side of BW
|
||||||
|
if (rFrac > cFrac && rFrac >= 0 && cFrac <= 1) {
|
||||||
|
const x = Math.max(0, cFrac) * cssW;
|
||||||
|
const w = (Math.min(1, rFrac) - Math.max(0, cFrac)) * cssW;
|
||||||
|
_fastBwRight.style.display = "";
|
||||||
|
_fastBwRight.style.transform = `translateX(${x}px)`;
|
||||||
|
_fastBwRight.style.width = `${w}px`;
|
||||||
|
} else {
|
||||||
|
_fastBwRight.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyLocalTunedFrequency(hz, forceDisplay = false) {
|
function applyLocalTunedFrequency(hz, forceDisplay = false) {
|
||||||
if (!Number.isFinite(hz)) return;
|
if (!Number.isFinite(hz)) return;
|
||||||
const freqChanged = lastFreqHz !== hz;
|
const freqChanged = lastFreqHz !== hz;
|
||||||
@@ -1693,11 +1758,9 @@ function applyLocalTunedFrequency(hz, forceDisplay = false) {
|
|||||||
if (window.refreshCwTonePicker) {
|
if (window.refreshCwTonePicker) {
|
||||||
window.refreshCwTonePicker();
|
window.refreshCwTonePicker();
|
||||||
}
|
}
|
||||||
|
// Instant CSS marker repositioning (GPU-composited, no WebGL).
|
||||||
|
positionFastOverlay(lastFreqHz, currentBandwidthHz);
|
||||||
if (lastSpectrumData) {
|
if (lastSpectrumData) {
|
||||||
// Redraw the signal/BW overlay immediately so the frequency marker and
|
|
||||||
// bandwidth picker move without waiting for the next spectrum frame or
|
|
||||||
// requestAnimationFrame callback.
|
|
||||||
drawSignalOverlay();
|
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
}
|
}
|
||||||
positionRdsPsOverlay();
|
positionRdsPsOverlay();
|
||||||
@@ -3788,8 +3851,8 @@ async function applyBwDefaultForMode(mode, sendToServer) {
|
|||||||
currentBandwidthHz = def;
|
currentBandwidthHz = def;
|
||||||
window.currentBandwidthHz = currentBandwidthHz;
|
window.currentBandwidthHz = currentBandwidthHz;
|
||||||
syncBandwidthInput(def);
|
syncBandwidthInput(def);
|
||||||
|
positionFastOverlay(lastFreqHz, def);
|
||||||
if (lastSpectrumData) {
|
if (lastSpectrumData) {
|
||||||
drawSignalOverlay();
|
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
}
|
}
|
||||||
if (sendToServer) {
|
if (sendToServer) {
|
||||||
@@ -3810,8 +3873,8 @@ async function applyBandwidthFromInput() {
|
|||||||
currentBandwidthHz = clamped;
|
currentBandwidthHz = clamped;
|
||||||
window.currentBandwidthHz = currentBandwidthHz;
|
window.currentBandwidthHz = currentBandwidthHz;
|
||||||
syncBandwidthInput(clamped);
|
syncBandwidthInput(clamped);
|
||||||
|
positionFastOverlay(lastFreqHz, clamped);
|
||||||
if (lastSpectrumData) {
|
if (lastSpectrumData) {
|
||||||
drawSignalOverlay();
|
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -3888,8 +3951,8 @@ async function applyAutoBandwidth() {
|
|||||||
currentBandwidthHz = estimated;
|
currentBandwidthHz = estimated;
|
||||||
window.currentBandwidthHz = currentBandwidthHz;
|
window.currentBandwidthHz = currentBandwidthHz;
|
||||||
syncBandwidthInput(estimated);
|
syncBandwidthInput(estimated);
|
||||||
|
positionFastOverlay(lastFreqHz, estimated);
|
||||||
if (lastSpectrumData) {
|
if (lastSpectrumData) {
|
||||||
drawSignalOverlay();
|
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -9725,7 +9788,7 @@ if (spectrumCanvas || overviewCanvas) {
|
|||||||
currentBandwidthHz = newBw;
|
currentBandwidthHz = newBw;
|
||||||
window.currentBandwidthHz = currentBandwidthHz;
|
window.currentBandwidthHz = currentBandwidthHz;
|
||||||
syncBandwidthInput(newBw);
|
syncBandwidthInput(newBw);
|
||||||
drawSignalOverlay();
|
positionFastOverlay(lastFreqHz, newBw);
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
scheduleOverviewDraw();
|
scheduleOverviewDraw();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -160,6 +160,9 @@
|
|||||||
<span id="signal-split-value" aria-live="polite">50/50</span>
|
<span id="signal-split-value" aria-live="polite">50/50</span>
|
||||||
</div>
|
</div>
|
||||||
<canvas id="signal-overlay-canvas" aria-hidden="true"></canvas>
|
<canvas id="signal-overlay-canvas" aria-hidden="true"></canvas>
|
||||||
|
<div id="fast-freq-marker" aria-hidden="true"></div>
|
||||||
|
<div id="fast-bw-left" aria-hidden="true"></div>
|
||||||
|
<div id="fast-bw-right" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<div class="full-row freq-row">
|
<div class="full-row freq-row">
|
||||||
|
|||||||
@@ -675,6 +675,35 @@ small { color: var(--text-muted); }
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
|
#fast-freq-marker {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
background: #ff1744;
|
||||||
|
opacity: 0.85;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 5;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
#fast-bw-left, #fast-bw-right {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
|
will-change: transform, width;
|
||||||
|
}
|
||||||
|
#fast-bw-left {
|
||||||
|
background: linear-gradient(to right, transparent, rgba(255,23,68,0.10));
|
||||||
|
}
|
||||||
|
#fast-bw-right {
|
||||||
|
background: linear-gradient(to left, transparent, rgba(255,23,68,0.10));
|
||||||
|
}
|
||||||
#rds-ps-overlay {
|
#rds-ps-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
Reference in New Issue
Block a user