ui(spectrum): вертикаль несущей не перечёркивает букву полосы фильтра

Буква слайса стоит по центру полосы фильтра у самого верха, и вертикаль
несущей шла туда же — от Y=0. В SSB/CW это не мешало (несущая лежит у кромки
полосы, буква — по центру), а в модуляциях с несущей (AM/FM/DSB) центр полосы
и есть несущая, и линия шла прямо по букве.

Одна функция на оба вида — CarrierTopY (CPU) / CarrierTopYGL (GL): смотрит,
попадает ли вертикаль под глиф, и если да, отдаёт Y ПОД буквой. Решение по
факту пересечения, а не по списку «модуляций с несущей»: список пришлось бы
вести и сопровождать, а в SSB зазор и так выходит нулевым сам собой.

- Слайсы: DrawSliceFilterLinesRaw и DrawSliceFilterMarkers.
- Главный VFO: вместе с линией вниз уезжает и её «шляпка» — треугольник в CPU
  (у RawTriangleDown появился параметр верхней координаты) и метка в GL. Без
  этого они остались бы поверх буквы и зазор не помог бы.
- Полоса TX при split сверяется с той же буквой — она нарисована на RX-полосе.
- В GL буква главного флага теперь рисуется ПОСЛЕ линий, как в CPU: порядок
  наложения у путей должен совпадать.

Кегль буквы вынесен в BAND_LETTER_FONT: рисование и расчёт зазора обязаны
брать его из одного места, иначе зазор разъедется с глифом.

