diff --git a/MainForm.pas b/MainForm.pas
index 6d0347f..1b56496 100644
--- a/MainForm.pas
+++ b/MainForm.pas
@@ -22,7 +22,8 @@ unit MainForm;
interface
uses
- Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, VfoOverlay, FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
+ Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, VfoOverlay, SampleRateOverlay,
+ FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, Buttons, Menus, Math, Types,
LCLIntf, LCLType, GraphType,
HPSDRProtocol, HPSDRNetwork,
@@ -179,8 +180,9 @@ type
FDeviceCount: Integer;
FDeviceDialog: TDeviceDialog;
FPendingIP: string; // IP устройства для подключения
- FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре
- FPanelHidden: Boolean; // True = левая панель скрыта
+ FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре
+ FSampleRateOverlay: TSampleRateOverlay; // оверлей span/hide слева вверху спектра
+ FPanelHidden: Boolean; // True = левая панель скрыта
FPendingBoardType: Integer; // BoardType устройства для подключения
// ---- DSP + Audio ----
@@ -341,12 +343,11 @@ type
FSpectrumTimer: TTimer;
FAfterShowTimer:TTimer; // однократный таймер для пост-инициализации
- // ---- Toolbar ----
+ // ---- Top panel (toolbar + VFO group + S-meter) ----
PanelToolbar: TPanel;
BtnDiscover: TFlatButton;
- BtnStartStop: TFlatButton; // START / STOP (connect+run в одном)
- BtnSettings: TFlatButton; // открыть диалог настроек
- PanelSep: TPanel; // 1px разделитель под тулбаром
+ BtnStartStop: TFlatButton;
+ BtnSettings: TFlatButton;
// ---- Left panel ----
PanelLeft: TPanel;
@@ -418,16 +419,7 @@ type
BtnTUN: TFlatButton;
// ---- Right panel ----
- PanelRight: TPanel;
- PanelSpanButtons: TPanel;
- LblSpan: TLabel;
- BtnSpan48k: TFlatButton;
- BtnSpan96k: TFlatButton;
- BtnSpan192k: TFlatButton;
- BtnSpan384k: TFlatButton;
- BtnSpan768k: TFlatButton;
- BtnSpan1536k: TFlatButton;
- BtnHidePanel: TFlatButton; // скрыть/показать левую панель
+ PanelRight: TPanel;
PbSpectrum: TPaintBox;
PbRuler: TPaintBox; // полоса частотных меток между спектром и водопадом
PanelSplitter: TPanel; // перетаскиваемый разделитель спектр/водопад
@@ -445,7 +437,9 @@ type
procedure SetStatusText(Index: Integer; const Text: string);
procedure SetRadioOfflineStatus(const ConnText: string; ClearDevice: Boolean = False);
procedure ApplyStatusTheme(const T: TAppTheme);
- procedure PositionSpanOverlayButtons;
+ procedure PositionSampleRateOverlay;
+ procedure OnSampleRateSelect(SampleRate: Integer);
+ procedure OnSampleRateHidePanel;
procedure UpdateVfoDisplay;
function FormatFreq(Hz: Double): string;
procedure LayoutTopVfoBlock;
@@ -551,7 +545,6 @@ type
procedure BtnNBClick(Sender: TObject);
procedure BtnSNBClick(Sender: TObject);
procedure BtnANFClick(Sender: TObject);
- procedure BtnSpanClick(Sender: TObject);
// Веб-интерфейс: callbacks от TWebServer
procedure WebOnFreq(Hz: Double);
procedure WebOnMode(Mode: Integer);
@@ -660,7 +653,6 @@ type
procedure ActivateXvtrBand(Idx: Integer);
procedure DeactivateXvtr;
// -------------------------------------------------------------------------
- procedure BtnHidePanelClick(Sender: TObject);
procedure BtnSettingsClick(Sender: TObject);
procedure PositionVfoOverlay;
procedure OnModeFilterSelect(Mode: Integer; FilterBW: Integer);
@@ -1127,14 +1119,9 @@ begin
// --- Sample Rate / Span ---
// SampleRate — глобальный, не per-band. Не восстанавливаем из диапазона.
- // Обновляем только FSpanHz для отображения (берём текущий FSampleRate)
FSpanHz := FSampleRate;
- StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
- StyleSpanButton(BtnSpan96k, FSampleRate = 96000);
- StyleSpanButton(BtnSpan192k, FSampleRate = 192000);
- StyleSpanButton(BtnSpan384k, FSampleRate = 384000);
- StyleSpanButton(BtnSpan768k, FSampleRate = 768000);
- StyleSpanButton(BtnSpan1536k, FSampleRate = 1536000);
+ if Assigned(FSampleRateOverlay) then
+ FSampleRateOverlay.SetCurrentRate(FSampleRate);
// --- VFO A: центрируем на новой частоте, сбрасываем shift ---
FCenterFreq := B.VfoA; // DDC = центр диапазона
@@ -1375,12 +1362,6 @@ begin
FCurrentXvtr := -1;
BuildUI;
- StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
- StyleSpanButton(BtnSpan96k, FSampleRate = 96000);
- StyleSpanButton(BtnSpan192k, FSampleRate = 192000);
- StyleSpanButton(BtnSpan384k, FSampleRate = 384000);
- StyleSpanButton(BtnSpan768k, FSampleRate = 768000);
- StyleSpanButton(BtnSpan1536k, FSampleRate = 1536000);
ResizeSMeter;
ApplyDarkTheme;
UpdateVfoDisplay;
@@ -1501,6 +1482,7 @@ const
var
i, X, Y, W: Integer;
B: TFlatButton;
+ SepLine: TPanel;
function MakeBtn(Parent: TWinControl; const Cap: string;
ALeft, ATop, AW, AH: Integer; Handler: TNotifyEvent): TFlatButton;
@@ -1535,7 +1517,7 @@ begin
PanelToolbar := TPanel.Create(Self);
PanelToolbar.Parent := Self;
PanelToolbar.Align := alTop;
- PanelToolbar.Height := 36;
+ PanelToolbar.Height := 72;
PanelToolbar.BevelOuter := bvNone;
PanelToolbar.Color := CLR_PANEL;
@@ -1557,7 +1539,13 @@ begin
BtnStartStop.ClrBorder := TColor($00304830);
BtnStartStop.ClrText := TColor($0090C090);
- // ---- Separator line ----
+ // Линия-разделитель внутри PanelToolbar, прижата к его нижнему краю
+ SepLine := TPanel.Create(Self);
+ SepLine.Parent := PanelToolbar;
+ SepLine.BevelOuter := bvNone;
+ SepLine.Color := TColor($00606060);
+ SepLine.SetBounds(16, PanelToolbar.Height - 10, PanelToolbar.Width - 32, 2);
+ SepLine.Anchors := [akLeft, akRight, akBottom];
// ---- Status bar ----
StatusPanel := TMainStatusBar.Create(Self);
@@ -1570,11 +1558,11 @@ begin
PanelLeft.Width := LEFT_W;
PanelLeft.BevelOuter := bvNone;
- Y := 38; // gap below separator: separator at y=66, content starts at y=74
+ Y := 0;
// Compact top VFO group
PanelTopVfoGroup := TPanel.Create(Self);
- PanelTopVfoGroup.Parent := Self;
+ PanelTopVfoGroup.Parent := PanelToolbar;
PanelTopVfoGroup.SetBounds(0, 0, TOP_VFO_W * 2 + 64, 60);
PanelTopVfoGroup.BevelOuter := bvNone;
PanelTopVfoGroup.Color := TColor($00181818);
@@ -1940,59 +1928,15 @@ begin
PanelRight.BevelOuter := bvNone;
PanelRight.OnResize := RightPanelResize;
- PanelSpanButtons := TPanel.Create(Self);
- PanelSpanButtons.Parent := PanelRight;
- PanelSpanButtons.Align := alTop;
- PanelSpanButtons.Height := 38;
- PanelSpanButtons.BevelOuter := bvNone;
+ FSampleRateOverlay := TSampleRateOverlay.Create(Self);
+ FSampleRateOverlay.Parent := PanelRight;
+ FSampleRateOverlay.OnSpanSelect := OnSampleRateSelect;
+ FSampleRateOverlay.OnHidePanel := OnSampleRateHidePanel;
+ FSampleRateOverlay.SetCurrentRate(FSampleRate);
- LblSpan := TLabel.Create(Self);
- LblSpan.Parent := PanelSpanButtons; LblSpan.Left := 4; LblSpan.Top := 8;
- LblSpan.Caption := 'SPAN:'; LblSpan.Font.Color := CLR_TEXTDIM;
- LblSpan.Font.Name := 'Courier New'; LblSpan.Font.Size := 7;
-
- BtnSpan48k := MakeBtn(PanelSpanButtons, '48k', 50, 3, 46, BTN_SM, BtnSpanClick);
- BtnSpan96k := MakeBtn(PanelSpanButtons, '96k', 99, 3, 46, BTN_SM, BtnSpanClick);
- BtnSpan192k := MakeBtn(PanelSpanButtons, '192k', 148, 3, 52, BTN_SM, BtnSpanClick);
- BtnSpan384k := MakeBtn(PanelSpanButtons, '384k', 203, 3, 52, BTN_SM, BtnSpanClick);
- BtnSpan768k := MakeBtn(PanelSpanButtons, '768k', 258, 3, 52, BTN_SM, BtnSpanClick);
- BtnSpan1536k := MakeBtn(PanelSpanButtons, '1536k', 313, 3, 58, BTN_SM, BtnSpanClick);
- BtnSpan48k.Tag := 48000;
- BtnSpan96k.Tag := 96000;
- BtnSpan192k.Tag := 192000;
- BtnSpan384k.Tag := 384000;
- BtnSpan768k.Tag := 768000;
- BtnSpan1536k.Tag := 1536000;
- StyleSpanButton(BtnSpan48k, False);
- StyleSpanButton(BtnSpan96k, False);
- StyleSpanButton(BtnSpan192k, True);
- StyleSpanButton(BtnSpan384k, False);
- StyleSpanButton(BtnSpan768k, False);
- StyleSpanButton(BtnSpan1536k, False);
-
- BtnHidePanel := MakeBtn(PanelRight, '◀', 0, 0, 28, 28, BtnHidePanelClick);
- BtnHidePanel.Font.Size := 10;
- BtnHidePanel.ClrNorm := TColor($00543F18);
- BtnHidePanel.ClrHot := TColor($00624B1D);
- BtnHidePanel.ClrActive := TColor($00624B1D);
- BtnHidePanel.ClrBorder := TColor($00685624);
- BtnHidePanel.ClrText := TColor($00D8E6EE);
- BtnHidePanel.ClrTextAct := TColor($00F0FAFF);
- BtnHidePanel.BringToFront;
-
- // Разделитель: на уровне формы, под всеми верхними элементами
- PanelSep := TPanel.Create(Self);
- PanelSep.Parent := Self;
- PanelSep.Align := alNone;
- PanelSep.BevelOuter := bvNone;
- PanelSep.ParentColor := False;
- PanelSep.Color := TColor($00606060);
- PanelSep.SetBounds(16, PanelToolbar.Height + 30, ClientWidth - 32, 2);
-
- // Большой S-метр — на главной форме поверх двух панелей
- // S-метр: Parent=Self, перекрывает Toolbar+SpanButtons вертикально
+ // S-метр — внутри PanelToolbar, справа
PanelSMeterRight := TPanel.Create(Self);
- PanelSMeterRight.Parent := Self;
+ PanelSMeterRight.Parent := PanelToolbar;
PanelSMeterRight.BevelOuter := bvNone;
PanelSMeterRight.Color := CLR_PANEL;
PbSMeterRight := TPaintBox.Create(Self);
@@ -2033,16 +1977,6 @@ begin
PbWaterfall.OnMouseMove := PbWaterfallMouseMove;
PbWaterfall.OnMouseUp := PbWaterfallMouseUp;
- // SPAN кнопки как оверлей на спектре (в стиле web)
- BtnSpan48k.Parent := PanelRight;
- BtnSpan96k.Parent := PanelRight;
- BtnSpan192k.Parent := PanelRight;
- BtnSpan384k.Parent := PanelRight;
- BtnSpan768k.Parent := PanelRight;
- BtnSpan1536k.Parent := PanelRight;
- LblSpan.Visible := False;
- PositionSpanOverlayButtons;
-
// Initial layout
ResizeSpectrumPanels;
end;
@@ -2089,7 +2023,7 @@ begin
P := PanelRight.ScreenToClient(PanelSplitter.ClientToScreen(Point(X, Y)));
DeltaY := P.Y - FSplitterDragY0;
- TopOff := PanelSpanButtons.Height;
+ TopOff := 0;
AvailH := PanelRight.ClientHeight - TopOff - RULER_H - SPLITTER_H;
if AvailH <= 0 then Exit;
@@ -2127,7 +2061,7 @@ begin
P := PanelRight.ScreenToClient(PanelSplitter.ClientToScreen(Point(X, Y)));
DeltaY := P.Y - FSplitterDragY0;
- TopOff := PanelSpanButtons.Height;
+ TopOff := 0;
AvailH := PanelRight.ClientHeight - TopOff - RULER_H - SPLITTER_H;
if AvailH <= 0 then Exit;
@@ -2173,7 +2107,7 @@ var
TopVfoAW, TopVfoBW, NeedFreqAW, NeedFreqBW, FreqTextW, GroupW: Integer;
XferX, XferY: Integer;
begin
- if (PanelToolbar = nil) or (PanelSpanButtons = nil) or (PanelTopVfoGroup = nil) or
+ if (PanelToolbar = nil) or (PanelTopVfoGroup = nil) or
(PanelTopVfoA = nil) or (PanelTopVfoB = nil) then Exit;
Canvas.Font.Name := FreqDispB.FontName;
@@ -2217,16 +2151,14 @@ begin
DynGap := XFER_SIDE_GAP + ExtraPerSide;
GroupW := TopVfoAW + DynGap + XFER_PANEL_W + DynGap + TopVfoBW;
- BlockH := PanelToolbar.Height + 30 - (MARGIN * 2); // 30 = span row content, not full PanelSpanButtons
+ BlockH := PanelToolbar.Height - (MARGIN * 2) - 8;
if BlockH < 56 then BlockH := 56;
// Центрируем группу в доступном пространстве
GroupLeft := SafeLeft + (AvailW - GroupW) div 2;
GroupTop := TOP_OFFSET;
- // Ось выравнивания: центр S-метра на форме (SMTop=3, SMBot=63) → y=33
- // Относительно PanelTopVfoGroup (GroupTop=2): CenterY = 33 - 2 = 31
- CenterY := (PanelToolbar.Height + 30) div 2 - GroupTop;
+ CenterY := PanelToolbar.Height div 2 - GroupTop;
PanelTopVfoGroup.SetBounds(GroupLeft, GroupTop, GroupW, BlockH);
PanelTopVfoGroup.Visible := True;
@@ -2264,15 +2196,11 @@ begin
FreqDispB.SetBounds(FreqDispB.Left, FreqTop,
FreqTextW + VFO_FREQ_LEFT_PAD + VFO_FREQ_RIGHT_PAD, TOP_VFO_FREQ_H);
- if PanelSep <> nil then
- PanelSep.SetBounds(16, PanelToolbar.Height + 30, ClientWidth - 32, 2);
-
- PanelTopVfoGroup.BringToFront;
end;
procedure TMainForm.ResizeSMeter;
// PanelSMeterRight на главной форме (Parent=Self).
-// По Y: от самого верха формы до низа PanelSpanButtons (Toolbar+Span = 66px).
+// По Y: от самого верха формы до низа тулбара.
var
FW: Integer;
RightW: Integer; // ширина PanelRight
@@ -2281,8 +2209,7 @@ var
SMTop: Integer;
SMBot: Integer;
begin
- if (PanelSMeterRight = nil) or (PanelToolbar = nil) or
- (PanelSpanButtons = nil) or (PanelLeft = nil) then Exit;
+ if (PanelSMeterRight = nil) or (PanelToolbar = nil) or (PanelLeft = nil) then Exit;
LayoutTopVfoBlock;
@@ -2294,15 +2221,13 @@ begin
// Ширина S-метра = 32% от PanelRight
SMW := Round(RightW * SMETER_WIDTH_RATIO);
- // X: прижат к правому краю формы
+ // X: прижат к правому краю PanelToolbar
SMLeft := FW - SMW - SMETER_MARGIN;
- // Y: от верха формы+margin до низа PanelSpanButtons-margin
SMTop := SMETER_MARGIN;
- SMBot := PanelToolbar.Height + 30 - SMETER_MARGIN; // 30 = span row content, ends before separator
+ SMBot := PanelToolbar.Height - SMETER_MARGIN - 9;
PanelSMeterRight.SetBounds(SMLeft, SMTop, SMW, SMBot - SMTop);
- PanelSMeterRight.BringToFront;
end;
procedure TMainForm.ResizeSpectrumPanels;
@@ -2320,7 +2245,7 @@ begin
if Assigned(FSpecView) then SyncSpecViewFreq;
RW := PanelRight.ClientWidth;
RH := PanelRight.ClientHeight;
- TopOff := PanelSpanButtons.Height;
+ TopOff := 0;
// S-метр позиционируется отдельно в ResizeSMeter
ResizeSMeter;
@@ -2334,7 +2259,7 @@ begin
FSpectrumWidth := RW;
FSpectrumHeight := 0;
FWaterfallHeight := 0;
- PositionSpanOverlayButtons;
+ PositionSampleRateOverlay;
Exit;
end;
@@ -2351,7 +2276,7 @@ begin
PbRuler.Visible := True;
PanelSplitter.Visible := False;
PbWaterfall.Visible := False;
- PositionSpanOverlayButtons;
+ PositionSampleRateOverlay;
FSpectrumWidth := RW;
FSpectrumHeight := SH;
FWaterfallHeight := 0;
@@ -2380,7 +2305,7 @@ begin
PbWaterfall.Visible := True;
PbRuler.SetBounds(0, TopOff + WH, RW, RULER_H);
PbRuler.Visible := True;
- PositionSpanOverlayButtons;
+ PositionSampleRateOverlay;
FSpectrumWidth := RW;
FSpectrumHeight := 0;
FWaterfallHeight := WH;
@@ -2424,7 +2349,7 @@ begin
// Водопад — под сплиттером
PbWaterfall.SetBounds(0, TopOff + SH + RULER_H + SPLITTER_H, RW, WH);
- PositionSpanOverlayButtons;
+ PositionSampleRateOverlay;
// Обновляем переменные размеров
FSpectrumWidth := RW;
@@ -2471,7 +2396,7 @@ begin
if PanelFMCTCSS <> nil then DP(PanelFMCTCSS);
if PanelFMStep <> nil then DP(PanelFMStep);
DP(PanelRX); DP(PanelTX);
- DP(PanelRight); DP(PanelSpanButtons);
+ DP(PanelRight);
PbSpectrum.Color := T.BG;
PbWaterfall.Color := T.BG;
@@ -2481,7 +2406,7 @@ begin
// Метки
DL(LblAGCTop, False);
- DL(LblDrv); DL(LblSpan);
+ DL(LblDrv);
if LblFMSQ <> nil then DL(LblFMSQ, False);
// Кнопки тулбара
@@ -2518,17 +2443,10 @@ begin
StyleButton(BtnMOX, BtnMOX.Active);
StyleButton(BtnTUN, BtnTUN.Active);
- // Span / VFO-copy / HidePanel
- StyleSpanButton(BtnSpan48k, BtnSpan48k.Active);
- StyleSpanButton(BtnSpan96k, BtnSpan96k.Active);
- StyleSpanButton(BtnSpan192k, BtnSpan192k.Active);
- StyleSpanButton(BtnSpan384k, BtnSpan384k.Active);
- StyleSpanButton(BtnSpan768k, BtnSpan768k.Active);
- StyleSpanButton(BtnSpan1536k, BtnSpan1536k.Active);
+ // VFO-copy
StyleSpanButton(BtnVfoACopyB, False);
StyleSpanButton(BtnVfoSwap, False);
StyleSpanButton(BtnVfoBCopyA, False);
- if BtnHidePanel <> nil then StyleSpanButton(BtnHidePanel, False);
// Ползунки
DS(TrkAGC); DS(TrkVolume); DS(TrkDrive);
@@ -2618,59 +2536,17 @@ begin
B.Font.Size := 7;
end;
-procedure TMainForm.PositionSpanOverlayButtons;
+procedure TMainForm.PositionSampleRateOverlay;
const
- BTN_TOP = 6;
- GAP = 3;
- W48 = 40;
- W96 = 40;
- W192 = 44;
- W384 = 44;
- W768 = 44;
- W1536 = 50;
- HBTN = 18;
-var
- X, NeedW, BaseX, BaseY: Integer;
+ MARGIN = 4;
begin
- if (PbSpectrum = nil) or (BtnSpan48k = nil) then Exit;
- NeedW := W48 + W96 + W192 + W384 + W768 + W1536 + GAP * 5 + 8;
- if PbSpectrum.Width < NeedW then
- begin
- BtnSpan48k.Visible := False;
- BtnSpan96k.Visible := False;
- BtnSpan192k.Visible := False;
- BtnSpan384k.Visible := False;
- BtnSpan768k.Visible := False;
- BtnSpan1536k.Visible := False;
- Exit;
- end;
-
- BaseX := PbSpectrum.Left;
- BaseY := PbSpectrum.Top;
- X := BaseX + 56;
- BtnSpan48k.SetBounds(X, BaseY + BTN_TOP, W48, HBTN); Inc(X, W48 + GAP);
- BtnSpan96k.SetBounds(X, BaseY + BTN_TOP, W96, HBTN); Inc(X, W96 + GAP);
- BtnSpan192k.SetBounds(X, BaseY + BTN_TOP, W192, HBTN); Inc(X, W192 + GAP);
- BtnSpan384k.SetBounds(X, BaseY + BTN_TOP, W384, HBTN); Inc(X, W384 + GAP);
- BtnSpan768k.SetBounds(X, BaseY + BTN_TOP, W768, HBTN); Inc(X, W768 + GAP);
- BtnSpan1536k.SetBounds(X, BaseY + BTN_TOP, W1536, HBTN);
- if BtnHidePanel <> nil then
- BtnHidePanel.SetBounds(BaseX, BaseY + BTN_TOP, 28, HBTN);
-
- BtnSpan48k.Visible := True;
- BtnSpan96k.Visible := True;
- BtnSpan192k.Visible := True;
- BtnSpan384k.Visible := True;
- BtnSpan768k.Visible := True;
- BtnSpan1536k.Visible := True;
-
- BtnSpan48k.BringToFront;
- BtnSpan96k.BringToFront;
- BtnSpan192k.BringToFront;
- BtnSpan384k.BringToFront;
- BtnSpan768k.BringToFront;
- BtnSpan1536k.BringToFront;
- if BtnHidePanel <> nil then BtnHidePanel.BringToFront;
+ if (FSampleRateOverlay = nil) or (PbSpectrum = nil) then Exit;
+ FSampleRateOverlay.SetBounds(
+ PbSpectrum.Left + MARGIN,
+ PbSpectrum.Top + MARGIN,
+ FSampleRateOverlay.Width,
+ FSampleRateOverlay.Height);
+ FSampleRateOverlay.BringToFront;
end;
// ===========================================================================
@@ -3954,6 +3830,8 @@ begin
begin
FSampleRate := G_Settings.SampleRate;
FSpanHz := FSampleRate;
+ if Assigned(FSampleRateOverlay) then
+ FSampleRateOverlay.SetCurrentRate(FSampleRate);
end;
BtnNR.Tag := EnsureRange(G_Settings.NRMode, 0, 4); UpdateNRButton;
BtnNB.Tag := EnsureRange(G_Settings.NBMode, 0, 2); UpdateNBButton;
@@ -5390,13 +5268,12 @@ begin
FSettings.SaveWebSettings(W);
end;
-procedure TMainForm.BtnHidePanelClick(Sender: TObject);
+procedure TMainForm.OnSampleRateHidePanel;
begin
FPanelHidden := not FPanelHidden;
if FPanelHidden then
begin
PanelLeft.Width := 0;
- BtnHidePanel.Caption := '▶';
FVfoOverlay.Parent := PanelRight;
FVfoOverlay.Width := 260;
FVfoOverlay.Height := 136; // OVL_H_NORM — расширяется сам при открытии AGC-пикера
@@ -5409,9 +5286,10 @@ begin
else
begin
PanelLeft.Width := 232;
- BtnHidePanel.Caption := '◀';
FVfoOverlay.Visible := False;
end;
+ if Assigned(FSampleRateOverlay) then
+ FSampleRateOverlay.SetPanelHidden(FPanelHidden);
ResizeSpectrumPanels;
ResizeSMeter;
end;
@@ -5492,25 +5370,20 @@ begin
FBandCache[FCurrentBand].AGCMode := FAGCMode;
end;
-procedure TMainForm.BtnSpanClick(Sender: TObject);
+procedure TMainForm.OnSampleRateSelect(SampleRate: Integer);
var
NewRate: Integer;
DDCRate: Word;
begin
- NewRate := (Sender as TFlatButton).Tag;
+ NewRate := SampleRate;
if NewRate = FSampleRate then Exit;
FSampleRate := NewRate;
FSpanHz := NewRate;
DDCRate := NewRate div 1000;
- // Обновляем кнопки сразу — UI отзывчив
- StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
- StyleSpanButton(BtnSpan96k, FSampleRate = 96000);
- StyleSpanButton(BtnSpan192k, FSampleRate = 192000);
- StyleSpanButton(BtnSpan384k, FSampleRate = 384000);
- StyleSpanButton(BtnSpan768k, FSampleRate = 768000);
- StyleSpanButton(BtnSpan1536k, FSampleRate = 1536000);
+ if Assigned(FSampleRateOverlay) then
+ FSampleRateOverlay.SetCurrentRate(NewRate);
Application.ProcessMessages;
FBandCache[FCurrentBand].SpanHz := FSpanHz; // для обратной совместимости
@@ -5762,27 +5635,17 @@ begin
end;
procedure TMainForm.SyncWebSpan;
+const
+ ValidSpans: array[0..5] of Integer = (48000, 96000, 192000, 384000, 768000, 1536000);
var
- ValidSpans: array[0..5] of Integer;
- Found: Boolean;
i: Integer;
- Btn: TFlatButton;
begin
- ValidSpans[0]:=48000; ValidSpans[1]:=96000; ValidSpans[2]:=192000;
- ValidSpans[3]:=384000; ValidSpans[4]:=768000; ValidSpans[5]:=1536000;
- Found := False;
- for i := 0 to 5 do
- if ValidSpans[i] = FWebSyncInt then begin Found := True; Break; end;
- if not Found then Exit;
- if FWebSyncInt = FSampleRate then Exit;
- Btn := nil;
- if FWebSyncInt = 48000 then Btn := BtnSpan48k
- else if FWebSyncInt = 96000 then Btn := BtnSpan96k
- else if FWebSyncInt = 192000 then Btn := BtnSpan192k
- else if FWebSyncInt = 384000 then Btn := BtnSpan384k
- else if FWebSyncInt = 768000 then Btn := BtnSpan768k
- else if FWebSyncInt = 1536000 then Btn := BtnSpan1536k;
- if Assigned(Btn) then BtnSpanClick(Btn);
+ for i := 0 to High(ValidSpans) do
+ if ValidSpans[i] = FWebSyncInt then
+ begin
+ OnSampleRateSelect(FWebSyncInt);
+ Exit;
+ end;
end;
procedure TMainForm.WebOnVolume(V: Integer);
diff --git a/SampleRateOverlay.pas b/SampleRateOverlay.pas
new file mode 100644
index 0000000..9f5801c
--- /dev/null
+++ b/SampleRateOverlay.pas
@@ -0,0 +1,241 @@
+unit SampleRateOverlay;
+
+{
+ Overlay виджет для выбора sample rate (span) спектра.
+ Размещается в верхнем левом углу спектрограммы поверх PbSpectrum.
+ Содержит кнопку скрытия левой панели и ряд кнопок span.
+}
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+interface
+
+uses
+ Classes, SysUtils, Controls, Graphics, Types, Math;
+
+type
+ TSpanSelectEvent = procedure(SampleRate: Integer) of object;
+ THidePanelEvent = procedure of object;
+
+ TSampleRateOverlay = class(TCustomControl)
+ private
+ FCurrentRate: Integer;
+ FPanelHidden: Boolean;
+ FOnSpanSelect: TSpanSelectEvent;
+ FOnHidePanel: THidePanelEvent;
+
+ FHitRects: array[0..6] of TRect; // [0]=hide button, [1..6]=span buttons
+ FHitValues: array[0..6] of Integer; // 0=hide, else sample rate
+ FHitCount: Integer;
+ FHotIdx: Integer;
+
+ procedure DrawSelf(C: TCanvas; W, H: Integer);
+ procedure RegisterHit(const HR: TRect; Value: Integer);
+ protected
+ 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
+ constructor Create(AOwner: TComponent); override;
+ procedure SetCurrentRate(Rate: Integer);
+ procedure SetPanelHidden(Hidden: Boolean);
+ property OnSpanSelect: TSpanSelectEvent read FOnSpanSelect write FOnSpanSelect;
+ property OnHidePanel: THidePanelEvent read FOnHidePanel write FOnHidePanel;
+ end;
+
+implementation
+
+const
+ SPAN_RATES: array[0..5] of Integer = (48000, 96000, 192000, 384000, 768000, 1536000);
+ SPAN_NAMES: array[0..5] of string = ('48k', '96k', '192k', '384k', '768k', '1536k');
+
+ HIDE_W = 22;
+ SPAN_H = 18;
+ PAD_X = 4;
+ PAD_Y = 4;
+ BTN_GAP = 3;
+
+ CLR_BG = TColor($00141414);
+ CLR_BORDER = TColor($00505050);
+ CLR_TEXT = TColor($00D8E6EE);
+ CLR_BTN_NORM = TColor($00543F18);
+ CLR_BTN_HOT = TColor($00624B1D);
+ CLR_BTN_ACT = TColor($00785A20);
+ CLR_BTN_BDR = TColor($00685624);
+
+function SpanBtnWidth(const Name: string; C: TCanvas): Integer;
+begin
+ C.Font.Name := 'Courier New';
+ C.Font.Size := 7;
+ C.Font.Style := [];
+ Result := C.TextWidth(Name) + 10;
+ if Result < 36 then Result := 36;
+end;
+
+constructor TSampleRateOverlay.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ ControlStyle := ControlStyle + [csOpaque];
+ FCurrentRate := 192000;
+ FPanelHidden := False;
+ FHitCount := 0;
+ FHotIdx := -1;
+ Cursor := crHandPoint;
+ Width := 320;
+ Height := SPAN_H + PAD_Y * 2;
+end;
+
+procedure TSampleRateOverlay.RegisterHit(const HR: TRect; Value: Integer);
+begin
+ if FHitCount > High(FHitRects) then Exit;
+ FHitRects[FHitCount] := HR;
+ FHitValues[FHitCount] := Value;
+ Inc(FHitCount);
+end;
+
+procedure TSampleRateOverlay.DrawSelf(C: TCanvas; W, H: Integer);
+var
+ X, I, TW, TH, BtnW: Integer;
+ HR: TRect;
+ IsAct, IsHot: Boolean;
+ Cap: string;
+begin
+ FHitCount := 0;
+
+ // Фон
+ C.Brush.Color := CLR_BG;
+ 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 := [];
+ TH := C.TextHeight('A');
+ X := PAD_X;
+
+ // Кнопка скрытия панели (◀ или ▶)
+ HR := Rect(X, PAD_Y, X + HIDE_W, PAD_Y + SPAN_H);
+ IsHot := (FHotIdx = 0);
+ 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_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.Size := 7;
+ C.Font.Color := CLR_TEXT;
+ C.Brush.Style := bsClear;
+ C.Font.Size := 8;
+ C.TextOut(HR.Left + (HIDE_W - TW) div 2,
+ HR.Top + (SPAN_H - TH) div 2, Cap);
+ C.Font.Size := 7;
+ RegisterHit(HR, 0);
+ Inc(X, HIDE_W + BTN_GAP);
+
+ // Кнопки span
+ for I := 0 to High(SPAN_RATES) do
+ begin
+ BtnW := SpanBtnWidth(SPAN_NAMES[I], C);
+ HR := Rect(X, PAD_Y, X + BtnW, PAD_Y + SPAN_H);
+ IsAct := (SPAN_RATES[I] = FCurrentRate);
+ IsHot := (FHotIdx = I + 1);
+
+ 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_BTN_BDR;
+ C.Brush.Style := bsClear; C.Rectangle(HR);
+
+ TW := C.TextWidth(SPAN_NAMES[I]);
+ C.Font.Color := CLR_TEXT;
+ C.Brush.Style := bsClear;
+ C.TextOut(HR.Left + (BtnW - TW) div 2,
+ HR.Top + (SPAN_H - TH) div 2, SPAN_NAMES[I]);
+
+ RegisterHit(HR, SPAN_RATES[I]);
+ Inc(X, BtnW + BTN_GAP);
+ end;
+end;
+
+procedure TSampleRateOverlay.Paint;
+begin
+ DrawSelf(Canvas, Width, Height);
+end;
+
+procedure TSampleRateOverlay.MouseMove(Shift: TShiftState; X, Y: Integer);
+var
+ I, OldHot: Integer;
+begin
+ OldHot := FHotIdx;
+ FHotIdx := -1;
+ for I := 0 to FHitCount - 1 do
+ if PtInRect(FHitRects[I], Point(X, Y)) then
+ begin
+ FHotIdx := I;
+ Break;
+ end;
+ if FHotIdx <> OldHot then Invalidate;
+ inherited MouseMove(Shift, X, Y);
+end;
+
+procedure TSampleRateOverlay.MouseDown(Button: TMouseButton;
+ Shift: TShiftState; X, Y: Integer);
+var
+ I: Integer;
+begin
+ if Button <> mbLeft then Exit;
+ for I := 0 to FHitCount - 1 do
+ if PtInRect(FHitRects[I], Point(X, Y)) then
+ begin
+ if FHitValues[I] = 0 then
+ begin
+ if Assigned(FOnHidePanel) then FOnHidePanel;
+ end
+ else
+ begin
+ if Assigned(FOnSpanSelect) then FOnSpanSelect(FHitValues[I]);
+ end;
+ Break;
+ end;
+end;
+
+procedure TSampleRateOverlay.MouseLeave;
+begin
+ if FHotIdx >= 0 then
+ begin
+ FHotIdx := -1;
+ Invalidate;
+ end;
+ inherited MouseLeave;
+end;
+
+procedure TSampleRateOverlay.SetCurrentRate(Rate: Integer);
+begin
+ if FCurrentRate = Rate then Exit;
+ FCurrentRate := Rate;
+ Invalidate;
+end;
+
+procedure TSampleRateOverlay.SetPanelHidden(Hidden: Boolean);
+begin
+ if FPanelHidden = Hidden then Exit;
+ FPanelHidden := Hidden;
+ Invalidate;
+end;
+
+end.
diff --git a/ewsdr.lpi b/ewsdr.lpi
index 0e76185..dc940c5 100644
--- a/ewsdr.lpi
+++ b/ewsdr.lpi
@@ -95,6 +95,10 @@
+
+
+
+