mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(slices): грид-раскладка панов + фиксы минимумов стека, утечки кнопок, CPU
Грид: пан 0 сверху во всю ширину, доп. паны колонками снизу; тумблер «▦/▤» в ряду зума пана 0, вертикальные сплиттеры-призраки колонок (FPanColShare), доля ряда FPanGridRowShare; автоткат в стек при <2 доп. панах. Регион пана обобщён до прямоугольника (StackLeft/StackWidth). Фиксы по экранному тесту: - наезд панов: раздача высот и драг сплиттеров клампятся минимумом содержимого пана (MinLayoutHeight/PanMinHeight, пан 0 += wideband); - «блуждающие» кнопки после STOP: MakeFlatBtn даёт Owner=контейнер, TPanafallPanel.Destroy теперь явно освобождает −/⌂/+/⊞/▦; - жор CPU ~40%/пан: убран безусловный Invalidate зум-бара из тика (SyncPanZoomUI), инвалидация только при реальной смене значений; - пропорция спектр/водопад пана 0 не восстанавливалась после закрытия панов: кламп ratio минимумами больше не пишется в FSplitterRatio. Подтверждено юзером на экране: грид/стек/STOP/pop-out работают, CPU со всеми панами 17-20%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+326
-38
@@ -183,6 +183,19 @@ type
|
||||
FPanSplitTop0: Integer;
|
||||
FPanSplitMinTop: Integer;
|
||||
FPanSplitMaxTop: Integer;
|
||||
// Грид-раскладка: пан 0 сверху во всю ширину, доп. паны колонками снизу.
|
||||
// Активна при FPanGridMode и >=2 доп. панах в стеке (иначе обычный стек).
|
||||
FPanGridMode: Boolean;
|
||||
FPanGridRowShare: Double; // доля высоты верхнего ряда (пан 0), 0.15..0.85
|
||||
FPanColShare: array[1..MAX_PANS-1] of Double; // доли ширины колонок
|
||||
FPanVSplitters: array[1..MAX_PANS-1] of TPanel; // слева от колонки пана i
|
||||
// Drag вертикального сплиттера (индекс пана справа; 0 = нет).
|
||||
FPanVSplitDrag: Integer;
|
||||
FPanVSplitX0: Integer;
|
||||
FPanVSplitLeft0: Integer;
|
||||
FPanVSplitMinLeft: Integer;
|
||||
FPanVSplitMaxLeft: Integer;
|
||||
FPanWideH: Integer; // wideband-высота последнего LayoutPanStack (для PanMinHeight)
|
||||
FPendingPanClose: Integer; // «×» шапки: PanId к закрытию в тике (0 = нет)
|
||||
// Меню rate пана («NNN kHz» в шапке панов N; создаётся лениво).
|
||||
FPanRateMenu: TPopupMenu;
|
||||
@@ -612,9 +625,17 @@ type
|
||||
procedure PanSplitMouseDown(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure PanSplitMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
|
||||
procedure OnPanGridToggle(Sender: TObject); // «▦/▤» в ряду зума пана 0
|
||||
function PanGridActive: Boolean; // грид реально применён
|
||||
procedure PanVSplitMouseDown(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure PanVSplitMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
|
||||
procedure PanVSplitMouseUp(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure PanSplitMouseUp(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure LayoutPanStack(AWideH: Integer); // регионы панов + Layout каждого
|
||||
function PanMinHeight(PanId: Integer): Integer; // мин. высота региона пана
|
||||
function Pan0HeaderH: Integer; // высота шапки пана 0 (0 без стека)
|
||||
procedure PbSpectrumDblClick(Sender: TObject);
|
||||
procedure TrkDriveChange(Sender: TObject);
|
||||
@@ -1079,6 +1100,8 @@ begin
|
||||
FPan.OnADCClick := OnPanADCClick; // бейдж RX1/RX2 (3.4, платы с 2 АЦП)
|
||||
FPans[0] := FPan;
|
||||
FPanShare[0] := 1.0;
|
||||
FPanGridMode := False;
|
||||
FPanGridRowShare := 0.5; // грид: пан 0 — верхняя половина
|
||||
FPanCbLock := SyncObjs.TCriticalSection.Create;
|
||||
FSpecView.VfoA := FController.FVfoA;
|
||||
FSpecView.VfoB := FController.FVfoB;
|
||||
@@ -2309,6 +2332,7 @@ begin
|
||||
FPan.ZoomInClick := BtnZoomInClick;
|
||||
FPan.ZoomDefClick := BtnZoomDefClick;
|
||||
FPan.ZoomOutClick := BtnZoomOutClick;
|
||||
FPan.GridClick := OnPanGridToggle; // «▦/▤» грид-раскладка панов
|
||||
FPan.OnSplitterMoved := RightPanelResize;
|
||||
FPan.Build(PanelRight, Max(1, FController.FSpecMSAA));
|
||||
// Алиасы контролов (исторические имена; постепенная миграция на FPan.*)
|
||||
@@ -2574,13 +2598,37 @@ begin
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
function TMainForm.PanGridActive: Boolean;
|
||||
// Грид реально применён: включён юзером и в стеке >=2 доп. панов
|
||||
// (с одним доп. паном грид совпадает со стеком).
|
||||
var i, N: Integer;
|
||||
begin
|
||||
N := 0;
|
||||
for i := 1 to MAX_PANS - 1 do
|
||||
if (FPans[i] <> nil) and not PanFloating(i) then Inc(N);
|
||||
Result := FPanGridMode and (N >= 2);
|
||||
end;
|
||||
|
||||
function TMainForm.PanMinHeight(PanId: Integer): Integer;
|
||||
// Мин. высота региона пана в стеке/гриде: ниже неё Layout панели клампит
|
||||
// спектр/водопад к минимумам и виджеты вылезают за регион (паны наезжают).
|
||||
begin
|
||||
if FPans[PanId] = nil then Exit(0);
|
||||
Result := FPans[PanId].MinLayoutHeight(
|
||||
IfThen(PanId = 0, FPanWideH, 0),
|
||||
FController.FShowSpectrum, FController.FShowWaterfall);
|
||||
end;
|
||||
|
||||
procedure TMainForm.LayoutPanStack(AWideH: Integer);
|
||||
const
|
||||
PAN_SPLIT_H = 5;
|
||||
var
|
||||
ActiveIds: array[0..MAX_PANS-1] of Integer;
|
||||
N, i, k, TotalH, UsableH, Y, Hi: Integer;
|
||||
MinH: array[0..MAX_PANS-1] of Integer;
|
||||
N, i, k, TotalH, TotalW, UsableH, UsableW, Y, X, Hi, Wi: Integer;
|
||||
Row0H, RowTop, RowH, M, MinRest, RemainH, BotMin: Integer;
|
||||
ShareSum: Double;
|
||||
Grid: Boolean;
|
||||
begin
|
||||
// Плавающие (pop-out) паны в стеке не участвуют — у них своё окно.
|
||||
N := 0;
|
||||
@@ -2588,37 +2636,148 @@ begin
|
||||
if (FPans[i] <> nil) and not PanFloating(i) then
|
||||
begin ActiveIds[N] := i; Inc(N); end;
|
||||
if N = 0 then Exit;
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
UsableH := TotalH - (N - 1) * PAN_SPLIT_H;
|
||||
ShareSum := 0;
|
||||
for k := 0 to N - 1 do ShareSum := ShareSum + FPanShare[ActiveIds[k]];
|
||||
if ShareSum <= 0 then ShareSum := 1;
|
||||
Y := 0;
|
||||
for k := 0 to N - 1 do
|
||||
FPanWideH := AWideH; // для PanMinHeight (в т.ч. из сплиттер-драга)
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
TotalW := PanelRight.ClientWidth;
|
||||
Grid := PanGridActive; // пан 0 + >=2 доп. панов колонками
|
||||
|
||||
// Тумблер «▦/▤» в ряду зума пана 0: есть смысл при >=2 доп. панах в стеке.
|
||||
if FPans[0] <> nil then
|
||||
begin
|
||||
i := ActiveIds[k];
|
||||
if k > 0 then
|
||||
// Виден при >=2 доп. панах; при включённом гриде — всегда (выключить можно
|
||||
// даже когда паны закрылись и раскладка сама откатилась в стек).
|
||||
FPans[0].GridBtnEnabled := FPanGridMode or (N >= 3);
|
||||
if FPans[0].BtnGrid <> nil then
|
||||
begin
|
||||
FPanSplitters[i].SetBounds(0, Y, PanelRight.ClientWidth, PAN_SPLIT_H);
|
||||
Inc(Y, PAN_SPLIT_H);
|
||||
if FPanGridMode then FPans[0].BtnGrid.Caption := '▤'
|
||||
else FPans[0].BtnGrid.Caption := '▦';
|
||||
end;
|
||||
if k = N - 1 then
|
||||
Hi := TotalH - Y // последний пан добирает остаток (округления долей)
|
||||
else
|
||||
Hi := Round(UsableH * FPanShare[i] / ShareSum);
|
||||
FPans[i].StackTop := Y;
|
||||
FPans[i].StackHeight := Hi;
|
||||
end;
|
||||
|
||||
if not Grid then
|
||||
begin
|
||||
// ---- Вертикальный стек (как раньше) ----
|
||||
for i := 1 to MAX_PANS - 1 do
|
||||
if FPanVSplitters[i] <> nil then FPanVSplitters[i].Visible := False;
|
||||
UsableH := TotalH - (N - 1) * PAN_SPLIT_H;
|
||||
ShareSum := 0;
|
||||
for k := 0 to N - 1 do ShareSum := ShareSum + FPanShare[ActiveIds[k]];
|
||||
if ShareSum <= 0 then ShareSum := 1;
|
||||
// Видимость шапки — по ОБЩЕМУ числу панов (включая плавающие), как
|
||||
// Pan0HeaderH/UpdatePanHeaders: иначе при pop-out пана N шапка пана 0
|
||||
// прячется, а wideband остаётся сдвинутым на Pan0HeaderH — спектр
|
||||
// накрывает линейку частот wideband.
|
||||
FPans[i].HeaderVisible := PanCount > 1;
|
||||
if i = 0 then
|
||||
FPans[i].Layout(AWideH, FController.FShowSpectrum, FController.FShowWaterfall)
|
||||
else
|
||||
FPans[i].Layout(0, FController.FShowSpectrum, FController.FShowWaterfall);
|
||||
Inc(Y, Hi);
|
||||
// накрывает линейку частот wideband. Ставим ДО расчёта минимумов
|
||||
// (MinLayoutHeight учитывает шапку).
|
||||
MinRest := 0;
|
||||
for k := 0 to N - 1 do
|
||||
begin
|
||||
i := ActiveIds[k];
|
||||
FPans[i].HeaderVisible := PanCount > 1;
|
||||
MinH[k] := PanMinHeight(i);
|
||||
Inc(MinRest, MinH[k]);
|
||||
end;
|
||||
Y := 0;
|
||||
RemainH := UsableH;
|
||||
for k := 0 to N - 1 do
|
||||
begin
|
||||
i := ActiveIds[k];
|
||||
Dec(MinRest, MinH[k]); // теперь = сумма минимумов панов ПОСЛЕ текущего
|
||||
if k > 0 then
|
||||
begin
|
||||
FPanSplitters[i].Visible := True; // грид мог спрятать
|
||||
FPanSplitters[i].SetBounds(0, Y, TotalW, PAN_SPLIT_H);
|
||||
Inc(Y, PAN_SPLIT_H);
|
||||
end;
|
||||
if k = N - 1 then
|
||||
Hi := RemainH // последний пан добирает остаток (округления долей)
|
||||
else
|
||||
begin
|
||||
Hi := Round(UsableH * FPanShare[i] / ShareSum);
|
||||
// Доли не смеют ужать пан ниже минимума содержимого (виджеты вылезут
|
||||
// за регион и паны наедут друг на друга), а раздутый минимумом пан —
|
||||
// отобрать у оставшихся их минимумы.
|
||||
if Hi < MinH[k] then Hi := MinH[k];
|
||||
if Hi > RemainH - MinRest then Hi := RemainH - MinRest;
|
||||
if Hi < 1 then Hi := 1; // окно меньше суммы минимумов — деградируем без наезда выше
|
||||
end;
|
||||
Dec(RemainH, Hi);
|
||||
FPans[i].StackTop := Y;
|
||||
FPans[i].StackHeight := Hi;
|
||||
FPans[i].StackLeft := 0;
|
||||
FPans[i].StackWidth := 0; // вся ширина
|
||||
if i = 0 then
|
||||
FPans[i].Layout(AWideH, FController.FShowSpectrum, FController.FShowWaterfall)
|
||||
else
|
||||
FPans[i].Layout(0, FController.FShowSpectrum, FController.FShowWaterfall);
|
||||
Inc(Y, Hi);
|
||||
end;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// ---- Грид: пан 0 сверху во всю ширину, доп. паны колонками снизу ----
|
||||
FPanGridRowShare := EnsureRange(FPanGridRowShare, 0.15, 0.85);
|
||||
Row0H := Round((TotalH - PAN_SPLIT_H) * FPanGridRowShare);
|
||||
// Кламп ряда минимумами содержимого (шапки — до расчёта минимумов):
|
||||
// ни пан 0, ни колонки нижнего ряда не смеют стать ниже своих минимумов.
|
||||
FPans[0].HeaderVisible := PanCount > 1;
|
||||
BotMin := 0;
|
||||
for k := 1 to N - 1 do
|
||||
begin
|
||||
i := ActiveIds[k];
|
||||
FPans[i].HeaderVisible := True;
|
||||
BotMin := Max(BotMin, PanMinHeight(i));
|
||||
end;
|
||||
if Row0H > TotalH - PAN_SPLIT_H - BotMin then
|
||||
Row0H := TotalH - PAN_SPLIT_H - BotMin;
|
||||
if Row0H < PanMinHeight(0) then Row0H := PanMinHeight(0);
|
||||
FPans[0].StackTop := 0;
|
||||
FPans[0].StackHeight := Row0H;
|
||||
FPans[0].StackLeft := 0;
|
||||
FPans[0].StackWidth := 0;
|
||||
FPans[0].Layout(AWideH, FController.FShowSpectrum, FController.FShowWaterfall);
|
||||
|
||||
// Горизонтальный сплиттер ряда — сплиттер ПЕРВОЙ колонки (во всю ширину);
|
||||
// сплиттеры остальных колонок прячем (между колонками — вертикальные).
|
||||
M := N - 1; // колонок в нижнем ряду
|
||||
FPanSplitters[ActiveIds[1]].Visible := True;
|
||||
FPanSplitters[ActiveIds[1]].SetBounds(0, Row0H, TotalW, PAN_SPLIT_H);
|
||||
for k := 2 to N - 1 do
|
||||
FPanSplitters[ActiveIds[k]].Visible := False;
|
||||
|
||||
RowTop := Row0H + PAN_SPLIT_H;
|
||||
RowH := TotalH - RowTop;
|
||||
UsableW := TotalW - (M - 1) * PAN_SPLIT_H;
|
||||
ShareSum := 0;
|
||||
for k := 1 to N - 1 do ShareSum := ShareSum + FPanColShare[ActiveIds[k]];
|
||||
if ShareSum <= 0 then ShareSum := 1;
|
||||
X := 0;
|
||||
for k := 1 to N - 1 do
|
||||
begin
|
||||
i := ActiveIds[k];
|
||||
if k > 1 then
|
||||
begin
|
||||
FPanVSplitters[i].Visible := True;
|
||||
FPanVSplitters[i].SetBounds(X, RowTop, PAN_SPLIT_H, RowH);
|
||||
Inc(X, PAN_SPLIT_H);
|
||||
end
|
||||
else if FPanVSplitters[i] <> nil then
|
||||
FPanVSplitters[i].Visible := False; // у первой колонки левого сплиттера нет
|
||||
if k = N - 1 then
|
||||
Wi := TotalW - X
|
||||
else
|
||||
Wi := Round(UsableW * FPanColShare[i] / ShareSum);
|
||||
FPans[i].StackTop := RowTop;
|
||||
FPans[i].StackHeight := RowH;
|
||||
FPans[i].StackLeft := X;
|
||||
FPans[i].StackWidth := Wi;
|
||||
FPans[i].HeaderVisible := True;
|
||||
FPans[i].Layout(0, FController.FShowSpectrum, FController.FShowWaterfall);
|
||||
Inc(X, Wi);
|
||||
end;
|
||||
// Вертикальные сплиттеры панов вне нижнего ряда (плавающие) — спрятать.
|
||||
for i := 1 to MAX_PANS - 1 do
|
||||
if (FPanVSplitters[i] <> nil) and ((FPans[i] = nil) or PanFloating(i)) then
|
||||
FPanVSplitters[i].Visible := False;
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
@@ -2701,6 +2860,7 @@ begin
|
||||
if FPans[i] <> nil then
|
||||
begin
|
||||
if FPans[i].BtnAddPan <> nil then StyleButton(FPans[i].BtnAddPan, False);
|
||||
if FPans[i].BtnGrid <> nil then StyleButton(FPans[i].BtnGrid, False);
|
||||
if FPans[i].BtnPopPan <> nil then StyleButton(FPans[i].BtnPopPan, False);
|
||||
if FPans[i].BtnClosePan <> nil then StyleButton(FPans[i].BtnClosePan, False);
|
||||
ApplyPanHeaderTheme(FPans[i]);
|
||||
@@ -2708,6 +2868,7 @@ begin
|
||||
begin
|
||||
FPans[i].SetFlagsTheme(T);
|
||||
if FPanSplitters[i] <> nil then FPanSplitters[i].Color := T.Border;
|
||||
if FPanVSplitters[i] <> nil then FPanVSplitters[i].Color := T.Border;
|
||||
if FPanFloatForm[i] <> nil then FPanFloatForm[i].Color := T.BG;
|
||||
// Зум-бар пана N (пан 0 — через алиас FPanZoomBar ниже).
|
||||
if FPans[i].ZoomBar <> nil then FPans[i].ZoomBar.SetTheme(T);
|
||||
@@ -5371,7 +5532,9 @@ begin
|
||||
FController.PanDDCRateKHz(P.PanId) * 1000.0);
|
||||
P.ZoomBar.SetState(FController.PanZoomFactor(P.PanId),
|
||||
FController.PanPanSlider(P.PanId));
|
||||
if P.PbPanZoom <> nil then P.PbPanZoom.Invalidate;
|
||||
// Инвалидация бара — внутри Set{Freq,State} и ТОЛЬКО при реальной смене:
|
||||
// нас зовут из тика на каждый пан каждый кадр, безусловный Invalidate =
|
||||
// полный софт-рендер бара + Qt-рекомпозит на частоте кадров (жор CPU).
|
||||
end;
|
||||
|
||||
procedure TMainForm.RefreshPanZoom(P: TPanafallPanel);
|
||||
@@ -5382,6 +5545,7 @@ begin
|
||||
P.LayoutFlags;
|
||||
MarkPanDirty(P);
|
||||
if P.PbWaterfall <> nil then P.PbWaterfall.Invalidate;
|
||||
if P.PbPanZoom <> nil then P.PbPanZoom.Invalidate; // событийный путь — можно
|
||||
end;
|
||||
|
||||
procedure TMainForm.AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
||||
@@ -5509,7 +5673,10 @@ begin
|
||||
ResetPanGLCaches(P);
|
||||
P.StackTop := 0;
|
||||
P.StackHeight := 0; // 0/0 = весь родитель (окно)
|
||||
P.StackLeft := 0;
|
||||
P.StackWidth := 0;
|
||||
if FPanSplitters[PanId] <> nil then FPanSplitters[PanId].Visible := False;
|
||||
if FPanVSplitters[PanId] <> nil then FPanVSplitters[PanId].Visible := False;
|
||||
F.Show;
|
||||
UpdatePanHeaders;
|
||||
ResizeSpectrumPanels; // пере-стек оставшихся в главном окне
|
||||
@@ -5761,6 +5928,20 @@ begin
|
||||
Split.OnMouseUp := PanSplitMouseUp;
|
||||
FPanSplitters[PanId] := Split;
|
||||
|
||||
// Вертикальный сплиттер слева от колонки пана (грид-раскладка).
|
||||
Split := TPanel.Create(Self);
|
||||
Split.Parent := PanelRight;
|
||||
Split.BevelOuter := bvNone;
|
||||
Split.Color := TColor($00303030);
|
||||
Split.Cursor := crHSplit;
|
||||
Split.Tag := PanId;
|
||||
Split.Visible := False; // включает LayoutPanStack в грид-режиме
|
||||
Split.OnMouseDown := PanVSplitMouseDown;
|
||||
Split.OnMouseMove := PanVSplitMouseMove;
|
||||
Split.OnMouseUp := PanVSplitMouseUp;
|
||||
FPanVSplitters[PanId] := Split;
|
||||
FPanColShare[PanId] := 1.0;
|
||||
|
||||
// Публикуем пан для пиксельных колбэков (display-поток) и делим высоту поровну.
|
||||
FPanCbLock.Enter;
|
||||
try
|
||||
@@ -5807,6 +5988,7 @@ begin
|
||||
if (FActiveSliceId <> 0) and not FController.GetSlice(FActiveSliceId, S) then
|
||||
FActiveSliceId := 0;
|
||||
FreeAndNil(FPanSplitters[PanId]);
|
||||
FreeAndNil(FPanVSplitters[PanId]);
|
||||
P.Free;
|
||||
// Пан был в отдельном окне — окно больше не нужно. Release (не Free):
|
||||
// сюда можно попасть из событий контролов этого же окна («×» шапки → тик).
|
||||
@@ -6042,24 +6224,42 @@ procedure TMainForm.PanSplitMouseDown(Sender: TObject; Button: TMouseButton;
|
||||
const
|
||||
PAN_SPLIT_H = 5;
|
||||
var
|
||||
BelowId, AboveId, i, TotalH, MinPx: Integer;
|
||||
BelowId, AboveId, i, TotalH, MinPx, BotMin: Integer;
|
||||
Split: TPanel;
|
||||
begin
|
||||
if Button <> mbLeft then Exit;
|
||||
Split := TPanel(Sender);
|
||||
BelowId := Split.Tag;
|
||||
// Пан над сплиттером — ближайший активный с меньшим PanId.
|
||||
AboveId := -1;
|
||||
for i := BelowId - 1 downto 0 do
|
||||
if FPans[i] <> nil then begin AboveId := i; Break; end;
|
||||
if (AboveId < 0) or (FPans[BelowId] = nil) then Exit;
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
if TotalH <= 0 then Exit;
|
||||
// Границы призрака: каждому пану — минимум 15% стека (= кламп долей в MouseUp).
|
||||
MinPx := Round(0.15 * TotalH);
|
||||
FPanSplitMinTop := FPans[AboveId].StackTop + MinPx;
|
||||
FPanSplitMaxTop := FPans[BelowId].StackTop + FPans[BelowId].StackHeight
|
||||
- MinPx - PAN_SPLIT_H;
|
||||
if PanGridActive then
|
||||
begin
|
||||
// Грид: этот сплиттер — граница ряда пан 0 / нижний ряд колонок.
|
||||
// Минимум — 15% ИЛИ минимум содержимого (что больше): ниже минимума
|
||||
// виджеты пана вылезают за регион и паны наезжают друг на друга.
|
||||
FPanSplitMinTop := Max(MinPx, PanMinHeight(0));
|
||||
BotMin := MinPx; // минимум нижнего ряда — макс. по его колонкам
|
||||
for i := 1 to MAX_PANS - 1 do
|
||||
if (FPans[i] <> nil) and not PanFloating(i) then
|
||||
BotMin := Max(BotMin, PanMinHeight(i));
|
||||
FPanSplitMaxTop := TotalH - BotMin - PAN_SPLIT_H;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Пан над сплиттером — ближайший активный с меньшим PanId.
|
||||
AboveId := -1;
|
||||
for i := BelowId - 1 downto 0 do
|
||||
if FPans[i] <> nil then begin AboveId := i; Break; end;
|
||||
if (AboveId < 0) or (FPans[BelowId] = nil) then Exit;
|
||||
// Границы призрака: каждому пану — минимум 15% стека ИЛИ минимум его
|
||||
// содержимого (что больше; = кламп раздачи в LayoutPanStack).
|
||||
FPanSplitMinTop := FPans[AboveId].StackTop
|
||||
+ Max(MinPx, PanMinHeight(AboveId));
|
||||
FPanSplitMaxTop := FPans[BelowId].StackTop + FPans[BelowId].StackHeight
|
||||
- Max(MinPx, PanMinHeight(BelowId)) - PAN_SPLIT_H;
|
||||
end;
|
||||
if FPanSplitMaxTop < FPanSplitMinTop then FPanSplitMaxTop := FPanSplitMinTop;
|
||||
FPanSplitDrag := BelowId;
|
||||
FPanSplitY0 := Mouse.CursorPos.Y;
|
||||
FPanSplitTop0 := Split.Top;
|
||||
@@ -6088,13 +6288,20 @@ begin
|
||||
BelowId := FPanSplitDrag;
|
||||
FPanSplitDrag := 0;
|
||||
TPanel(Sender).Color := TColor($00303030);
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
if TotalH <= 0 then Exit;
|
||||
if PanGridActive then
|
||||
begin
|
||||
// Грид: сплиттер задаёт долю высоты верхнего ряда (пан 0) напрямую.
|
||||
FPanGridRowShare := EnsureRange(TPanel(Sender).Top / TotalH, 0.15, 0.85);
|
||||
ResizeSpectrumPanels;
|
||||
Exit;
|
||||
end;
|
||||
// Пан над сплиттером — ближайший активный с меньшим PanId.
|
||||
AboveId := -1;
|
||||
for i := BelowId - 1 downto 0 do
|
||||
if FPans[i] <> nil then begin AboveId := i; Break; end;
|
||||
if (AboveId < 0) or (FPans[BelowId] = nil) then Exit;
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
if TotalH <= 0 then Exit;
|
||||
// Дельта — по финальному положению призрака (он уже клампился в Move).
|
||||
DeltaY := TPanel(Sender).Top - FPanSplitTop0;
|
||||
DShare := DeltaY / TotalH;
|
||||
@@ -6108,6 +6315,87 @@ begin
|
||||
ResizeSpectrumPanels;
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnPanGridToggle(Sender: TObject);
|
||||
begin
|
||||
FPanGridMode := not FPanGridMode;
|
||||
ResizeSpectrumPanels;
|
||||
end;
|
||||
|
||||
procedure TMainForm.PanVSplitMouseDown(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
const
|
||||
PAN_SPLIT_W = 5;
|
||||
var
|
||||
RightId, LeftId, i, TotalW, MinPx: Integer;
|
||||
Split: TPanel;
|
||||
begin
|
||||
if (Button <> mbLeft) or not PanGridActive then Exit;
|
||||
Split := TPanel(Sender);
|
||||
RightId := Split.Tag;
|
||||
// Колонка слева — ближайший застекованный доп. пан с меньшим PanId.
|
||||
LeftId := -1;
|
||||
for i := RightId - 1 downto 1 do
|
||||
if (FPans[i] <> nil) and not PanFloating(i) then begin LeftId := i; Break; end;
|
||||
if (LeftId < 0) or (FPans[RightId] = nil) then Exit;
|
||||
TotalW := PanelRight.ClientWidth;
|
||||
if TotalW <= 0 then Exit;
|
||||
// Каждой колонке — минимум 15% ширины (= кламп долей в MouseUp).
|
||||
MinPx := Round(0.15 * TotalW);
|
||||
FPanVSplitMinLeft := FPans[LeftId].StackLeft + MinPx;
|
||||
FPanVSplitMaxLeft := FPans[RightId].StackLeft + FPans[RightId].StackWidth
|
||||
- MinPx - PAN_SPLIT_W;
|
||||
FPanVSplitDrag := RightId;
|
||||
FPanVSplitX0 := Mouse.CursorPos.X;
|
||||
FPanVSplitLeft0 := Split.Left;
|
||||
Split.Color := TColor($005050A0);
|
||||
end;
|
||||
|
||||
procedure TMainForm.PanVSplitMouseMove(Sender: TObject; Shift: TShiftState;
|
||||
X, Y: Integer);
|
||||
var NewLeft: Integer;
|
||||
begin
|
||||
// Живой призрак: двигаем сам сплиттер, пересчёт долей — по MouseUp.
|
||||
if (FPanVSplitDrag = 0) or (TPanel(Sender).Tag <> FPanVSplitDrag) then Exit;
|
||||
NewLeft := FPanVSplitLeft0 + (Mouse.CursorPos.X - FPanVSplitX0);
|
||||
if NewLeft < FPanVSplitMinLeft then NewLeft := FPanVSplitMinLeft;
|
||||
if NewLeft > FPanVSplitMaxLeft then NewLeft := FPanVSplitMaxLeft;
|
||||
TPanel(Sender).Left := NewLeft;
|
||||
end;
|
||||
|
||||
procedure TMainForm.PanVSplitMouseUp(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
var
|
||||
RightId, LeftId, i, DeltaX, TotalW: Integer;
|
||||
DShare, MinShare, ShareSum: Double;
|
||||
begin
|
||||
if FPanVSplitDrag = 0 then Exit;
|
||||
RightId := FPanVSplitDrag;
|
||||
FPanVSplitDrag := 0;
|
||||
TPanel(Sender).Color := TColor($00303030);
|
||||
LeftId := -1;
|
||||
for i := RightId - 1 downto 1 do
|
||||
if (FPans[i] <> nil) and not PanFloating(i) then begin LeftId := i; Break; end;
|
||||
if (LeftId < 0) or (FPans[RightId] = nil) then Exit;
|
||||
TotalW := PanelRight.ClientWidth;
|
||||
if TotalW <= 0 then Exit;
|
||||
// Доли колонок нормируются суммой (init 1.0 на пан) — пиксели → доли через неё.
|
||||
ShareSum := 0;
|
||||
for i := 1 to MAX_PANS - 1 do
|
||||
if (FPans[i] <> nil) and not PanFloating(i) then
|
||||
ShareSum := ShareSum + FPanColShare[i];
|
||||
if ShareSum <= 0 then ShareSum := 1;
|
||||
DeltaX := TPanel(Sender).Left - FPanVSplitLeft0;
|
||||
DShare := DeltaX / TotalW * ShareSum;
|
||||
MinShare := 0.15 * ShareSum;
|
||||
if FPanColShare[LeftId] + DShare < MinShare then
|
||||
DShare := MinShare - FPanColShare[LeftId];
|
||||
if FPanColShare[RightId] - DShare < MinShare then
|
||||
DShare := FPanColShare[RightId] - MinShare;
|
||||
FPanColShare[LeftId] := FPanColShare[LeftId] + DShare;
|
||||
FPanColShare[RightId] := FPanColShare[RightId] - DShare;
|
||||
ResizeSpectrumPanels;
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnVfoOverlaySliceSelect(Sender: TObject);
|
||||
begin
|
||||
if Sender is TVfoOverlay then
|
||||
|
||||
Reference in New Issue
Block a user