Метрика проверена замером (Qt6, offscreen): глиф 8 bold = 7x12 px ⇒ вертикаль
стартует с Y=14, по горизонтали зазор ±6 px от центра полосы. Правится
константами CLEAR_X/CLEAR_Y рядом с функцией.

Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На экране не проверено: нужен эфир с
AM/FM и слайсами.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 19:21:08 +03:00
co-authored by Claude Opus 5
parent 13b46f0b7d
commit d0c82ade73
2 changed files with 100 additions and 22 deletions
+46 -9
View File
@@ -106,6 +106,9 @@ type
procedure DrawSliceOverlays(W, H: Integer);
procedure DrawSliceFilterMarkers(W, H: Integer);
procedure DrawBandLetterGL(X1, X2: Integer; L: Char);
// Y начала вертикали несущей, чтобы она не легла на букву (двойник
// CPU-CarrierTopY; метрику берём из той же текстуры буквы).
function CarrierTopYGL(X1, X2, CarrierX: Integer; L: Char): Integer;
function SliceTexIndex(O: TVfoOverlay): Integer; // индекс в FSliceTex, -1 если нет
function FormatFreqGL(Hz: Double): string;
function ActiveVfoFrequency: Double;
@@ -592,7 +595,8 @@ end;
procedure TSpectrumViewOpenGL.DrawFilterAndCursors(W, H: Integer);
var
X1, X2, VfoX, TXX1, TXX2, TXVfoX: Integer;
X1, X2, VfoX, TXX1, TXX2, TXVfoX, CarrY: Integer;
MainLetter: Char;
begin
TXX1 := 0;
TXX2 := 0;
@@ -620,22 +624,30 @@ begin
else if X2 > X1 then
DrawRect(X1, 0, X2, H, FTheme.SpecFilter, 1);
// Буква главного флага (A) на его полосе — только когда есть слайсы. Саму
// букву рисуем ниже, но знать о ней надо здесь: под неё уходит начало
// вертикали несущей и её «шляпка».
MainLetter := #0;
if (SliceOverlayCount > 0) and (FActiveVfo = 0) and Assigned(FVfoOverlay) then
MainLetter := FVfoOverlay.SliceLetter;
DrawLine(X1, 0, X1, H, FTheme.SpecFilterEdge, 1, False);
DrawLine(X2, 0, X2, H, FTheme.SpecFilterEdge, 1, False);
DrawLine(VfoX, 0, VfoX, H - 12, FTheme.SpecVfoCursor, 2, False);
DrawRect(VfoX - 5, 0, VfoX + 5, 8, FTheme.SpecVfoCursor, 1);
CarrY := CarrierTopYGL(X1, X2, VfoX, MainLetter);
DrawLine(VfoX, CarrY, VfoX, H - 12, FTheme.SpecVfoCursor, 2, False);
DrawRect(VfoX - 5, CarrY, VfoX + 5, CarrY + 8, FTheme.SpecVfoCursor, 1);
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
begin
DrawLine(TXX1, 0, TXX1, H, TColor($002030E0), 1, False);
DrawLine(TXX2, 0, TXX2, H, TColor($002030E0), 1, False);
DrawLine(TXVfoX, 0, TXVfoX, H - 12, TColor($002030E0), 2, False);
DrawRect(TXVfoX - 5, 0, TXVfoX + 5, 8, TColor($002030E0), 1);
// Буква нарисована на RX-полосе (X1..X2) — с ней и сверяемся.
CarrY := CarrierTopYGL(X1, X2, TXVfoX, MainLetter);
DrawLine(TXVfoX, CarrY, TXVfoX, H - 12, TColor($002030E0), 2, False);
DrawRect(TXVfoX - 5, CarrY, TXVfoX + 5, CarrY + 8, TColor($002030E0), 1);
end;
// Буква главного флага (A) на его полосе — только когда есть слайсы.
if (SliceOverlayCount > 0) and (FActiveVfo = 0) and Assigned(FVfoOverlay) then
DrawBandLetterGL(X1, X2, FVfoOverlay.SliceLetter);
if MainLetter <> #0 then DrawBandLetterGL(X1, X2, MainLetter);
DrawSliceFilterMarkers(W, H);
end;
@@ -660,7 +672,9 @@ begin
DrawRect(SX1, 0, SX2, H, Clr, IfThen(O.TxActive, 100, 60) / 255);
DrawLine(SX1, 0, SX1, H, Clr, 1, False);
DrawLine(SX2, 0, SX2, H, Clr, 1, False);
DrawLine(SVfoX, 0, SVfoX, H - 12, Clr, 2, False);
// Несущая — под буквой, если она приходится на неё (AM/FM/DSB).
DrawLine(SVfoX, CarrierTopYGL(SX1, SX2, SVfoX, O.SliceLetter),
SVfoX, H - 12, Clr, 2, False);
DrawBandLetterGL(SX1, SX2, O.SliceLetter);
end;
end;
@@ -679,6 +693,29 @@ begin
DrawTexture(FLetterTex[idx], cx - FLetterTex[idx].W div 2, 0);
end;
function TSpectrumViewOpenGL.CarrierTopYGL(X1, X2, CarrierX: Integer;
L: Char): Integer;
// То же правило, что в CPU-пути: в модуляциях с несущей (AM/FM/DSB) вертикаль
// приходится ровно на букву — начинаем её ПОД буквой; в SSB/CW несущая у кромки
// полосы, пересечения нет и зазор нулевой. Текстуру буквы при необходимости
// заливаем здесь же: она нужна нам раньше, чем до неё дойдёт DrawBandLetterGL
// (порядок вызовов в кадре — линии, потом буквы).
const
CLEAR_X = 3;
CLEAR_Y = 2;
var
idx: Integer;
begin
Result := 0;
if (L < 'A') or (L > 'H') or (X2 <= X1) then Exit;
idx := Ord(L) - Ord('A');
if FLetterTex[idx].Tex = 0 then
UploadText(FLetterTex[idx], L, SliceColor(L), 8, True);
if FLetterTex[idx].H <= 0 then Exit;
if Abs(CarrierX - (X1 + X2) div 2) <= FLetterTex[idx].W div 2 + CLEAR_X then
Result := FLetterTex[idx].H + CLEAR_Y;
end;
procedure TSpectrumViewOpenGL.DrawAGCLines(W, H: Integer; DBmax,
InvRange: Double);
var