Files
ewsdr/SMeterView.pas
T

573 lines
23 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
Copyright (C)
2026 - Uladzimir Karpenka, EW8BAK
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit SMeterView;
{
SMeterView.pas — рендеринг S-метра, TX-метра и индикаторов мощности/SWR.
TSMeterView — изолированный класс без зависимостей на SpectrumView.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
IntfGraphics, FPImage, LCLType, GraphType, AppTheme, PlatformUtils;
const
SM_CLR_METER_ON = TColor($0000CC44);
SM_CLR_AMBER = TColor($0000AAFF);
type
TSMeterView = class
private
FSmBitmap: TBitmap;
FTheme: TAppTheme;
FLastSMeter: Double;
FSMeterPeak: Double;
FSMeterMin: Double;
FLastFwdW: Double;
FLastSWR: Double;
FPAMaxPower: Double;
// Есть ли у железа мост детектора мощности (Caps.HasPA). У AD936x его нет:
// FLastFwdW/FLastSWR так и стоят на значениях инициализации, и «0W / SWR
// 1.0» на передаче — не показание, а выдумка. Шкала в этом случае рисуется
// пустой: деления есть, заливки и цифр нет.
FHasPATelemetry: Boolean;
FTransmitting: Boolean;
FPbSMeterRight: TPaintBox;
// Масштаб канвы: 2 на Retina, 1 на Windows/Linux. Битмап растёт во столько
// же раз, пиксельные константы и шрифты умножаются на него.
FScale: Integer;
procedure DrawSMeterWide(ACanvas: TCanvas; R: TRect;
Value, Peak, MinVal: Double;
out ZX1, ZX2, ZY1, ZY2: Integer);
procedure DrawSMeterZone(ACanvas: TCanvas; R: TRect;
X1, X2, Y1, Y2: Integer);
procedure DrawTXMeter(ACanvas: TCanvas; R: TRect);
public
constructor Create;
destructor Destroy; override;
property LastSMeter: Double read FLastSMeter write FLastSMeter;
property SMeterPeak: Double read FSMeterPeak write FSMeterPeak;
property SMeterMin: Double read FSMeterMin write FSMeterMin;
property LastFwdW: Double read FLastFwdW write FLastFwdW;
property LastSWR: Double read FLastSWR write FLastSWR;
property PAMaxPower: Double read FPAMaxPower write FPAMaxPower;
property HasPATelemetry: Boolean read FHasPATelemetry write FHasPATelemetry;
property Transmitting: Boolean read FTransmitting write FTransmitting;
property PbSMeterRight: TPaintBox write FPbSMeterRight;
procedure PaintSMeterRight(Sender: TObject);
procedure SetTheme(const T: TAppTheme);
end;
implementation
// ════════════════════════════════════════════════════════════════════════════
// TSMeterView
// ════════════════════════════════════════════════════════════════════════════
constructor TSMeterView.Create;
begin
inherited Create;
FSmBitmap := nil;
FTheme := DarkTheme;
FLastSMeter := -130;
FSMeterPeak := -130;
FSMeterMin := -130;
FLastFwdW := 0;
FLastSWR := 1;
FPAMaxPower := 100.0;
FHasPATelemetry := True;
FTransmitting := False;
FScale := 1;
end;
destructor TSMeterView.Destroy;
begin
FreeAndNil(FSmBitmap);
inherited;
end;
procedure TSMeterView.SetTheme(const T: TAppTheme);
begin
FTheme := T;
end;
// ────────────────────────────────────────────────────────────────────────────
// DrawSMeterWide
// ────────────────────────────────────────────────────────────────────────────
procedure TSMeterView.DrawSMeterWide(ACanvas: TCanvas; R: TRect;
Value, Peak, MinVal: Double; out ZX1, ZX2, ZY1, ZY2: Integer);
const
DB_MIN = -127.0; DB_MAX = -13.0; DB_S9 = -73.0; DB_OVR = -43.0;
DBM_MARKS: array[0..5] of Double = (-120,-100,-80,-60,-40,-20);
DBM_LABELS: array[0..5] of string = ('-120','-100','-80','-60','-40','-20');
S_MARKS_DBM: array[0..7] of Double = (-121,-109,-97,-85,-73,-53,-33,-13);
S_MARKS_LBL: array[0..7] of string = ('S1','S3','S5','S7','S9','+20','+40','+60');
LEFT_INFO_MIN = 62; RIGHT_PAD = 8; LABEL_PAD = 4;
TICK_LONG = 5; TICK_MED = 3;
var
CLR_SMETER_BG, CLR_BAR_GREEN, CLR_BAR_OVER: TColor;
CLR_PEAK_MARKER, CLR_TICK_GREEN, CLR_TICK_WHITE, CLR_TICK_BLUE: TColor;
CLR_LABEL_DBM, CLR_LABEL_S, CLR_SCALE_DBM: TColor;
CLR_SCALE_S_GREEN, CLR_SCALE_S_BLUE, CLR_BDR: TColor;
W, H, BX, BW, LeftInfo: Integer;
Y_BAR_TOP, Y_BAR_BOT: Integer;
BarEnd, PeakLeft, PeakRight, S9X, OvrX: Integer;
i, X, TW: Integer; Lbl: string; SNum, Over: Integer;
LblDbm, LblS: string;
TWDbm, LblZoneL, TH10, TH6, MaxTWDbm, MaxTWS, LabelNeed: Integer;
Sc: Integer; // масштаб канвы, см. FScale
function DBtoX(dB: Double): Integer; inline;
begin
Result := BX + Round((dB - DB_MIN) / (DB_MAX - DB_MIN) * BW);
if Result < BX then Result := BX;
if Result > BX + BW then Result := BX + BW;
end;
procedure VLine(X2, Y1, Y2: Integer; C: TColor);
begin ACanvas.Pen.Color := C; ACanvas.MoveTo(X2, Y1); ACanvas.LineTo(X2, Y2); end;
procedure FillBar(X1, Y1, X2, Y2: Integer; C: TColor);
begin
ACanvas.Brush.Color := C; ACanvas.Brush.Style := bsSolid;
ACanvas.Pen.Style := psClear;
if X2 > X1 then ACanvas.FillRect(Rect(X1, Y1, X2, Y2));
ACanvas.Pen.Style := psSolid;
end;
begin
CLR_SMETER_BG := FTheme.SMeterBG;
CLR_BAR_GREEN := FTheme.SMeterBarGreen;
CLR_BAR_OVER := FTheme.SMeterBarOver;
CLR_PEAK_MARKER := FTheme.SMeterPeakMarker;
CLR_TICK_GREEN := FTheme.SMeterTickGreen;
CLR_TICK_WHITE := FTheme.SMeterTickWhite;
CLR_TICK_BLUE := FTheme.SMeterTickBlue;
CLR_LABEL_DBM := FTheme.SMeterLabelDbm;
CLR_LABEL_S := FTheme.SMeterLabelS;
CLR_SCALE_DBM := FTheme.SMeterScaleDbm;
CLR_SCALE_S_GREEN:= FTheme.SMeterScaleSGreen;
CLR_SCALE_S_BLUE := FTheme.SMeterScaleSBlue;
CLR_BDR := FTheme.SMeterBdr;
Sc := FScale;
W := R.Right - R.Left; H := R.Bottom - R.Top;
ZX1 := 0; ZX2 := 0; ZY1 := 0; ZY2 := 0;
if (W < 80 * Sc) or (H < 16 * Sc) then Exit;
LblDbm := Format('%.1f', [Value]);
if Value >= -73.0 then
begin
Over := Round(Value - (-73.0));
if Over < 5 then LblS := 'S9'
else begin Over := ((Over + 5) div 10) * 10; if Over = 0 then Over := 10; LblS := Format('S9+%d', [Over]); end;
end else if Value <= -121.0 then LblS := 'S1'
else begin
LblS := 'S1';
for SNum := 1 to 8 do
if Value >= (-121.0 + (SNum - 1) * 6.0) then LblS := 'S' + IntToStr(SNum);
end;
ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold];
ACanvas.Font.Size := 10 * Sc; TWDbm := ACanvas.TextWidth(LblDbm);
MaxTWDbm := ACanvas.TextWidth('-120.0');
MaxTWS := ACanvas.TextWidth('S9+60');
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := [];
LabelNeed := Max(MaxTWDbm + 2 * Sc + ACanvas.TextWidth('dBm '), MaxTWS) + LABEL_PAD * 2 * Sc;
LeftInfo := Max(LEFT_INFO_MIN * Sc, LabelNeed + 4 * Sc);
BX := R.Left + LeftInfo; BW := W - LeftInfo - RIGHT_PAD * Sc;
if BW < 10 * Sc then Exit;
LblZoneL := BX - LabelNeed;
Y_BAR_TOP := R.Top + H * 44 div 100;
Y_BAR_BOT := R.Top + H * 58 div 100;
S9X := DBtoX(DB_S9); OvrX := DBtoX(DB_OVR);
ACanvas.Brush.Color := CLR_SMETER_BG; ACanvas.Brush.Style := bsSolid;
ACanvas.Pen.Style := psClear; ACanvas.FillRect(R); ACanvas.Pen.Style := psSolid;
// Перо в физических пикселях: иначе на Retina все риски станут вдвое тоньше.
ACanvas.Pen.Width := Sc;
FillBar(BX, Y_BAR_TOP, S9X, Y_BAR_BOT, CLR_BAR_GREEN);
FillBar(S9X, Y_BAR_TOP, OvrX, Y_BAR_BOT, CLR_BAR_GREEN);
FillBar(OvrX, Y_BAR_TOP, BX + BW, Y_BAR_BOT, CLR_BAR_OVER);
BarEnd := DBtoX(Value);
PeakLeft := DBtoX(MinVal);
PeakRight := DBtoX(Peak);
ZX1 := Max(BX + 1, Min(PeakLeft, PeakRight));
ZX2 := Min(BX + BW - 1, Max(PeakLeft, PeakRight));
ZY1 := Y_BAR_TOP + Sc; ZY2 := Y_BAR_BOT - Sc;
if BarEnd > BX then
begin
if BarEnd <= S9X then
FillBar(BX, Y_BAR_TOP+Sc, BarEnd, Y_BAR_BOT-Sc, CLR_BAR_GREEN)
else begin
FillBar(BX, Y_BAR_TOP+Sc, S9X, Y_BAR_BOT-Sc, CLR_BAR_GREEN);
FillBar(S9X, Y_BAR_TOP+Sc, BarEnd, Y_BAR_BOT-Sc, CLR_BAR_OVER);
end;
end;
if (PeakRight > BX) and (PeakRight <= BX + BW) then
begin
ACanvas.Pen.Color := CLR_PEAK_MARKER; ACanvas.Pen.Width := 2 * Sc;
ACanvas.MoveTo(PeakRight, Y_BAR_TOP - (Y_BAR_BOT - Y_BAR_TOP));
ACanvas.LineTo(PeakRight, Y_BAR_BOT + (Y_BAR_BOT - Y_BAR_TOP));
ACanvas.Pen.Width := Sc;
end;
ACanvas.Brush.Style := bsClear; ACanvas.Pen.Color := CLR_BDR;
ACanvas.Rectangle(LblZoneL, Y_BAR_TOP, BX + BW, Y_BAR_BOT);
ACanvas.Pen.Color := FTheme.SMeterDivider;
ACanvas.MoveTo(BX, Y_BAR_TOP + Sc); ACanvas.LineTo(BX, Y_BAR_BOT - Sc);
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; ACanvas.Brush.Style := bsClear;
for i := 0 to High(DBM_MARKS) do
begin
X := DBtoX(DBM_MARKS[i]);
VLine(X, Y_BAR_TOP - TICK_LONG * Sc, Y_BAR_TOP - Sc, CLR_TICK_WHITE);
Lbl := DBM_LABELS[i]; TW := ACanvas.TextWidth(Lbl);
ACanvas.Font.Color := CLR_SCALE_DBM;
ACanvas.TextOut(X - TW div 2, Y_BAR_TOP - TICK_LONG * Sc - ACanvas.TextHeight(Lbl) - Sc, Lbl);
end;
i := -125;
while i < -13 do
begin
X := DBtoX(i); VLine(X, Y_BAR_TOP - TICK_MED * Sc, Y_BAR_TOP - Sc, CLR_TICK_GREEN);
Inc(i, 5);
end;
for i := 0 to High(S_MARKS_DBM) do
begin
X := DBtoX(S_MARKS_DBM[i]);
if S_MARKS_DBM[i] >= DB_S9 then VLine(X, Y_BAR_BOT+Sc, Y_BAR_BOT+TICK_LONG*Sc, CLR_TICK_BLUE)
else VLine(X, Y_BAR_BOT+Sc, Y_BAR_BOT+TICK_LONG*Sc, CLR_TICK_GREEN);
Lbl := S_MARKS_LBL[i]; TW := ACanvas.TextWidth(Lbl);
if S_MARKS_DBM[i] >= DB_S9 then ACanvas.Font.Color := CLR_SCALE_S_BLUE
else ACanvas.Font.Color := CLR_SCALE_S_GREEN;
ACanvas.TextOut(X - TW div 2, Y_BAR_BOT + TICK_LONG * Sc + Sc, Lbl);
end;
ACanvas.Brush.Style := bsClear;
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; TH10 := ACanvas.TextHeight('0');
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; TH6 := ACanvas.TextHeight('0');
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_DBM;
ACanvas.TextOut(LblZoneL + 4 * Sc, Y_BAR_TOP - TH10 - 2 * Sc, LblDbm);
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; ACanvas.Font.Color := CLR_TICK_BLUE;
ACanvas.TextOut(LblZoneL + 4 * Sc + TWDbm + 2 * Sc, Y_BAR_TOP - TH6 - 4 * Sc, 'dBm');
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_S;
ACanvas.TextOut(LblZoneL + 4 * Sc, Y_BAR_BOT + 2 * Sc, LblS);
end;
// ────────────────────────────────────────────────────────────────────────────
// DrawSMeterZone
// ────────────────────────────────────────────────────────────────────────────
procedure TSMeterView.DrawSMeterZone(ACanvas: TCanvas; R: TRect;
X1, X2, Y1, Y2: Integer);
const ALPHA = 16384;
var Img: TLazIntfImage; FC: TFPColor; IX, IY: Integer;
begin
if (X2 <= X1) or (FSmBitmap = nil) then Exit;
Img := FSmBitmap.CreateIntfImage;
try
for IY := Y1 to Y2 - 1 do
for IX := X1 to X2 - 1 do
begin
FC := Img.Colors[IX, IY];
FC.Red := Min(65535, FC.Red + ALPHA);
FC.Green := Min(65535, FC.Green + ALPHA);
FC.Blue := Min(65535, FC.Blue + ALPHA);
Img.Colors[IX, IY] := FC;
end;
FSmBitmap.LoadFromIntfImage(Img);
finally Img.Free; end;
end;
// ────────────────────────────────────────────────────────────────────────────
// DrawTXMeter
// ────────────────────────────────────────────────────────────────────────────
procedure TSMeterView.DrawTXMeter(ACanvas: TCanvas; R: TRect);
const
SWR_HIGH_THRESH = 2.5;
SWR_MAX = 10.0;
LEFT_INFO_MIN = 62; RIGHT_PAD = 8; LABEL_PAD = 4;
TICK_LONG = 5; TICK_MED = 3;
PWR_MARKS: array[0..4] of Double = (0.0, 0.25, 0.5, 0.75, 1.0);
SWR_MARKS_VAL: array[0..3] of Double = (1.0, 2.5, 5.0, 10.0);
SWR_MARKS_LBL: array[0..3] of string = ('1', '2.5', '5', '10');
var
CLR_SMETER_BG, CLR_BAR_GREEN, CLR_BAR_OVER: TColor;
CLR_TICK_GREEN, CLR_TICK_WHITE, CLR_TICK_BLUE: TColor;
CLR_LABEL_DBM, CLR_LABEL_S: TColor;
CLR_SCALE_DBM, CLR_SCALE_S_GREEN, CLR_SCALE_S_BLUE, CLR_BDR: TColor;
CLR_SWR_HIGH: TColor;
W, H, BX, BW, LeftInfo: Integer;
Y_BAR_TOP, Y_BAR_BOT, Y_BAR_MID: Integer;
PY1, PY2, SY1, SY2: Integer;
PBarEnd, SBarEnd, S_WarnX: Integer;
LblPwr, LblSWR: string;
TWPwr, LblZoneL, TH10, TH6, MaxTWPwr, MaxTWSWR, LabelNeed: Integer;
Sc: Integer; // масштаб канвы, см. FScale
i, X, TW: Integer;
Lbl: string;
SWRHigh: Boolean;
PwrPct: Double;
function PwrToX(Pct: Double): Integer; inline;
begin
Result := BX + Round(Max(0.0, Min(1.0, Pct)) * BW);
if Result < BX then Result := BX;
if Result > BX + BW then Result := BX + BW;
end;
function SWRToX(SWR: Double): Integer; inline;
begin
Result := BX + Round(Max(0.0, Min(1.0, (SWR - 1.0) / (SWR_MAX - 1.0))) * BW);
if Result < BX then Result := BX;
if Result > BX + BW then Result := BX + BW;
end;
procedure VLine(X2, Y1, Y2: Integer; C: TColor);
begin ACanvas.Pen.Color := C; ACanvas.MoveTo(X2, Y1); ACanvas.LineTo(X2, Y2); end;
procedure FillBar(X1, Y1, X2, Y2: Integer; C: TColor);
begin
ACanvas.Brush.Color := C; ACanvas.Brush.Style := bsSolid;
ACanvas.Pen.Style := psClear;
if X2 > X1 then ACanvas.FillRect(Rect(X1, Y1, X2, Y2));
ACanvas.Pen.Style := psSolid;
end;
begin
CLR_SMETER_BG := FTheme.SMeterBG;
CLR_BAR_GREEN := FTheme.SMeterBarGreen;
CLR_BAR_OVER := FTheme.SMeterBarOver;
CLR_TICK_GREEN := FTheme.SMeterTickGreen;
CLR_TICK_WHITE := FTheme.SMeterTickWhite;
CLR_TICK_BLUE := FTheme.SMeterTickBlue;
CLR_LABEL_DBM := FTheme.SMeterLabelDbm;
CLR_LABEL_S := FTheme.SMeterLabelS;
CLR_SCALE_DBM := FTheme.SMeterScaleDbm;
CLR_SCALE_S_GREEN:= FTheme.SMeterScaleSGreen;
CLR_SCALE_S_BLUE := FTheme.SMeterScaleSBlue;
CLR_BDR := FTheme.SMeterBdr;
CLR_SWR_HIGH := TColor($000000CC);
Sc := FScale;
W := R.Right - R.Left; H := R.Bottom - R.Top;
if (W < 80 * Sc) or (H < 16 * Sc) then Exit;
SWRHigh := FHasPATelemetry and (FLastSWR > SWR_HIGH_THRESH);
if FHasPATelemetry then
begin
LblPwr := Format('%.0fW', [FLastFwdW]);
LblSWR := Format('SWR:%.1f', [FLastSWR]);
end
else
begin
LblPwr := ''; // измерять нечем — показания не выдумываем
LblSWR := '';
end;
ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold];
ACanvas.Font.Size := 10 * Sc;
TWPwr := ACanvas.TextWidth(LblPwr);
MaxTWPwr := ACanvas.TextWidth(Format('%.0fW', [FPAMaxPower]));
MaxTWSWR := ACanvas.TextWidth('SWR:10.0');
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := [];
LabelNeed := Max(MaxTWPwr, MaxTWSWR) + LABEL_PAD * 2 * Sc;
LeftInfo := Max(LEFT_INFO_MIN * Sc, LabelNeed + 4 * Sc);
BX := R.Left + LeftInfo; BW := W - LeftInfo - RIGHT_PAD * Sc;
if BW < 10 * Sc then Exit;
LblZoneL := BX - LabelNeed;
Y_BAR_TOP := R.Top + H * 44 div 100;
Y_BAR_BOT := R.Top + H * 58 div 100;
Y_BAR_MID := (Y_BAR_TOP + Y_BAR_BOT) div 2;
PY1 := Y_BAR_TOP;
PY2 := Y_BAR_MID - Sc;
SY1 := Y_BAR_MID + Sc;
SY2 := Y_BAR_BOT;
S_WarnX := SWRToX(SWR_HIGH_THRESH);
ACanvas.Brush.Color := CLR_SMETER_BG; ACanvas.Brush.Style := bsSolid;
ACanvas.Pen.Style := psClear; ACanvas.FillRect(R); ACanvas.Pen.Style := psSolid;
// Перо в физических пикселях, как и в DrawSMeterWide.
ACanvas.Pen.Width := Sc;
FillBar(BX, PY1, BX + BW, PY2, CLR_BAR_GREEN);
FillBar(BX, SY1, S_WarnX, SY2, CLR_BAR_GREEN);
FillBar(S_WarnX, SY1, BX + BW, SY2, CLR_BAR_OVER);
if FHasPATelemetry then
begin
if FPAMaxPower > 0 then PwrPct := FLastFwdW / FPAMaxPower else PwrPct := 0;
PBarEnd := PwrToX(PwrPct);
FillBar(BX, PY1 + Sc, PBarEnd, PY2 - Sc, SM_CLR_METER_ON);
end;
if FHasPATelemetry then
begin
SBarEnd := SWRToX(FLastSWR);
if not SWRHigh then
FillBar(BX, SY1 + Sc, SBarEnd, SY2 - Sc, SM_CLR_AMBER)
else
begin
FillBar(BX, SY1 + Sc, Min(S_WarnX, SBarEnd), SY2 - Sc, SM_CLR_AMBER);
if SBarEnd > S_WarnX then
FillBar(S_WarnX, SY1 + Sc, SBarEnd, SY2 - Sc, CLR_SWR_HIGH);
end;
end;
VLine(S_WarnX, SY1, SY2, FTheme.SMeterDivider);
ACanvas.Brush.Style := bsClear; ACanvas.Pen.Color := CLR_BDR;
ACanvas.Rectangle(LblZoneL, PY1, BX + BW, PY2);
if SWRHigh then ACanvas.Pen.Color := CLR_SWR_HIGH
else ACanvas.Pen.Color := CLR_BDR;
ACanvas.Rectangle(LblZoneL, SY1, BX + BW, SY2);
ACanvas.Pen.Color := FTheme.SMeterDivider;
ACanvas.MoveTo(BX, PY1 + Sc); ACanvas.LineTo(BX, PY2 - Sc);
ACanvas.MoveTo(BX, SY1 + Sc); ACanvas.LineTo(BX, SY2 - Sc);
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; ACanvas.Brush.Style := bsClear;
i := 5;
while i < 100 do
begin
if (i mod 25) <> 0 then
begin
X := PwrToX(i / 100.0);
VLine(X, PY1 - TICK_MED * Sc, PY1 - Sc, CLR_TICK_GREEN);
end;
Inc(i, 5);
end;
for i := 0 to 4 do
begin
X := PwrToX(PWR_MARKS[i]);
VLine(X, PY1 - TICK_LONG * Sc, PY1 - Sc, CLR_TICK_WHITE);
// Ватты на делениях считаются от Max power, а он есть только там, где есть
// детектор. Без него оставляем голые деления: подписать их нечем.
if not FHasPATelemetry then Continue;
Lbl := Format('%.0f', [PWR_MARKS[i] * FPAMaxPower]);
TW := ACanvas.TextWidth(Lbl);
ACanvas.Font.Color := CLR_SCALE_DBM;
ACanvas.TextOut(X - TW div 2, PY1 - TICK_LONG * Sc - ACanvas.TextHeight(Lbl) - Sc, Lbl);
end;
for i := 2 to 9 do
begin
if i = 5 then Continue;
X := SWRToX(i);
if i >= 3 then VLine(X, SY2 + Sc, SY2 + TICK_MED * Sc, CLR_TICK_BLUE)
else VLine(X, SY2 + Sc, SY2 + TICK_MED * Sc, CLR_TICK_GREEN);
end;
for i := 0 to 3 do
begin
X := SWRToX(SWR_MARKS_VAL[i]);
if SWR_MARKS_VAL[i] >= SWR_HIGH_THRESH then
VLine(X, SY2 + Sc, SY2 + TICK_LONG * Sc, CLR_TICK_BLUE)
else
VLine(X, SY2 + Sc, SY2 + TICK_LONG * Sc, CLR_TICK_GREEN);
Lbl := SWR_MARKS_LBL[i]; TW := ACanvas.TextWidth(Lbl);
if SWR_MARKS_VAL[i] >= SWR_HIGH_THRESH then
begin
if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH
else ACanvas.Font.Color := CLR_SCALE_S_BLUE;
end else
ACanvas.Font.Color := CLR_SCALE_S_GREEN;
ACanvas.TextOut(X - TW div 2, SY2 + TICK_LONG * Sc + Sc, Lbl);
end;
ACanvas.Brush.Style := bsClear;
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; TH10 := ACanvas.TextHeight('0');
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; TH6 := ACanvas.TextHeight('0');
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_DBM;
ACanvas.TextOut(LblZoneL + 4 * Sc, PY1 - TH10 - 2 * Sc, LblPwr);
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold];
if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH
else ACanvas.Font.Color := CLR_LABEL_S;
ACanvas.TextOut(LblZoneL + 4 * Sc, SY2 + 2 * Sc, LblSWR);
if SWRHigh then
begin
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_SWR_HIGH;
ACanvas.TextOut(LblZoneL + 4 * Sc + TWPwr + 2 * Sc, PY1 - TH6 - 4 * Sc, 'SWR High');
end;
end;
// ────────────────────────────────────────────────────────────────────────────
// PaintSMeterRight
// ────────────────────────────────────────────────────────────────────────────
procedure TSMeterView.PaintSMeterRight(Sender: TObject);
var PB: TPaintBox; W, H, BmpW, BmpH, ZX1, ZX2, ZY1, ZY2: Integer;
begin
PB := TPaintBox(Sender);
W := PB.Width; H := PB.Height;
if (W <= 0) or (H <= 0) then Exit;
// Битмап держим в физических пикселях канвы, иначе Cocoa растянет его вдвое
// и картинка замылится. На Windows/Linux FScale=1 и всё как было.
FScale := GetControlScale(PB);
BmpW := W * FScale; BmpH := H * FScale;
if (FSmBitmap = nil) or (FSmBitmap.Width <> BmpW) or (FSmBitmap.Height <> BmpH) then
begin
FreeAndNil(FSmBitmap);
FSmBitmap := TBitmap.Create;
FSmBitmap.SetSize(BmpW, BmpH);
end;
if FTransmitting then
begin
DrawTXMeter(FSmBitmap.Canvas, Rect(0, 0, BmpW, BmpH));
end else
begin
DrawSMeterWide(FSmBitmap.Canvas, Rect(0, 0, BmpW, BmpH),
FLastSMeter, FSMeterPeak, FSMeterMin,
ZX1, ZX2, ZY1, ZY2);
DrawSMeterZone(FSmBitmap.Canvas, Rect(0, 0, BmpW, BmpH), ZX1, ZX2, ZY1, ZY2);
end;
if FScale = 1 then
PB.Canvas.Draw(0, 0, FSmBitmap)
else
PB.Canvas.StretchDraw(Rect(0, 0, W, H), FSmBitmap);
end;
end.