From 99463fa22f49873d0aec0231ace5a38b66fb83e2 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Wed, 11 Mar 2026 21:18:18 +0300 Subject: [PATCH] add splitter --- MainForm.pas | 153 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 8 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index d3d6d8c..c0c0d8b 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -233,6 +233,12 @@ type FSpectrumWidth: Integer; FSpectrumHeight: Integer; FWaterfallHeight:Integer; + // ---- Splitter между спектром и водопадом ---- + FSplitter: TPanel; // визуальная полоса-разделитель + FSplitterDrag: Boolean; // True = идёт перетаскивание + FSplitterDragY0: Integer; // Y мыши в начале перетаскивания (в координатах PanelRight) + FSplitterSH0: Integer; // FSpectrumHeight в начале перетаскивания + FSplitterRatio: Double; // соотношение высоты спектра (0.15..0.85), сохраняется // Водопад: прямой массив пикселей BGRA + RawImage bitmap без IntfImage roundtrip FWfPixels: array of LongWord; // W×H пикселей, строка 0 = верх (новейшая) FWfBitmap: TBitmap; @@ -347,6 +353,7 @@ type BtnHidePanel: TFlatButton; // скрыть/показать левую панель PbSpectrum: TPaintBox; PbRuler: TPaintBox; // полоса частотных меток между спектром и водопадом + PanelSplitter: TPanel; // перетаскиваемый разделитель спектр/водопад PbWaterfall: TPaintBox; // ---- Status bar ---- @@ -502,6 +509,13 @@ type procedure SpectrumTimerTick(Sender: TObject); procedure RightPanelResize(Sender: TObject); procedure AfterShowTick(Sender: TObject); + // Splitter handlers + procedure SplitterMouseDown(Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer); + procedure SplitterMouseMove(Sender: TObject; Shift: TShiftState; + X, Y: Integer); + procedure SplitterMouseUp(Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer); published // Обработчики событий формы — должны быть в published для RTTI/LFM @@ -821,6 +835,8 @@ begin FVfoOverlay.OnSelect := OnModeFilterSelect; FVfoOverlay.Visible := False; FPanelHidden := False; + FSplitterRatio := 0.40; // 40% спектр, 60% водопад по умолчанию + FSplitterDrag := False; FSpectrumBitmap := TBitmap.Create; FWaterfallBitmap := TBitmap.Create; @@ -1331,6 +1347,17 @@ begin PbRuler.OnPaint := PbRulerPaint; PbRuler.Cursor := crDefault; + // ---- Splitter между спектром+линейкой и водопадом ---- + PanelSplitter := TPanel.Create(Self); + PanelSplitter.Parent := PanelRight; + PanelSplitter.BevelOuter := bvNone; + PanelSplitter.Color := TColor($00303030); + PanelSplitter.Cursor := crVSplit; + PanelSplitter.Height := 5; + PanelSplitter.OnMouseDown := SplitterMouseDown; + PanelSplitter.OnMouseMove := SplitterMouseMove; + PanelSplitter.OnMouseUp := SplitterMouseUp; + PbWaterfall := TPaintBox.Create(Self); PbWaterfall.Parent := PanelRight; PbWaterfall.OnPaint := PbWaterfallPaint; @@ -1352,6 +1379,97 @@ begin ResizeSpectrumPanels; end; +// =========================================================================== +// Splitter — перетаскивание границы спектр/водопад +// Обновление размеров происходит только при отпускании мыши (MouseUp), +// чтобы исключить фризы во время перетаскивания. +// Во время перетаскивания рисуем только призрак-линию на PanelSplitter. +// =========================================================================== + +procedure TMainForm.SplitterMouseDown(Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer); +var + P: TPoint; +begin + if Button <> mbLeft then Exit; + FSplitterDrag := True; + FSplitterSH0 := FSpectrumHeight; + // Y в координатах PanelRight + P := PanelRight.ScreenToClient(PanelSplitter.ClientToScreen(Point(X, Y))); + FSplitterDragY0 := P.Y; + PanelSplitter.Color := TColor($005050A0); // подсветка при захвате + {$IFDEF WINDOWS} + SetCapture(PanelSplitter.Handle); + {$ENDIF} +end; + +procedure TMainForm.SplitterMouseMove(Sender: TObject; Shift: TShiftState; + X, Y: Integer); +var + P: TPoint; + DeltaY: Integer; + NewSH: Integer; + TopOff, AvailH: Integer; +const + RULER_H = 18; + SPLITTER_H = 5; + MIN_SH = 60; + MIN_WH = 40; +begin + if not FSplitterDrag then Exit; + P := PanelRight.ScreenToClient(PanelSplitter.ClientToScreen(Point(X, Y))); + DeltaY := P.Y - FSplitterDragY0; + + TopOff := PanelSpanButtons.Height; + AvailH := PanelRight.ClientHeight - TopOff - RULER_H - SPLITTER_H; + if AvailH <= 0 then Exit; + + NewSH := FSplitterSH0 + DeltaY; + if NewSH < MIN_SH then NewSH := MIN_SH; + if NewSH > AvailH - MIN_WH then NewSH := AvailH - MIN_WH; + + // Только двигаем сплиттер визуально — без пересчёта битмапов + PanelSplitter.Top := TopOff + NewSH + RULER_H; + PbWaterfall.Top := TopOff + NewSH + RULER_H + SPLITTER_H; + PbWaterfall.Height := AvailH - NewSH; +end; + +procedure TMainForm.SplitterMouseUp(Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer); +var + P: TPoint; + DeltaY: Integer; + NewSH: Integer; + TopOff, AvailH: Integer; +const + RULER_H = 18; + SPLITTER_H = 5; + MIN_SH = 60; + MIN_WH = 40; +begin + if not FSplitterDrag then Exit; + FSplitterDrag := False; + {$IFDEF WINDOWS} + ReleaseCapture; + {$ENDIF} + PanelSplitter.Color := TColor($00303030); // обычный цвет + + P := PanelRight.ScreenToClient(PanelSplitter.ClientToScreen(Point(X, Y))); + DeltaY := P.Y - FSplitterDragY0; + + TopOff := PanelSpanButtons.Height; + AvailH := PanelRight.ClientHeight - TopOff - RULER_H - SPLITTER_H; + if AvailH <= 0 then Exit; + + NewSH := FSplitterSH0 + DeltaY; + if NewSH < MIN_SH then NewSH := MIN_SH; + if NewSH > AvailH - MIN_WH then NewSH := AvailH - MIN_WH; + + // Сохраняем новое соотношение и делаем полный пересчёт с перерисовкой + FSplitterRatio := NewSH / AvailH; + ResizeSpectrumPanels; +end; + // =========================================================================== // Resize handler (normal method instead of anonymous procedure) // =========================================================================== @@ -1398,8 +1516,14 @@ begin end; procedure TMainForm.ResizeSpectrumPanels; +const + RULER_H = 18; + SPLITTER_H = 5; + MIN_SH = 60; // минимальная высота спектра + MIN_WH = 40; // минимальная высота водопада var RW, RH, SH, WH, TopOff: Integer; + AvailH: Integer; begin if PanelRight = nil then Exit; RW := PanelRight.ClientWidth; @@ -1407,16 +1531,29 @@ begin TopOff := PanelSpanButtons.Height; // S-метр позиционируется отдельно в ResizeSMeter ResizeSMeter; - RH := RH - TopOff; - if RH < 10 then Exit; + AvailH := RH - TopOff - RULER_H - SPLITTER_H; + if AvailH < (MIN_SH + MIN_WH) then Exit; - SH := RH * 4 div 10; - WH := RH - SH - 18; - if WH < 10 then WH := 10; + // Ограничиваем соотношение, чтобы каждая зона имела минимальный размер + if FSplitterRatio < MIN_SH / AvailH then + FSplitterRatio := MIN_SH / AvailH; + if FSplitterRatio > 1.0 - MIN_WH / AvailH then + FSplitterRatio := 1.0 - MIN_WH / AvailH; + + SH := Round(AvailH * FSplitterRatio); + WH := AvailH - SH; + if WH < MIN_WH then WH := MIN_WH; + + // Спектр + PbSpectrum.SetBounds(0, TopOff, RW, SH); + // Линейка частот — всегда прижата к низу спектра + PbRuler.SetBounds(0, TopOff + SH, RW, RULER_H); + // Сплиттер — между линейкой и водопадом + if PanelSplitter <> nil then + PanelSplitter.SetBounds(0, TopOff + SH + RULER_H, RW, SPLITTER_H); + // Водопад — под сплиттером + PbWaterfall.SetBounds(0, TopOff + SH + RULER_H + SPLITTER_H, RW, WH); - PbSpectrum.SetBounds(0, TopOff, RW, SH); - PbRuler.SetBounds (0, TopOff + SH, RW, 18); - PbWaterfall.SetBounds(0, TopOff + SH + 18, RW, WH); PositionSpanOverlayButtons; // Обновляем переменные размеров