Render sample rate overlay into spectrum bitmap

This commit is contained in:
2026-05-22 17:15:29 +03:00
parent ea6e57b8b8
commit 26aa55ddde
3 changed files with 195 additions and 90 deletions
+35 -5
View File
@@ -438,6 +438,7 @@ type
procedure SetRadioOfflineStatus(const ConnText: string; ClearDevice: Boolean = False); procedure SetRadioOfflineStatus(const ConnText: string; ClearDevice: Boolean = False);
procedure ApplyStatusTheme(const T: TAppTheme); procedure ApplyStatusTheme(const T: TAppTheme);
procedure PositionSampleRateOverlay; procedure PositionSampleRateOverlay;
procedure OnSampleRateOverlayInvalidate(Sender: TObject);
procedure OnSampleRateSelect(SampleRate: Integer); procedure OnSampleRateSelect(SampleRate: Integer);
procedure OnSampleRateHidePanel; procedure OnSampleRateHidePanel;
procedure UpdateVfoDisplay; procedure UpdateVfoDisplay;
@@ -523,6 +524,7 @@ type
X, Y: Integer); X, Y: Integer);
procedure PbSpectrumMouseUp(Sender: TObject; Button: TMouseButton; procedure PbSpectrumMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); Shift: TShiftState; X, Y: Integer);
procedure PbSpectrumMouseLeave(Sender: TObject);
procedure PbWaterfallMouseDown(Sender: TObject; Button: TMouseButton; procedure PbWaterfallMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); Shift: TShiftState; X, Y: Integer);
procedure PbWaterfallMouseMove(Sender: TObject; Shift: TShiftState; procedure PbWaterfallMouseMove(Sender: TObject; Shift: TShiftState;
@@ -1929,10 +1931,11 @@ begin
PanelRight.OnResize := RightPanelResize; PanelRight.OnResize := RightPanelResize;
FSampleRateOverlay := TSampleRateOverlay.Create(Self); FSampleRateOverlay := TSampleRateOverlay.Create(Self);
FSampleRateOverlay.Parent := PanelRight;
FSampleRateOverlay.OnSpanSelect := OnSampleRateSelect; FSampleRateOverlay.OnSpanSelect := OnSampleRateSelect;
FSampleRateOverlay.OnHidePanel := OnSampleRateHidePanel; FSampleRateOverlay.OnHidePanel := OnSampleRateHidePanel;
FSampleRateOverlay.OnInvalidate := OnSampleRateOverlayInvalidate;
FSampleRateOverlay.SetCurrentRate(FSampleRate); FSampleRateOverlay.SetCurrentRate(FSampleRate);
FSpecView.SampleRateOverlay := FSampleRateOverlay;
// S-метр — внутри PanelToolbar, справа // S-метр — внутри PanelToolbar, справа
PanelSMeterRight := TPanel.Create(Self); PanelSMeterRight := TPanel.Create(Self);
@@ -1952,6 +1955,7 @@ begin
PbSpectrum.OnDblClick := PbSpectrumDblClick; PbSpectrum.OnDblClick := PbSpectrumDblClick;
PbSpectrum.OnMouseMove := PbSpectrumMouseMove; PbSpectrum.OnMouseMove := PbSpectrumMouseMove;
PbSpectrum.OnMouseUp := PbSpectrumMouseUp; PbSpectrum.OnMouseUp := PbSpectrumMouseUp;
PbSpectrum.OnMouseLeave := PbSpectrumMouseLeave;
PbRuler := TPaintBox.Create(Self); PbRuler := TPaintBox.Create(Self);
PbRuler.Parent := PanelRight; PbRuler.Parent := PanelRight;
@@ -2538,15 +2542,22 @@ end;
procedure TMainForm.PositionSampleRateOverlay; procedure TMainForm.PositionSampleRateOverlay;
const const
MARGIN = 4; MARGIN_X = 34;
MARGIN_Y = 4;
begin begin
if (FSampleRateOverlay = nil) or (PbSpectrum = nil) then Exit; if (FSampleRateOverlay = nil) or (PbSpectrum = nil) then Exit;
FSampleRateOverlay.SetBounds( FSampleRateOverlay.SetBounds(
PbSpectrum.Left + MARGIN, MARGIN_X,
PbSpectrum.Top + MARGIN, MARGIN_Y,
FSampleRateOverlay.Width, FSampleRateOverlay.Width,
FSampleRateOverlay.Height); FSampleRateOverlay.Height);
FSampleRateOverlay.BringToFront; end;
procedure TMainForm.OnSampleRateOverlayInvalidate(Sender: TObject);
begin
FSpectrumDirty := True;
if Assigned(FSpecView) then FSpecView.SpectrumDirty := True;
if Assigned(PbSpectrum) then PbSpectrum.Invalidate;
end; end;
// =========================================================================== // ===========================================================================
@@ -4637,6 +4648,9 @@ end;
procedure TMainForm.PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton; procedure TMainForm.PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); Shift: TShiftState; X, Y: Integer);
begin begin
if Assigned(FSampleRateOverlay) and
FSampleRateOverlay.HandleMouseDown(Button, X, Y) then Exit;
if Button = mbLeft then if Button = mbLeft then
begin begin
// Левая кнопка: сбрасываем маркер если активен // Левая кнопка: сбрасываем маркер если активен
@@ -4677,6 +4691,15 @@ end;
procedure TMainForm.PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState; procedure TMainForm.PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer); X, Y: Integer);
begin begin
if Assigned(FSampleRateOverlay) then
begin
FSampleRateOverlay.HandleMouseMove(X, Y);
if (FSampleRateOverlay.HotIdx >= 0) and (not FSpecDrag) then
PbSpectrum.Cursor := crHandPoint
else if not FSpecDrag then
PbSpectrum.Cursor := crDefault;
end;
if FSpecDrag and (ssLeft in Shift) then if FSpecDrag and (ssLeft in Shift) then
DoSpectrumDrag(X, PbSpectrum.Width); DoSpectrumDrag(X, PbSpectrum.Width);
// Маркер следует за курсором — обновление произойдёт в ближайшем тике таймера (50ms) // Маркер следует за курсором — обновление произойдёт в ближайшем тике таймера (50ms)
@@ -4699,6 +4722,13 @@ begin
end; end;
end; end;
procedure TMainForm.PbSpectrumMouseLeave(Sender: TObject);
begin
if Assigned(FSampleRateOverlay) then
FSampleRateOverlay.HandleMouseLeave;
if not FSpecDrag then PbSpectrum.Cursor := crDefault;
end;
// Водопад (аналогично) // Водопад (аналогично)
procedure TMainForm.PbWaterfallMouseDown(Sender: TObject; Button: TMouseButton; procedure TMainForm.PbWaterfallMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); Shift: TShiftState; X, Y: Integer);
+155 -84
View File
@@ -1,9 +1,9 @@
unit SampleRateOverlay; unit SampleRateOverlay;
{ {
Overlay виджет для выбора sample rate (span) спектра. Bitmap overlay для выбора sample rate (span) спектра.
Размещается в верхнем левом углу спектрограммы поверх PbSpectrum. Рисуется прямо в bitmap спектра, как AlertOverlay, поэтому фон под ним
Содержит кнопку скрытия левой панели и ряд кнопок span. смешивается вручную через alpha.
} }
{$IFDEF FPC} {$IFDEF FPC}
@@ -19,32 +19,43 @@ type
TSpanSelectEvent = procedure(SampleRate: Integer) of object; TSpanSelectEvent = procedure(SampleRate: Integer) of object;
THidePanelEvent = procedure of object; THidePanelEvent = procedure of object;
TSampleRateOverlay = class(TCustomControl) TSampleRateOverlay = class(TComponent)
private private
FCurrentRate: Integer; FCurrentRate: Integer;
FPanelHidden: Boolean; FPanelHidden: Boolean;
FOnSpanSelect: TSpanSelectEvent; FOnSpanSelect: TSpanSelectEvent;
FOnHidePanel: THidePanelEvent; FOnHidePanel: THidePanelEvent;
FOnInvalidate: TNotifyEvent;
FLeft: Integer;
FTop: Integer;
FWidth: Integer;
FHeight: Integer;
FHitRects: array[0..6] of TRect; // [0]=hide button, [1..6]=span buttons FHitRects: array[0..6] of TRect; // [0]=hide button, [1..6]=span buttons
FHitValues: array[0..6] of Integer; // 0=hide, else sample rate FHitValues: array[0..6] of Integer; // 0=hide, else sample rate
FHitCount: Integer; FHitCount: Integer;
FHotIdx: Integer; FHotIdx: Integer;
procedure DrawSelf(C: TCanvas; W, H: Integer); procedure DrawSelf(Target: TBitmap; C: TCanvas; OriginX, OriginY, W, H: Integer);
procedure RegisterHit(const HR: TRect; Value: Integer); procedure RegisterHit(const HR: TRect; Value: Integer);
protected procedure RequestInvalidate;
procedure Paint; override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseLeave; override;
public public
constructor Create(AOwner: TComponent); override; constructor Create(AOwner: TComponent); override;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
procedure DrawOverlay(Target: TBitmap; C: TCanvas; W, H: Integer);
function HandleMouseMove(X, Y: Integer): Boolean;
function HandleMouseDown(Button: TMouseButton; X, Y: Integer): Boolean;
function HandleMouseLeave: Boolean;
procedure SetCurrentRate(Rate: Integer); procedure SetCurrentRate(Rate: Integer);
procedure SetPanelHidden(Hidden: Boolean); procedure SetPanelHidden(Hidden: Boolean);
property Left: Integer read FLeft;
property Top: Integer read FTop;
property Width: Integer read FWidth;
property Height: Integer read FHeight;
property HotIdx: Integer read FHotIdx;
property OnSpanSelect: TSpanSelectEvent read FOnSpanSelect write FOnSpanSelect; property OnSpanSelect: TSpanSelectEvent read FOnSpanSelect write FOnSpanSelect;
property OnHidePanel: THidePanelEvent read FOnHidePanel write FOnHidePanel; property OnHidePanel: THidePanelEvent read FOnHidePanel write FOnHidePanel;
property OnInvalidate: TNotifyEvent read FOnInvalidate write FOnInvalidate;
end; end;
implementation implementation
@@ -59,14 +70,44 @@ const
PAD_Y = 4; PAD_Y = 4;
BTN_GAP = 3; BTN_GAP = 3;
CLR_BG = TColor($00141414);
CLR_BORDER = TColor($00505050);
CLR_TEXT = TColor($00D8E6EE); CLR_TEXT = TColor($00D8E6EE);
CLR_BTN_NORM = TColor($00543F18);
CLR_BTN_HOT = TColor($00624B1D);
CLR_BTN_ACT = TColor($00785A20);
CLR_BTN_BDR = TColor($00685624); CLR_BTN_BDR = TColor($00685624);
procedure BlendRect(Target: TBitmap; const Rct: TRect; R, G, B, Alpha: Byte);
var
Y, X, InvA: Integer;
Row: PByte;
RR: TRect;
begin
if (Target = nil) or (Alpha = 0) then Exit;
RR := Rct;
if RR.Left < 0 then RR.Left := 0;
if RR.Top < 0 then RR.Top := 0;
if RR.Right > Target.Width then RR.Right := Target.Width;
if RR.Bottom > Target.Height then RR.Bottom := Target.Height;
if (RR.Right <= RR.Left) or (RR.Bottom <= RR.Top) then Exit;
InvA := 255 - Alpha;
Target.BeginUpdate(False);
try
for Y := RR.Top to RR.Bottom - 1 do
begin
Row := PByte(Target.ScanLine[Y]);
if Row = nil then Continue;
Inc(Row, RR.Left * 4);
for X := RR.Left to RR.Right - 1 do
begin
Row[0] := Byte((Alpha * B + InvA * Row[0]) div 255);
Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255);
Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255);
Inc(Row, 4);
end;
end;
finally
Target.EndUpdate(False);
end;
end;
function SpanBtnWidth(const Name: string; C: TCanvas): Integer; function SpanBtnWidth(const Name: string; C: TCanvas): Integer;
begin begin
C.Font.Name := 'Courier New'; C.Font.Name := 'Courier New';
@@ -79,108 +120,133 @@ end;
constructor TSampleRateOverlay.Create(AOwner: TComponent); constructor TSampleRateOverlay.Create(AOwner: TComponent);
begin begin
inherited Create(AOwner); inherited Create(AOwner);
ControlStyle := ControlStyle + [csOpaque]; FCurrentRate := 192000;
FCurrentRate := 192000; FPanelHidden := False;
FPanelHidden := False; FHitCount := 0;
FHitCount := 0; FHotIdx := -1;
FHotIdx := -1; FWidth := 320;
Cursor := crHandPoint; FHeight := SPAN_H + PAD_Y * 2;
Width := 320; end;
Height := SPAN_H + PAD_Y * 2;
procedure TSampleRateOverlay.RequestInvalidate;
begin
if Assigned(FOnInvalidate) then FOnInvalidate(Self);
end;
procedure TSampleRateOverlay.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
if (FLeft = ALeft) and (FTop = ATop) and
(FWidth = AWidth) and (FHeight = AHeight) then Exit;
FLeft := ALeft;
FTop := ATop;
FWidth := AWidth;
FHeight := AHeight;
RequestInvalidate;
end; end;
procedure TSampleRateOverlay.RegisterHit(const HR: TRect; Value: Integer); procedure TSampleRateOverlay.RegisterHit(const HR: TRect; Value: Integer);
begin begin
if FHitCount > High(FHitRects) then Exit; if FHitCount > High(FHitRects) then Exit;
FHitRects[FHitCount] := HR; FHitRects[FHitCount] := HR;
FHitValues[FHitCount] := Value; FHitValues[FHitCount] := Value;
Inc(FHitCount); Inc(FHitCount);
end; end;
procedure TSampleRateOverlay.DrawSelf(C: TCanvas; W, H: Integer); procedure TSampleRateOverlay.DrawSelf(Target: TBitmap; C: TCanvas;
OriginX, OriginY, W, H: Integer);
var var
X, I, TW, TH, BtnW: Integer; X, I, TW, TH, BtnW, ContentW: Integer;
HR: TRect; HR, FullR: TRect;
IsAct, IsHot: Boolean; IsAct, IsHot: Boolean;
Cap: string; Cap: string;
begin begin
if (Target = nil) or (C = nil) then Exit;
FHitCount := 0; FHitCount := 0;
// Фон C.Font.Name := 'Courier New';
C.Brush.Color := CLR_BG; C.Font.Size := 7;
C.Brush.Style := bsSolid;
C.Pen.Style := psClear;
C.FillRect(Rect(0, 0, W, H));
// Рамка
C.Pen.Style := psSolid;
C.Pen.Color := CLR_BORDER;
C.Brush.Style := bsClear;
C.Rectangle(0, 0, W, H);
C.Font.Name := 'Courier New';
C.Font.Size := 7;
C.Font.Style := []; C.Font.Style := [];
TH := C.TextHeight('A'); TH := C.TextHeight('A');
X := PAD_X;
// Кнопка скрытия панели (◀ или ▶) ContentW := PAD_X + HIDE_W + BTN_GAP;
HR := Rect(X, PAD_Y, X + HIDE_W, PAD_Y + SPAN_H); for I := 0 to High(SPAN_NAMES) do
IsHot := (FHotIdx = 0); begin
if IsHot then C.Brush.Color := CLR_BTN_HOT Inc(ContentW, SpanBtnWidth(SPAN_NAMES[I], C));
else C.Brush.Color := CLR_BTN_NORM; if I < High(SPAN_NAMES) then Inc(ContentW, BTN_GAP);
C.Brush.Style := bsSolid; C.Pen.Style := psClear; end;
C.FillRect(HR); Inc(ContentW, PAD_X);
C.Pen.Style := psSolid; C.Pen.Color := CLR_BTN_BDR; if ContentW > W then ContentW := W;
C.Brush.Style := bsClear; C.Rectangle(HR);
C.Font.Size := 8; FullR := Rect(OriginX, OriginY, OriginX + ContentW, OriginY + H);
if FPanelHidden then Cap := '▶' else Cap := '◀'; BlendRect(Target, FullR, $14, $14, $14, 104);
TW := C.TextWidth(Cap); C.Font.Size := 7; BlendRect(Target, Rect(FullR.Left, FullR.Top, FullR.Right, FullR.Top + 1),
C.Font.Color := CLR_TEXT; $80, $80, $80, 56);
BlendRect(Target, Rect(FullR.Left, FullR.Bottom - 1, FullR.Right, FullR.Bottom),
$00, $00, $00, 48);
BlendRect(Target, Rect(FullR.Left, FullR.Top, FullR.Left + 1, FullR.Bottom),
$80, $80, $80, 42);
BlendRect(Target, Rect(FullR.Right - 1, FullR.Top, FullR.Right, FullR.Bottom),
$00, $00, $00, 42);
X := OriginX + PAD_X;
HR := Rect(X, OriginY + PAD_Y, X + HIDE_W, OriginY + PAD_Y + SPAN_H);
IsHot := FHotIdx = 0;
if IsHot then BlendRect(Target, HR, $1D, $4B, $62, 206)
else BlendRect(Target, HR, $18, $3F, $54, 182);
C.Pen.Style := psSolid;
C.Pen.Color := CLR_BTN_BDR;
C.Brush.Style := bsClear;
C.Rectangle(HR);
C.Font.Size := 8;
if FPanelHidden then Cap := '>' else Cap := '<';
TW := C.TextWidth(Cap);
C.Font.Color := CLR_TEXT;
C.Brush.Style := bsClear; C.Brush.Style := bsClear;
C.Font.Size := 8;
C.TextOut(HR.Left + (HIDE_W - TW) div 2, C.TextOut(HR.Left + (HIDE_W - TW) div 2,
HR.Top + (SPAN_H - TH) div 2, Cap); HR.Top + (SPAN_H - TH) div 2, Cap);
C.Font.Size := 7; C.Font.Size := 7;
RegisterHit(HR, 0); RegisterHit(HR, 0);
Inc(X, HIDE_W + BTN_GAP); Inc(X, HIDE_W + BTN_GAP);
// Кнопки span
for I := 0 to High(SPAN_RATES) do for I := 0 to High(SPAN_RATES) do
begin begin
BtnW := SpanBtnWidth(SPAN_NAMES[I], C); BtnW := SpanBtnWidth(SPAN_NAMES[I], C);
HR := Rect(X, PAD_Y, X + BtnW, PAD_Y + SPAN_H); HR := Rect(X, OriginY + PAD_Y, X + BtnW, OriginY + PAD_Y + SPAN_H);
IsAct := (SPAN_RATES[I] = FCurrentRate); IsAct := SPAN_RATES[I] = FCurrentRate;
IsHot := (FHotIdx = I + 1); IsHot := FHotIdx = I + 1;
if IsAct then C.Brush.Color := CLR_BTN_ACT if IsAct then BlendRect(Target, HR, $20, $5A, $78, 220)
else if IsHot then C.Brush.Color := CLR_BTN_HOT else if IsHot then BlendRect(Target, HR, $1D, $4B, $62, 206)
else C.Brush.Color := CLR_BTN_NORM; else BlendRect(Target, HR, $18, $3F, $54, 182);
C.Brush.Style := bsSolid; C.Pen.Style := psClear; C.Pen.Style := psSolid;
C.FillRect(HR); C.Pen.Color := CLR_BTN_BDR;
C.Pen.Style := psSolid; C.Pen.Color := CLR_BTN_BDR; C.Brush.Style := bsClear;
C.Brush.Style := bsClear; C.Rectangle(HR); C.Rectangle(HR);
TW := C.TextWidth(SPAN_NAMES[I]); TW := C.TextWidth(SPAN_NAMES[I]);
C.Font.Color := CLR_TEXT; C.Font.Color := CLR_TEXT;
C.Brush.Style := bsClear; C.Brush.Style := bsClear;
C.TextOut(HR.Left + (BtnW - TW) div 2, C.TextOut(HR.Left + (BtnW - TW) div 2,
HR.Top + (SPAN_H - TH) div 2, SPAN_NAMES[I]); HR.Top + (SPAN_H - TH) div 2, SPAN_NAMES[I]);
RegisterHit(HR, SPAN_RATES[I]); RegisterHit(HR, SPAN_RATES[I]);
Inc(X, BtnW + BTN_GAP); Inc(X, BtnW + BTN_GAP);
end; end;
end; end;
procedure TSampleRateOverlay.Paint; procedure TSampleRateOverlay.DrawOverlay(Target: TBitmap; C: TCanvas; W, H: Integer);
begin begin
DrawSelf(Canvas, Width, Height); if (FWidth <= 0) or (FHeight <= 0) then Exit;
if (FLeft >= W) or (FTop >= H) then Exit;
DrawSelf(Target, C, FLeft, FTop, Min(FWidth, W - FLeft), Min(FHeight, H - FTop));
end; end;
procedure TSampleRateOverlay.MouseMove(Shift: TShiftState; X, Y: Integer); function TSampleRateOverlay.HandleMouseMove(X, Y: Integer): Boolean;
var var
I, OldHot: Integer; I, OldHot: Integer;
begin begin
Result := False;
OldHot := FHotIdx; OldHot := FHotIdx;
FHotIdx := -1; FHotIdx := -1;
for I := 0 to FHitCount - 1 do for I := 0 to FHitCount - 1 do
@@ -189,15 +255,18 @@ begin
FHotIdx := I; FHotIdx := I;
Break; Break;
end; end;
if FHotIdx <> OldHot then Invalidate; if FHotIdx <> OldHot then
inherited MouseMove(Shift, X, Y); begin
RequestInvalidate;
Result := True;
end;
end; end;
procedure TSampleRateOverlay.MouseDown(Button: TMouseButton; function TSampleRateOverlay.HandleMouseDown(Button: TMouseButton; X, Y: Integer): Boolean;
Shift: TShiftState; X, Y: Integer);
var var
I: Integer; I: Integer;
begin begin
Result := False;
if Button <> mbLeft then Exit; if Button <> mbLeft then Exit;
for I := 0 to FHitCount - 1 do for I := 0 to FHitCount - 1 do
if PtInRect(FHitRects[I], Point(X, Y)) then if PtInRect(FHitRects[I], Point(X, Y)) then
@@ -210,32 +279,34 @@ begin
begin begin
if Assigned(FOnSpanSelect) then FOnSpanSelect(FHitValues[I]); if Assigned(FOnSpanSelect) then FOnSpanSelect(FHitValues[I]);
end; end;
Result := True;
Break; Break;
end; end;
end; end;
procedure TSampleRateOverlay.MouseLeave; function TSampleRateOverlay.HandleMouseLeave: Boolean;
begin begin
Result := False;
if FHotIdx >= 0 then if FHotIdx >= 0 then
begin begin
FHotIdx := -1; FHotIdx := -1;
Invalidate; RequestInvalidate;
Result := True;
end; end;
inherited MouseLeave;
end; end;
procedure TSampleRateOverlay.SetCurrentRate(Rate: Integer); procedure TSampleRateOverlay.SetCurrentRate(Rate: Integer);
begin begin
if FCurrentRate = Rate then Exit; if FCurrentRate = Rate then Exit;
FCurrentRate := Rate; FCurrentRate := Rate;
Invalidate; RequestInvalidate;
end; end;
procedure TSampleRateOverlay.SetPanelHidden(Hidden: Boolean); procedure TSampleRateOverlay.SetPanelHidden(Hidden: Boolean);
begin begin
if FPanelHidden = Hidden then Exit; if FPanelHidden = Hidden then Exit;
FPanelHidden := Hidden; FPanelHidden := Hidden;
Invalidate; RequestInvalidate;
end; end;
end. end.
+5 -1
View File
@@ -20,7 +20,7 @@ interface
uses uses
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math, Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
IntfGraphics, FPImage, LCLIntf, LCLType, GraphType, AppTheme, IntfGraphics, FPImage, LCLIntf, LCLType, GraphType, AppTheme,
AlertOverlay, AlertOverlay, SampleRateOverlay,
WaterfallView, SMeterView, RulerView; WaterfallView, SMeterView, RulerView;
const const
@@ -69,6 +69,7 @@ type
FTXOverlay: Boolean; FTXOverlay: Boolean;
// ── ADC overload overlay ───────────────────────────────────────────────── // ── ADC overload overlay ─────────────────────────────────────────────────
FADCOverloadVisible: Boolean; FADCOverloadVisible: Boolean;
FSampleRateOverlay: TSampleRateOverlay;
// ── Marker ──────────────────────────────────────────────────────────────── // ── Marker ────────────────────────────────────────────────────────────────
FMarkerActive: Boolean; FMarkerActive: Boolean;
FMarkerX: Integer; FMarkerX: Integer;
@@ -197,6 +198,7 @@ type
property PbWaterfall: TPaintBox write SetPbWaterfall; property PbWaterfall: TPaintBox write SetPbWaterfall;
property PbRuler: TPaintBox write SetPbRuler; property PbRuler: TPaintBox write SetPbRuler;
property PbSMeterRight: TPaintBox write SetPbSMeterRight; property PbSMeterRight: TPaintBox write SetPbSMeterRight;
property SampleRateOverlay: TSampleRateOverlay read FSampleRateOverlay write FSampleRateOverlay;
// ── Данные от DSP ───────────────────────────────────────────────────────── // ── Данные от DSP ─────────────────────────────────────────────────────────
procedure SetSpectrumData(const Pixels: array of Single; Count: Integer); procedure SetSpectrumData(const Pixels: array of Single; Count: Integer);
@@ -914,6 +916,8 @@ begin
if FMarkerActive then DrawMarkerLine(C, W, H); if FMarkerActive then DrawMarkerLine(C, W, H);
DrawADCOverloadOverlay(C, W, H); DrawADCOverloadOverlay(C, W, H);
if Assigned(FSampleRateOverlay) then
FSampleRateOverlay.DrawOverlay(FSpectrumBitmap, C, W, H);
end; end;
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────