commit no. 9

This commit is contained in:
2026-03-25 19:48:36 +01:00
parent 740f957b7c
commit 1670e4eea6

View File

@@ -375,14 +375,12 @@ void Application::render() {
ImGui::SameLine(); ImGui::SameLine();
} }
// Spectrum + Waterfall with draggable splitter // Waterfall (top) + Spectrum (bottom) with draggable splitter
ImGui::BeginChild("Display", {contentW, contentH}, false); ImGui::BeginChild("Display", {contentW, contentH}, false);
{ {
constexpr float kSplitterH = 6.0f; constexpr float kSplitterH = 6.0f;
float specH = contentH * spectrumFrac_;
float waterfH = contentH - specH - kSplitterH;
renderSpectrumPanel(); renderWaterfallPanel();
// ── Draggable splitter bar ── // ── Draggable splitter bar ──
ImVec2 splPos = ImGui::GetCursorScreenPos(); ImVec2 splPos = ImGui::GetCursorScreenPos();
@@ -395,7 +393,8 @@ void Application::render() {
if (active) { if (active) {
float dy = ImGui::GetIO().MouseDelta.y; float dy = ImGui::GetIO().MouseDelta.y;
spectrumFrac_ += dy / contentH; // Dragging down = more waterfall = less spectrum
spectrumFrac_ -= dy / contentH;
spectrumFrac_ = std::clamp(spectrumFrac_, 0.1f, 0.9f); spectrumFrac_ = std::clamp(spectrumFrac_, 0.1f, 0.9f);
draggingSplit_ = true; draggingSplit_ = true;
} else if (draggingSplit_) { } else if (draggingSplit_) {
@@ -411,7 +410,7 @@ void Application::render() {
float cy = splPos.y + kSplitterH * 0.5f; float cy = splPos.y + kSplitterH * 0.5f;
dl->AddLine({splPos.x, cy}, {splPos.x + contentW, cy}, splCol, 2.0f); dl->AddLine({splPos.x, cy}, {splPos.x + contentW, cy}, splCol, 2.0f);
renderWaterfallPanel(); renderSpectrumPanel();
// ── Cross-panel hover line & frequency label ── // ── Cross-panel hover line & frequency label ──
if (cursors_.hover.active && specSizeX_ > 0 && wfSizeX_ > 0) { if (cursors_.hover.active && specSizeX_ > 0 && wfSizeX_ > 0) {
@@ -421,10 +420,10 @@ void Application::render() {
settings_.isIQ, freqScale_, viewLo_, viewHi_); settings_.isIQ, freqScale_, viewLo_, viewHi_);
ImU32 hoverCol = IM_COL32(200, 200, 200, 80); ImU32 hoverCol = IM_COL32(200, 200, 200, 80);
// Line spanning spectrum + splitter + waterfall // Line spanning waterfall + splitter + spectrum
dlp->AddLine({hx, specPosY_}, {hx, wfPosY_ + wfSizeY_}, hoverCol, 1.0f); dlp->AddLine({hx, wfPosY_}, {hx, specPosY_ + specSizeY_}, hoverCol, 1.0f);
// Frequency label at top of the line // Frequency label at top of waterfall
char freqLabel[48]; char freqLabel[48];
double hf = cursors_.hover.freq; double hf = cursors_.hover.freq;
if (std::abs(hf) >= 1e6) if (std::abs(hf) >= 1e6)
@@ -435,8 +434,8 @@ void Application::render() {
std::snprintf(freqLabel, sizeof(freqLabel), "%.1f Hz", hf); std::snprintf(freqLabel, sizeof(freqLabel), "%.1f Hz", hf);
ImVec2 tSz = ImGui::CalcTextSize(freqLabel); ImVec2 tSz = ImGui::CalcTextSize(freqLabel);
float lx = std::min(hx + 4, specPosX_ + specSizeX_ - tSz.x - 4); float lx = std::min(hx + 4, wfPosX_ + wfSizeX_ - tSz.x - 4);
float ly = specPosY_ + 2; float ly = wfPosY_ + 2;
dlp->AddRectFilled({lx - 2, ly - 1}, {lx + tSz.x + 2, ly + tSz.y + 1}, dlp->AddRectFilled({lx - 2, ly - 1}, {lx + tSz.x + 2, ly + tSz.y + 1},
IM_COL32(0, 0, 0, 180)); IM_COL32(0, 0, 0, 180));
dlp->AddText({lx, ly}, IM_COL32(220, 220, 240, 240), freqLabel); dlp->AddText({lx, ly}, IM_COL32(220, 220, 240, 240), freqLabel);
@@ -659,11 +658,8 @@ void Application::renderControlPanel() {
void Application::renderSpectrumPanel() { void Application::renderSpectrumPanel() {
float availW = ImGui::GetContentRegionAvail().x; float availW = ImGui::GetContentRegionAvail().x;
// Use the parent's full content height (availY includes spectrum + splitter + waterfall) // Spectrum is at the bottom — use all remaining height after waterfall + splitter.
// to compute the spectrum height from the split fraction. float specH = ImGui::GetContentRegionAvail().y;
constexpr float kSplitterH = 6.0f;
float parentH = ImGui::GetContentRegionAvail().y;
float specH = (parentH - kSplitterH) * spectrumFrac_;
ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec2 pos = ImGui::GetCursorScreenPos();
specPosX_ = pos.x; specPosX_ = pos.x;
@@ -717,7 +713,10 @@ void Application::renderSpectrumPanel() {
void Application::renderWaterfallPanel() { void Application::renderWaterfallPanel() {
float availW = ImGui::GetContentRegionAvail().x; float availW = ImGui::GetContentRegionAvail().x;
float availH = ImGui::GetContentRegionAvail().y; // Waterfall is at the top — compute height from the split fraction.
constexpr float kSplitterH = 6.0f;
float parentH = ImGui::GetContentRegionAvail().y;
float availH = (parentH - kSplitterH) * (1.0f - spectrumFrac_);
// History depth must be >= panel height for 1:1 pixel mapping. // History depth must be >= panel height for 1:1 pixel mapping.
// Only recreate when bin count or needed height actually changes. // Only recreate when bin count or needed height actually changes.
@@ -736,34 +735,32 @@ void Application::renderWaterfallPanel() {
int h = waterfall_.height(); int h = waterfall_.height();
// The newest row was just written at currentRow()+1 (mod h) — but // The newest row was just written at currentRow()+1 (mod h) — but
// advanceRow already decremented, so currentRow() IS the newest. // advanceRow already decremented, so currentRow() IS the newest.
// The row *after* currentRow() (i.e. currentRow()+1) is the oldest
// visible row. We only want the most recent screenRows rows so
// that every texture row maps to exactly one screen pixel.
int screenRows = std::min(static_cast<int>(availH), h); int screenRows = std::min(static_cast<int>(availH), h);
// Newest row index in the circular buffer. // Newest row index in the circular buffer.
int newestRow = (waterfall_.currentRow() + 1) % h; int newestRow = (waterfall_.currentRow() + 1) % h;
// Render 1:1 (one texture row = one screen pixel), top-aligned, // Render 1:1 (one texture row = one screen pixel), bottom-aligned,
// newest line at top (right below the spectrogram), scrolling down. // newest line at bottom, scrolling upward.
// //
// advanceRow() decrements currentRow_, so rows are written at // We flip the V coordinates (v1 before v0) so that the vertical
// decreasing indices. Going from newest to oldest = increasing // direction is reversed: newest at the bottom of the draw region.
// index (mod h). Normal V order (no flip needed).
float rowToV = 1.0f / h; float rowToV = 1.0f / h;
float screenY = pos.y;
bool logMode = (freqScale_ == FreqScale::Logarithmic && !settings_.isIQ); bool logMode = (freqScale_ == FreqScale::Logarithmic && !settings_.isIQ);
// drawSpan renders rows [rowStart..rowStart+rowCount) but with
// flipped V so oldest is at top and newest at bottom.
auto drawSpan = [&](int rowStart, int rowCount, float yStart, float spanH) { auto drawSpan = [&](int rowStart, int rowCount, float yStart, float spanH) {
float v0 = rowStart * rowToV; float v0 = rowStart * rowToV;
float v1 = (rowStart + rowCount) * rowToV; float v1 = (rowStart + rowCount) * rowToV;
// Flip: swap v0 and v1 so texture is vertically inverted
if (!logMode) { if (!logMode) {
dl->AddImage(texID, dl->AddImage(texID,
{pos.x, yStart}, {pos.x, yStart},
{pos.x + availW, yStart + spanH}, {pos.x + availW, yStart + spanH},
{viewLo_, v0}, {viewHi_, v1}); {viewLo_, v1}, {viewHi_, v0});
} else { } else {
constexpr float kMinBinFrac = 0.001f; constexpr float kMinBinFrac = 0.001f;
float logMin2 = std::log10(kMinBinFrac); float logMin2 = std::log10(kMinBinFrac);
@@ -779,26 +776,32 @@ void Application::renderWaterfallPanel() {
dl->AddImage(texID, dl->AddImage(texID,
{pos.x + sL * availW, yStart}, {pos.x + sL * availW, yStart},
{pos.x + sR * availW, yStart + spanH}, {pos.x + sR * availW, yStart + spanH},
{uL, v0}, {uR, v1}); {uL, v1}, {uR, v0});
} }
} }
}; };
// From newestRow, walk forward (increasing index mod h) for // From newestRow, walk forward (increasing index mod h) for
// screenRows steps to cover newest→oldest. // screenRows steps to cover newest→oldest.
// Use availH for the screen extent so there's no fractional pixel gap. // With V-flip, oldest rows render at the top, newest at the bottom.
float pxPerRow = availH / static_cast<float>(screenRows); float pxPerRow = availH / static_cast<float>(screenRows);
if (newestRow + screenRows <= h) { if (newestRow + screenRows <= h) {
drawSpan(newestRow, screenRows, screenY, availH); drawSpan(newestRow, screenRows, pos.y, availH);
} else { } else {
int firstCount = h - newestRow; // Wrap-around: two spans. Because we flip V, the second span
float firstH = firstCount * pxPerRow; // (wrap-around, containing older rows) goes at the TOP.
drawSpan(newestRow, firstCount, screenY, firstH); int firstCount = h - newestRow; // rows newestRow..h-1
int secondCount = screenRows - firstCount; // rows 0..secondCount-1
int secondCount = screenRows - firstCount; // Second span (older, wraps to index 0) at top
float secondH = secondCount * pxPerRow;
if (secondCount > 0) if (secondCount > 0)
drawSpan(0, secondCount, screenY + firstH, availH - firstH); drawSpan(0, secondCount, pos.y, secondH);
// First span (newer, includes newestRow) at bottom
float firstH = availH - secondH;
drawSpan(newestRow, firstCount, pos.y + secondH, firstH);
} }
// ── Frequency axis labels ── // ── Frequency axis labels ──
@@ -834,7 +837,7 @@ void Application::renderWaterfallPanel() {
else else
std::snprintf(label, sizeof(label), "%.0f", freq); std::snprintf(label, sizeof(label), "%.0f", freq);
dl->AddText({x + 2, pos.y + availH - 14}, textCol, label); dl->AddText({x + 2, pos.y + 2}, textCol, label);
} }
// Store waterfall geometry for cross-panel cursor drawing. // Store waterfall geometry for cross-panel cursor drawing.