Add AGC picker to VfoOverlay DSP row

AGC button in DSP row shows current mode (FAST/MED/SLOW/LONG/OFF).
Click toggles a picker row below; selecting a mode closes it, fires
OnAGCChange, and syncs MainForm BtnAGCMode buttons and DSP engine.
Overlay height expands 136→159 px while picker is open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 13:16:21 +03:00
co-authored by Claude Sonnet 4.6
parent 8193a2b598
commit 24ff61cb05
2 changed files with 127 additions and 29 deletions
+109 -26
View File
@@ -10,6 +10,7 @@ uses
type
TModeFilterEvent = procedure(Mode: Integer; FilterBW: Integer) of object;
TDSPChangeEvent = procedure(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean) of object;
TAGCChangeEvent = procedure(AGCMode: Integer) of object;
TVfoOverlay = class(TCustomControl)
private
@@ -17,17 +18,20 @@ type
FFilterBW: Integer;
FVfoHz: Double;
FSMeterDB: Double;
FOnSelect: TModeFilterEvent;
FOnSelect: TModeFilterEvent;
FOnDSPChange: TDSPChangeEvent;
FOnAGCChange: TAGCChangeEvent;
FModeFilter: array[0..7] of Integer;
FNRMode: Integer; // 0=off, 1..4=NR/NR2/NR3/NR4
FNBMode: Integer; // 0=off, 1=NB, 2=NB2
FSNBOn: Boolean;
FANFOn: Boolean;
FAGCMode: Integer; // 0=FAST 1=MED 2=SLOW 3=LONG 4=OFF
FAGCPickerOpen: Boolean;
FHitRects: array[0..19] of TRect;
FHitActions: array[0..19] of Integer; // <0 = режим/DSP, >0 = BW фильтра
FHitRects: array[0..24] of TRect;
FHitActions: array[0..24] of Integer;
FHitCount: Integer;
FHotIdx: Integer;
@@ -36,6 +40,7 @@ type
procedure DrawModeRow(C: TCanvas; X, Y, RowW, RowH: Integer);
procedure DrawFilterRow(C: TCanvas; X, Y, RowW, RowH: Integer);
procedure DrawDSPRow(C: TCanvas; X, Y, RowW, RowH: Integer);
procedure DrawAGCPickerRow(C: TCanvas; X, Y, RowW, RowH: Integer);
procedure RegisterHit(HR: TRect; HV: Integer);
function CalcSLabel: string;
function FormatFreq(Hz: Double): string;
@@ -53,11 +58,13 @@ type
public
constructor Create(AOwner: TComponent); override;
procedure SetState(AMode, ABW: Integer; AVfoHz, ASMeterDB: Double);
procedure SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean);
procedure SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean;
AAGCMode: Integer);
procedure UpdateSMeter(DB: Double);
procedure UpdateVfo(AVfoHz: Double);
property OnSelect: TModeFilterEvent read FOnSelect write FOnSelect;
property OnDSPChange: TDSPChangeEvent read FOnDSPChange write FOnDSPChange;
property OnAGCChange: TAGCChangeEvent read FOnAGCChange write FOnAGCChange;
end;
implementation
@@ -67,6 +74,14 @@ const
HIT_NB = -10;
HIT_SNB = -11;
HIT_ANF = -12;
HIT_AGC = -13;
HIT_AGC_PICK_BASE = 100000; // 100000+N — выбор режима AGC (>всех BW фильтров)
AGC_NAMES: array[0..4] of string = ('FAST', 'MED', 'SLOW', 'LONG', 'OFF');
OVL_H_NORM = 136; // высота без пикера AGC
OVL_H_AGC = 159; // высота с открытым пикером (136 + GAP(3) + ROW_H(20))
CLR_PANEL_BG = TColor($00141414);
CLR_BORDER = TColor($00505050);
@@ -185,12 +200,14 @@ begin
FModeFilter[5] := 11000; // FM (NFM по умолчанию)
FModeFilter[6] := 8000; // AM
FModeFilter[7] := 8000; // SAM
FNRMode := 0;
FNBMode := 0;
FSNBOn := False;
FANFOn := False;
Width := 260;
Height := 136;
FNRMode := 0;
FNBMode := 0;
FSNBOn := False;
FANFOn := False;
FAGCMode := 0;
FAGCPickerOpen := False;
Width := 260;
Height := OVL_H_NORM;
Cursor := crHandPoint;
end;
@@ -356,8 +373,8 @@ end;
procedure TVfoOverlay.DrawDSPRow(C: TCanvas; X, Y, RowW, RowH: Integer);
const
DSP_COUNT = 4;
DSP_HITS: array[0..3] of Integer = (HIT_NR, HIT_NB, HIT_SNB, HIT_ANF);
DSP_COUNT = 5;
DSP_HITS: array[0..4] of Integer = (HIT_NR, HIT_NB, HIT_SNB, HIT_ANF, HIT_AGC);
var
I, BX, BRight: Integer;
HR: TRect;
@@ -386,6 +403,7 @@ begin
end;
2: begin Lbl := 'SNB'; IsAct := FSNBOn; end;
3: begin Lbl := 'ANF'; IsAct := FANFOn; end;
4: begin Lbl := AGC_NAMES[FAGCMode]; IsAct := FAGCPickerOpen; end;
else Lbl := ''; IsAct := False;
end;
IsHot := (FHotIdx >= 0) and (FHitActions[FHotIdx] = DSP_HITS[I]);
@@ -410,6 +428,43 @@ begin
end;
end;
procedure TVfoOverlay.DrawAGCPickerRow(C: TCanvas; X, Y, RowW, RowH: Integer);
const
AGC_COUNT = 5;
var
I, BX, BRight: Integer;
HR: TRect;
IsAct, IsHot: Boolean;
begin
BX := X;
for I := 0 to AGC_COUNT - 1 do
begin
BRight := X + ((I + 1) * RowW) div AGC_COUNT;
HR := Rect(BX, Y, BRight, Y + RowH);
IsAct := (I = FAGCMode);
IsHot := (FHotIdx >= 0) and
(FHitActions[FHotIdx] = HIT_AGC_PICK_BASE + I);
if IsAct then C.Brush.Color := CLR_BTN_ACT
else if IsHot then C.Brush.Color := CLR_BTN_HOT
else C.Brush.Color := CLR_BTN_NORM;
C.Brush.Style := bsSolid; C.Pen.Style := psClear;
C.FillRect(HR);
C.Pen.Style := psSolid; C.Pen.Color := CLR_BORDER;
C.Brush.Style := bsClear; C.Rectangle(HR);
if IsAct then C.Font.Color := CLR_ACCENT
else C.Font.Color := CLR_TEXT;
C.Font.Size := 7; C.Font.Name := 'Courier New';
C.Brush.Style := bsClear;
C.TextOut(BX + (BRight - BX - C.TextWidth(AGC_NAMES[I])) div 2,
Y + (RowH - C.TextHeight('A')) div 2, AGC_NAMES[I]);
RegisterHit(HR, HIT_AGC_PICK_BASE + I);
BX := BRight + 1;
end;
end;
procedure TVfoOverlay.DrawSelf(C: TCanvas; W, H: Integer);
const
PAD = 4;
@@ -463,8 +518,15 @@ begin
DrawFilterRow(C, PAD, CurY, W - PAD * 2, ROW_H);
Inc(CurY, ROW_H + GAP);
// Кнопки DSP (NR, NB, SNB, ANF)
// Кнопки DSP (NR, NB, SNB, ANF, AGC)
DrawDSPRow(C, PAD, CurY, W - PAD * 2, ROW_H);
// Пикер AGC (открывается кнопкой AGC)
if FAGCPickerOpen then
begin
Inc(CurY, ROW_H + GAP);
DrawAGCPickerRow(C, PAD, CurY, W - PAD * 2, ROW_H);
end;
end;
procedure TVfoOverlay.Paint;
@@ -501,17 +563,36 @@ begin
else
begin
// Кнопка DSP
case HV of
HIT_NR: FNRMode := (FNRMode + 1) mod 5;
HIT_NB: FNBMode := (FNBMode + 1) mod 3;
HIT_SNB: FSNBOn := not FSNBOn;
HIT_ANF: FANFOn := not FANFOn;
if HV = HIT_AGC then
begin
FAGCPickerOpen := not FAGCPickerOpen;
if FAGCPickerOpen then Height := OVL_H_AGC
else Height := OVL_H_NORM;
Invalidate;
end
else
begin
case HV of
HIT_NR: FNRMode := (FNRMode + 1) mod 5;
HIT_NB: FNBMode := (FNBMode + 1) mod 3;
HIT_SNB: FSNBOn := not FSNBOn;
HIT_ANF: FANFOn := not FANFOn;
end;
Invalidate;
if Assigned(FOnDSPChange) then
FOnDSPChange(FNRMode, FNBMode, FSNBOn, FANFOn);
end;
Invalidate;
if Assigned(FOnDSPChange) then
FOnDSPChange(FNRMode, FNBMode, FSNBOn, FANFOn);
end;
end
else if HV >= HIT_AGC_PICK_BASE then
begin
// Выбор режима AGC из пикера
FAGCMode := HV - HIT_AGC_PICK_BASE;
FAGCPickerOpen := False;
Height := OVL_H_NORM;
Invalidate;
if Assigned(FOnAGCChange) then FOnAGCChange(FAGCMode);
end
else if HV > 0 then
begin
NewBW := HV;
@@ -556,12 +637,14 @@ begin
Invalidate;
end;
procedure TVfoOverlay.SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean);
procedure TVfoOverlay.SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean;
AAGCMode: Integer);
begin
FNRMode := ANRMode;
FNBMode := ANBMode;
FSNBOn := ASNBOn;
FANFOn := AANFOn;
FNRMode := ANRMode;
FNBMode := ANBMode;
FSNBOn := ASNBOn;
FANFOn := AANFOn;
FAGCMode := AAGCMode;
Invalidate;
end;