mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 04:47:35 +00:00
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:
+54
-13
@@ -160,7 +160,7 @@ type
|
|||||||
procedure RawHLine(Y, X1, X2: Integer; Color: TColor;
|
procedure RawHLine(Y, X1, X2: Integer; Color: TColor;
|
||||||
OnPx: Integer = 0; OffPx: Integer = 0);
|
OnPx: Integer = 0; OffPx: Integer = 0);
|
||||||
procedure RawFillRect(X1, Y1, X2, Y2: Integer; Color: TColor);
|
procedure RawFillRect(X1, Y1, X2, Y2: Integer; Color: TColor);
|
||||||
procedure RawTriangleDown(CX, HalfW, Hgt: Integer; Color: TColor);
|
procedure RawTriangleDown(CX, Top, HalfW, Hgt: Integer; Color: TColor);
|
||||||
procedure RawCurve(W: Integer; Color: TColor);
|
procedure RawCurve(W: Integer; Color: TColor);
|
||||||
function GetTextMask(const S: string; FontSize: Integer;
|
function GetTextMask(const S: string; FontSize: Integer;
|
||||||
Bold: Boolean): TBitmap;
|
Bold: Boolean): TBitmap;
|
||||||
@@ -172,6 +172,8 @@ type
|
|||||||
procedure DrawSliceFilterBands(W, H: Integer);
|
procedure DrawSliceFilterBands(W, H: Integer);
|
||||||
procedure DrawSliceFilterLinesRaw(W, H: Integer);
|
procedure DrawSliceFilterLinesRaw(W, H: Integer);
|
||||||
procedure DrawBandLetterRaw(X1, X2: Integer; L: Char; Clr: TColor);
|
procedure DrawBandLetterRaw(X1, X2: Integer; L: Char; Clr: TColor);
|
||||||
|
// Y, с которого начинать вертикаль несущей, чтобы не перечеркнуть букву.
|
||||||
|
function CarrierTopY(X1, X2, CarrierX: Integer; L: Char): Integer;
|
||||||
procedure DrawBeaconMarkersRaw(W, H: Integer);
|
procedure DrawBeaconMarkersRaw(W, H: Integer);
|
||||||
// Штрихи DX-спотов ниже полосы подписей. Раскладка уже посчитана оверлеем
|
// Штрихи DX-спотов ниже полосы подписей. Раскладка уже посчитана оверлеем
|
||||||
// при пересборке кэша — здесь только N вертикальных линий.
|
// при пересборке кэша — здесь только N вертикальных линий.
|
||||||
@@ -366,6 +368,9 @@ const
|
|||||||
// Полоса фильтра передающего тракта (главный VFO при TX / передающий слайс).
|
// Полоса фильтра передающего тракта (главный VFO при TX / передающий слайс).
|
||||||
// TColor = $00BBGGRR, младший байт = R (как в BlendBand/RawPack).
|
// TColor = $00BBGGRR, младший байт = R (как в BlendBand/RawPack).
|
||||||
CLR_TX_BAND = TColor($003030E0);
|
CLR_TX_BAND = TColor($003030E0);
|
||||||
|
// Кегль буквы слайса на полосе фильтра. Один на рисование буквы и на расчёт
|
||||||
|
// зазора под неё (CarrierTopY) — иначе зазор разъедется с глифом.
|
||||||
|
BAND_LETTER_FONT = 8;
|
||||||
|
|
||||||
// Упаковка TColor в пиксель кадра (непрозрачный). Раскладка байтов как в
|
// Упаковка TColor в пиксель кадра (непрозрачный). Раскладка байтов как в
|
||||||
// BlendBand: non-Darwin = BGRA, Darwin = ARGB.
|
// BlendBand: non-Darwin = BGRA, Darwin = ARGB.
|
||||||
@@ -955,14 +960,16 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Треугольник остриём вниз (курсор VFO): вершина основания на Y=0.
|
// Треугольник остриём вниз (курсор VFO): вершина основания на Y=0.
|
||||||
procedure TSpectrumView.RawTriangleDown(CX, HalfW, Hgt: Integer; Color: TColor);
|
procedure TSpectrumView.RawTriangleDown(CX, Top, HalfW, Hgt: Integer; Color: TColor);
|
||||||
|
// Top — верх треугольника: «голова» несущей уезжает вниз, когда её иначе
|
||||||
|
// накрыла бы буква слайса (см. CarrierTopY).
|
||||||
var
|
var
|
||||||
Y, Half: Integer;
|
Y, Half: Integer;
|
||||||
begin
|
begin
|
||||||
for Y := 0 to Hgt do
|
for Y := 0 to Hgt do
|
||||||
begin
|
begin
|
||||||
Half := Round(HalfW * (Hgt - Y) / Hgt);
|
Half := Round(HalfW * (Hgt - Y) / Hgt);
|
||||||
RawHLine(Y, CX - Half, CX + Half + 1, Color);
|
RawHLine(Top + Y, CX - Half, CX + Half + 1, Color);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1236,7 +1243,9 @@ begin
|
|||||||
CalcSliceBandX(O, W, SX1, SX2, SVfoX);
|
CalcSliceBandX(O, W, SX1, SX2, SVfoX);
|
||||||
RawVLine(SX1, 0, H - 1, Clr);
|
RawVLine(SX1, 0, H - 1, Clr);
|
||||||
RawVLine(SX2, 0, H - 1, Clr);
|
RawVLine(SX2, 0, H - 1, Clr);
|
||||||
RawVLine(SVfoX, 0, H - 13, Clr, 2); // несущая (жирнее)
|
// Несущая (жирнее). В AM/FM/DSB она приходится ровно на букву — начинаем
|
||||||
|
// под ней, чтобы буква читалась (в SSB/CW несущая у кромки, зазор = 0).
|
||||||
|
RawVLine(SVfoX, CarrierTopY(SX1, SX2, SVfoX, O.SliceLetter), H - 13, Clr, 2);
|
||||||
// Буква слайса внутри полосы фильтра сверху — привязка полосы к флагу,
|
// Буква слайса внутри полосы фильтра сверху — привязка полосы к флагу,
|
||||||
// цвет = цвет слайса (совпадает с бейджем во флаге).
|
// цвет = цвет слайса (совпадает с бейджем во флаге).
|
||||||
DrawBandLetterRaw(SX1, SX2, O.SliceLetter, SliceColor(O.SliceLetter));
|
DrawBandLetterRaw(SX1, SX2, O.SliceLetter, SliceColor(O.SliceLetter));
|
||||||
@@ -1251,7 +1260,30 @@ var
|
|||||||
begin
|
begin
|
||||||
if (L < 'A') or (L > 'Z') then Exit;
|
if (L < 'A') or (L > 'Z') then Exit;
|
||||||
cx := (X1 + X2) div 2;
|
cx := (X1 + X2) div 2;
|
||||||
RawText(cx - RawTextWidth(L, 8, True) div 2, 0, L, Clr, 8, True, clNone);
|
RawText(cx - RawTextWidth(L, BAND_LETTER_FONT, True) div 2, 0, L, Clr,
|
||||||
|
BAND_LETTER_FONT, True, clNone);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TSpectrumView.CarrierTopY(X1, X2, CarrierX: Integer; L: Char): Integer;
|
||||||
|
// Вертикаль несущей идёт от самого верха полосы — и в модуляциях с несущей
|
||||||
|
// (AM/FM/DSB) она приходится ровно на букву слайса: и буква, и несущая стоят по
|
||||||
|
// центру полосы. В SSB/CW несущая лежит у кромки, пересечения нет, поэтому
|
||||||
|
// раньше это в глаза не бросалось. Здесь считаем, накрывает ли вертикаль букву,
|
||||||
|
// и если да — начинаем её ПОД буквой. L = #0 (или полоса без буквы) → 0.
|
||||||
|
const
|
||||||
|
CLEAR_X = 3; // запас по бокам от глифа
|
||||||
|
CLEAR_Y = 2; // зазор между буквой и началом вертикали
|
||||||
|
var
|
||||||
|
M: TBitmap;
|
||||||
|
cx: Integer;
|
||||||
|
begin
|
||||||
|
Result := 0;
|
||||||
|
if (L < 'A') or (L > 'Z') or (X2 <= X1) then Exit;
|
||||||
|
M := GetTextMask(L, BAND_LETTER_FONT, True);
|
||||||
|
if M = nil then Exit;
|
||||||
|
cx := (X1 + X2) div 2;
|
||||||
|
if Abs(CarrierX - cx) <= M.Width div 2 + CLEAR_X then
|
||||||
|
Result := M.Height + CLEAR_Y;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSpectrumView.CopyGridToSpectrum(W, H: Integer);
|
procedure TSpectrumView.CopyGridToSpectrum(W, H: Integer);
|
||||||
@@ -1569,7 +1601,8 @@ var
|
|||||||
DBmin, DBmax, dB: Double;
|
DBmin, DBmax, dB: Double;
|
||||||
VfoX, X1, X2: Integer;
|
VfoX, X1, X2: Integer;
|
||||||
TXVfoX, TXX1, TXX2: Integer;
|
TXVfoX, TXX1, TXX2: Integer;
|
||||||
AGCy, AGCHangY: Integer;
|
AGCy, AGCHangY, CarrY: Integer;
|
||||||
|
MainLetter: Char;
|
||||||
SrcF, Frac, dBv: Double;
|
SrcF, Frac, dBv: Double;
|
||||||
S0, S1: Integer;
|
S0, S1: Integer;
|
||||||
InvRange: Double;
|
InvRange: Double;
|
||||||
@@ -1768,11 +1801,15 @@ begin
|
|||||||
|
|
||||||
|
|
||||||
// Буква главного флага (A) на его полосе — только когда есть слайсы,
|
// Буква главного флага (A) на его полосе — только когда есть слайсы,
|
||||||
// иначе одиночный приём не засоряем.
|
// иначе одиночный приём не засоряем. Букву запоминаем: под неё ниже
|
||||||
|
// подстраиваются вертикаль несущей и её треугольник.
|
||||||
|
MainLetter := #0;
|
||||||
if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and
|
if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and
|
||||||
(FActiveVfo = 0) and Assigned(FVfoOverlay) then
|
(FActiveVfo = 0) and Assigned(FVfoOverlay) then
|
||||||
DrawBandLetterRaw(X1, X2, FVfoOverlay.SliceLetter,
|
begin
|
||||||
SliceColor(FVfoOverlay.SliceLetter));
|
MainLetter := FVfoOverlay.SliceLetter;
|
||||||
|
DrawBandLetterRaw(X1, X2, MainLetter, SliceColor(MainLetter));
|
||||||
|
end;
|
||||||
|
|
||||||
// Края/несущие/буквы слайсов (полосы уже нарисованы выше).
|
// Края/несущие/буквы слайсов (полосы уже нарисованы выше).
|
||||||
DrawSliceFilterLinesRaw(W, H);
|
DrawSliceFilterLinesRaw(W, H);
|
||||||
@@ -1811,14 +1848,18 @@ begin
|
|||||||
// Края фильтра + VFO
|
// Края фильтра + VFO
|
||||||
RawVLine(X1, 0, H - 1, FTheme.SpecFilterEdge);
|
RawVLine(X1, 0, H - 1, FTheme.SpecFilterEdge);
|
||||||
RawVLine(X2, 0, H - 1, FTheme.SpecFilterEdge);
|
RawVLine(X2, 0, H - 1, FTheme.SpecFilterEdge);
|
||||||
RawVLine(VfoX, 0, H - 13, FTheme.SpecVfoCursor, 2);
|
// Несущая и её треугольник — под буквой полосы, если она там есть.
|
||||||
RawTriangleDown(VfoX, 5, 8, FTheme.SpecVfoCursor);
|
CarrY := CarrierTopY(X1, X2, VfoX, MainLetter);
|
||||||
|
RawVLine(VfoX, CarrY, H - 13, FTheme.SpecVfoCursor, 2);
|
||||||
|
RawTriangleDown(VfoX, CarrY, 5, 8, FTheme.SpecVfoCursor);
|
||||||
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
|
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
|
||||||
begin
|
begin
|
||||||
RawVLine(TXX1, 0, H - 1, TColor($002030E0));
|
RawVLine(TXX1, 0, H - 1, TColor($002030E0));
|
||||||
RawVLine(TXX2, 0, H - 1, TColor($002030E0));
|
RawVLine(TXX2, 0, H - 1, TColor($002030E0));
|
||||||
RawVLine(TXVfoX, 0, H - 13, TColor($002030E0), 2);
|
// Буква нарисована на RX-полосе (X1..X2) — с ней и сверяемся.
|
||||||
RawTriangleDown(TXVfoX, 5, 8, TColor($002030E0));
|
CarrY := CarrierTopY(X1, X2, TXVfoX, MainLetter);
|
||||||
|
RawVLine(TXVfoX, CarrY, H - 13, TColor($002030E0), 2);
|
||||||
|
RawTriangleDown(TXVfoX, CarrY, 5, 8, TColor($002030E0));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if FMarkerActive then DrawMarkerLineRaw(W, H);
|
if FMarkerActive then DrawMarkerLineRaw(W, H);
|
||||||
|
|||||||
+46
-9
@@ -106,6 +106,9 @@ type
|
|||||||
procedure DrawSliceOverlays(W, H: Integer);
|
procedure DrawSliceOverlays(W, H: Integer);
|
||||||
procedure DrawSliceFilterMarkers(W, H: Integer);
|
procedure DrawSliceFilterMarkers(W, H: Integer);
|
||||||
procedure DrawBandLetterGL(X1, X2: Integer; L: Char);
|
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 SliceTexIndex(O: TVfoOverlay): Integer; // индекс в FSliceTex, -1 если нет
|
||||||
function FormatFreqGL(Hz: Double): string;
|
function FormatFreqGL(Hz: Double): string;
|
||||||
function ActiveVfoFrequency: Double;
|
function ActiveVfoFrequency: Double;
|
||||||
@@ -592,7 +595,8 @@ end;
|
|||||||
|
|
||||||
procedure TSpectrumViewOpenGL.DrawFilterAndCursors(W, H: Integer);
|
procedure TSpectrumViewOpenGL.DrawFilterAndCursors(W, H: Integer);
|
||||||
var
|
var
|
||||||
X1, X2, VfoX, TXX1, TXX2, TXVfoX: Integer;
|
X1, X2, VfoX, TXX1, TXX2, TXVfoX, CarrY: Integer;
|
||||||
|
MainLetter: Char;
|
||||||
begin
|
begin
|
||||||
TXX1 := 0;
|
TXX1 := 0;
|
||||||
TXX2 := 0;
|
TXX2 := 0;
|
||||||
@@ -620,22 +624,30 @@ begin
|
|||||||
else if X2 > X1 then
|
else if X2 > X1 then
|
||||||
DrawRect(X1, 0, X2, H, FTheme.SpecFilter, 1);
|
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(X1, 0, X1, H, FTheme.SpecFilterEdge, 1, False);
|
||||||
DrawLine(X2, 0, X2, H, FTheme.SpecFilterEdge, 1, False);
|
DrawLine(X2, 0, X2, H, FTheme.SpecFilterEdge, 1, False);
|
||||||
DrawLine(VfoX, 0, VfoX, H - 12, FTheme.SpecVfoCursor, 2, False);
|
CarrY := CarrierTopYGL(X1, X2, VfoX, MainLetter);
|
||||||
DrawRect(VfoX - 5, 0, VfoX + 5, 8, FTheme.SpecVfoCursor, 1);
|
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
|
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
|
||||||
begin
|
begin
|
||||||
DrawLine(TXX1, 0, TXX1, H, TColor($002030E0), 1, False);
|
DrawLine(TXX1, 0, TXX1, H, TColor($002030E0), 1, False);
|
||||||
DrawLine(TXX2, 0, TXX2, H, TColor($002030E0), 1, False);
|
DrawLine(TXX2, 0, TXX2, H, TColor($002030E0), 1, False);
|
||||||
DrawLine(TXVfoX, 0, TXVfoX, H - 12, TColor($002030E0), 2, False);
|
// Буква нарисована на RX-полосе (X1..X2) — с ней и сверяемся.
|
||||||
DrawRect(TXVfoX - 5, 0, TXVfoX + 5, 8, TColor($002030E0), 1);
|
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;
|
end;
|
||||||
|
|
||||||
// Буква главного флага (A) на его полосе — только когда есть слайсы.
|
if MainLetter <> #0 then DrawBandLetterGL(X1, X2, MainLetter);
|
||||||
if (SliceOverlayCount > 0) and (FActiveVfo = 0) and Assigned(FVfoOverlay) then
|
|
||||||
DrawBandLetterGL(X1, X2, FVfoOverlay.SliceLetter);
|
|
||||||
|
|
||||||
DrawSliceFilterMarkers(W, H);
|
DrawSliceFilterMarkers(W, H);
|
||||||
end;
|
end;
|
||||||
@@ -660,7 +672,9 @@ begin
|
|||||||
DrawRect(SX1, 0, SX2, H, Clr, IfThen(O.TxActive, 100, 60) / 255);
|
DrawRect(SX1, 0, SX2, H, Clr, IfThen(O.TxActive, 100, 60) / 255);
|
||||||
DrawLine(SX1, 0, SX1, H, Clr, 1, False);
|
DrawLine(SX1, 0, SX1, H, Clr, 1, False);
|
||||||
DrawLine(SX2, 0, SX2, 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);
|
DrawBandLetterGL(SX1, SX2, O.SliceLetter);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -679,6 +693,29 @@ begin
|
|||||||
DrawTexture(FLetterTex[idx], cx - FLetterTex[idx].W div 2, 0);
|
DrawTexture(FLetterTex[idx], cx - FLetterTex[idx].W div 2, 0);
|
||||||
end;
|
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,
|
procedure TSpectrumViewOpenGL.DrawAGCLines(W, H: Integer; DBmax,
|
||||||
InvRange: Double);
|
InvRange: Double);
|
||||||
var
|
var
|
||||||
|
|||||||
Reference in New Issue
Block a user