From ea6e57b8b8373e9932b72ebf41c2b7ebe193722f Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Fri, 22 May 2026 15:31:42 +0300 Subject: [PATCH] Replace PanelSpanButtons with SampleRateOverlay overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add SampleRateOverlay.pas: TCustomControl overlay (top-left of spectrum) with hide-panel button and 6 span buttons (48k–1536k), same hit-test pattern as VfoOverlay - Consolidate toolbar: move PanelTopVfoGroup and PanelSMeterRight into PanelToolbar (height 72) instead of floating on the form - Remove PanelSpanButtons (alTop spacer) that caused the ~1cm black strip at the top of the spectrum - Add separator line inside PanelToolbar anchored to its bottom edge, with 16px left/right margins - Fix left panel blocks starting Y offset (was 38, now 0) Co-Authored-By: Claude Sonnet 4.6 --- MainForm.pas | 287 +++++++++++------------------------------- SampleRateOverlay.pas | 241 +++++++++++++++++++++++++++++++++++ ewsdr.lpi | 4 + 3 files changed, 320 insertions(+), 212 deletions(-) create mode 100644 SampleRateOverlay.pas 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 @@ + + + +