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:
+315
-27
@@ -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;
|
||||
FPanWideH := AWideH; // для PanMinHeight (в т.ч. из сплиттер-драга)
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
TotalW := PanelRight.ClientWidth;
|
||||
Grid := PanGridActive; // пан 0 + >=2 доп. панов колонками
|
||||
|
||||
// Тумблер «▦/▤» в ряду зума пана 0: есть смысл при >=2 доп. панах в стеке.
|
||||
if FPans[0] <> nil then
|
||||
begin
|
||||
// Виден при >=2 доп. панах; при включённом гриде — всегда (выключить можно
|
||||
// даже когда паны закрылись и раскладка сама откатилась в стек).
|
||||
FPans[0].GridBtnEnabled := FPanGridMode or (N >= 3);
|
||||
if FPans[0].BtnGrid <> nil then
|
||||
begin
|
||||
if FPanGridMode then FPans[0].BtnGrid.Caption := '▤'
|
||||
else FPans[0].BtnGrid.Caption := '▦';
|
||||
end;
|
||||
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;
|
||||
Y := 0;
|
||||
for k := 0 to N - 1 do
|
||||
begin
|
||||
i := ActiveIds[k];
|
||||
if k > 0 then
|
||||
begin
|
||||
FPanSplitters[i].SetBounds(0, Y, PanelRight.ClientWidth, PAN_SPLIT_H);
|
||||
Inc(Y, PAN_SPLIT_H);
|
||||
end;
|
||||
if k = N - 1 then
|
||||
Hi := TotalH - Y // последний пан добирает остаток (округления долей)
|
||||
else
|
||||
Hi := Round(UsableH * FPanShare[i] / ShareSum);
|
||||
FPans[i].StackTop := Y;
|
||||
FPans[i].StackHeight := Hi;
|
||||
// Видимость шапки — по ОБЩЕМУ числу панов (включая плавающие), как
|
||||
// Pan0HeaderH/UpdatePanHeaders: иначе при pop-out пана N шапка пана 0
|
||||
// прячется, а wideband остаётся сдвинутым на Pan0HeaderH — спектр
|
||||
// накрывает линейку частот wideband.
|
||||
// накрывает линейку частот 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;
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
if TotalH <= 0 then Exit;
|
||||
MinPx := Round(0.15 * TotalH);
|
||||
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;
|
||||
TotalH := PanelRight.ClientHeight;
|
||||
if TotalH <= 0 then Exit;
|
||||
// Границы призрака: каждому пану — минимум 15% стека (= кламп долей в MouseUp).
|
||||
MinPx := Round(0.15 * TotalH);
|
||||
FPanSplitMinTop := FPans[AboveId].StackTop + MinPx;
|
||||
// Границы призрака: каждому пану — минимум 15% стека ИЛИ минимум его
|
||||
// содержимого (что больше; = кламп раздачи в LayoutPanStack).
|
||||
FPanSplitMinTop := FPans[AboveId].StackTop
|
||||
+ Max(MinPx, PanMinHeight(AboveId));
|
||||
FPanSplitMaxTop := FPans[BelowId].StackTop + FPans[BelowId].StackHeight
|
||||
- MinPx - PAN_SPLIT_H;
|
||||
- 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
|
||||
|
||||
+102
-26
@@ -110,6 +110,10 @@ type
|
||||
FOnPanClose: TNotifyEvent;
|
||||
FOnAddPan: TNotifyEvent;
|
||||
FAddPanWanted: Boolean; // хозяин: показывать «⊞» в ряду пан/зума
|
||||
FBtnGrid: TFlatButton; // «▦/▤» тумблер грид-раскладки (пан 0)
|
||||
FGridBtnWanted: Boolean; // хозяин: показывать тумблер (>=2 доп. панов)
|
||||
FStackLeft: Integer; // регион пана: X (0 при полной ширине)
|
||||
FStackWidth: Integer; // ширина региона (0 = вся ширина родителя)
|
||||
|
||||
procedure BtnClosePanClick(Sender: TObject);
|
||||
procedure BtnAddPanClick(Sender: TObject);
|
||||
@@ -135,7 +139,7 @@ type
|
||||
procedure PanZoomMouseUp(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure PanZoomDblClick(Sender: TObject);
|
||||
procedure PositionPanZoomBar(BottomY, RW, H: Integer; AVisible: Boolean);
|
||||
procedure PositionPanZoomBar(X0, BottomY, RW, H: Integer; AVisible: Boolean);
|
||||
public
|
||||
// Обработчики мыши спектра/водопада и кликов зум-кнопок хозяина —
|
||||
// задать ДО вызова Build (Build подвешивает их на создаваемые контролы).
|
||||
@@ -150,6 +154,7 @@ type
|
||||
ZoomInClick: TNotifyEvent;
|
||||
ZoomDefClick: TNotifyEvent;
|
||||
ZoomOutClick: TNotifyEvent;
|
||||
GridClick: TNotifyEvent; // «▦/▤» тумблер грид-раскладки (пан 0)
|
||||
|
||||
constructor Create(AOwner: TComponent; AUseOpenGL: Boolean); reintroduce;
|
||||
destructor Destroy; override;
|
||||
@@ -164,6 +169,10 @@ type
|
||||
// над спектром. Show-флаги приходят от контроллера (FShowSpectrum/
|
||||
// FShowWaterfall).
|
||||
procedure Layout(ATopOffset: Integer; AShowSpectrum, AShowWaterfall: Boolean);
|
||||
// Мин. высота региона, при которой Layout не вылезает за него (те же
|
||||
// константы/DPI, что в Layout; учитывает текущую HeaderVisible).
|
||||
function MinLayoutHeight(ATopOffset: Integer;
|
||||
AShowSpectrum, AShowWaterfall: Boolean): Integer;
|
||||
|
||||
property View: TSpectrumView read FView;
|
||||
property PbSpectrum: TControl read FPbSpectrum;
|
||||
@@ -186,6 +195,9 @@ type
|
||||
property PanId: Integer read FPanId write FPanId;
|
||||
property StackTop: Integer read FStackTop write FStackTop;
|
||||
property StackHeight: Integer read FStackHeight write FStackHeight;
|
||||
// Грид-раскладка: регион пана может быть колонкой (StackWidth=0 = вся ширина).
|
||||
property StackLeft: Integer read FStackLeft write FStackLeft;
|
||||
property StackWidth: Integer read FStackWidth write FStackWidth;
|
||||
// Шапка пана: текст ставит хозяин (частота), rate — отдельным лейблом
|
||||
// (SetHeaderRate); у панов N>0 хозяин делает его кликабельным (селектор
|
||||
// rate, RateClickable + OnRateClick). «×» → OnPanClose.
|
||||
@@ -212,6 +224,9 @@ type
|
||||
property ShowZoomRow: Boolean read FShowZoomRow write FShowZoomRow;
|
||||
// «⊞» в ряду пан/зума (пан 0): показать/спрятать решает хозяин.
|
||||
property AddPanEnabled: Boolean read FAddPanWanted write FAddPanWanted;
|
||||
// «▦/▤» тумблер грид-раскладки (пан 0, виден при >=2 доп. панах).
|
||||
property GridBtnEnabled: Boolean read FGridBtnWanted write FGridBtnWanted;
|
||||
property BtnGrid: TFlatButton read FBtnGrid;
|
||||
property OnPanClose: TNotifyEvent read FOnPanClose write FOnPanClose;
|
||||
property OnAddPan: TNotifyEvent read FOnAddPan write FOnAddPan;
|
||||
// Пользователь отпустил сплиттер (ratio уже обновлён) — хозяин делает
|
||||
@@ -288,6 +303,14 @@ begin
|
||||
// (Owner=Self) освободит inherited.
|
||||
FreeAndNil(FZoomBar);
|
||||
FreeAndNil(FView);
|
||||
// Кнопки ряда зума создаются MakeFlatBtn с Owner=КОНТЕЙНЕР (не Self) —
|
||||
// inherited их не освободит, и после гибели пана они оставались жить
|
||||
// на родителе (STOP → «блуждающие» −/⌂/+ поверх окна).
|
||||
FreeAndNil(FBtnZoomOut);
|
||||
FreeAndNil(FBtnZoomDef);
|
||||
FreeAndNil(FBtnZoomIn);
|
||||
FreeAndNil(FBtnAddPan);
|
||||
FreeAndNil(FBtnGrid);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
@@ -385,6 +408,11 @@ begin
|
||||
FBtnAddPan.Hint := 'Add panadapter';
|
||||
FBtnAddPan.ShowHint := True;
|
||||
FBtnAddPan.Visible := False;
|
||||
// Тумблер грид-раскладки (пан 0; глифом рулит хозяин: ▦ = в грид, ▤ = в стек).
|
||||
FBtnGrid := MakeFlatBtn(AParent, '▦', 0, 0, 10, 10, GridClick);
|
||||
FBtnGrid.Hint := 'Grid layout';
|
||||
FBtnGrid.ShowHint := True;
|
||||
FBtnGrid.Visible := False;
|
||||
|
||||
// ---- Шапка пана (тонкая полоска над спектром; видна при панах >= 2) ----
|
||||
FHeaderPanel := TPanel.Create(Self);
|
||||
@@ -427,6 +455,7 @@ begin
|
||||
FBtnZoomDef.Parent := NewParent;
|
||||
FBtnZoomIn.Parent := NewParent;
|
||||
FBtnAddPan.Parent := NewParent;
|
||||
FBtnGrid.Parent := NewParent;
|
||||
FHeaderPanel.Parent := NewParent; // дети шапки едут вместе с ней
|
||||
end;
|
||||
|
||||
@@ -538,31 +567,39 @@ begin
|
||||
FBtnPopPan.SetBounds(RW - 2 * BtnS - 6, 2, BtnS, BtnS);
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.PositionPanZoomBar(BottomY, RW, H: Integer;
|
||||
procedure TPanafallPanel.PositionPanZoomBar(X0, BottomY, RW, H: Integer;
|
||||
AVisible: Boolean);
|
||||
var BtnW, Gap, StripW, X, NBtns: Integer; WantAdd: Boolean;
|
||||
var BtnW, Gap, StripW, X, NBtns: Integer; WantAdd, WantGrid: Boolean;
|
||||
begin
|
||||
if (FPbPanZoom = nil) or (FBtnZoomIn = nil) then Exit;
|
||||
// «⊞» добавления пана живёт в этом же ряду; хозяин включает его флагом
|
||||
// AddPanEnabled (Visible кнопки управляется здесь вместе с рядом).
|
||||
// «⊞» добавления пана и «▦/▤» грид-тумблер живут в этом же ряду; хозяин
|
||||
// включает их флагами AddPanEnabled/GridBtnEnabled (Visible — здесь).
|
||||
WantAdd := FAddPanWanted and (FBtnAddPan <> nil);
|
||||
WantGrid := FGridBtnWanted and (FBtnGrid <> nil);
|
||||
FPbPanZoom.Visible := AVisible;
|
||||
FBtnZoomIn.Visible := AVisible;
|
||||
FBtnZoomDef.Visible := AVisible;
|
||||
FBtnZoomOut.Visible := AVisible;
|
||||
if FBtnAddPan <> nil then FBtnAddPan.Visible := AVisible and WantAdd;
|
||||
if FBtnGrid <> nil then FBtnGrid.Visible := AVisible and WantGrid;
|
||||
if not AVisible then Exit;
|
||||
Gap := MulDiv(2, Screen.PixelsPerInch, 96);
|
||||
BtnW := H; // квадратные кнопки в высоту строки
|
||||
NBtns := 3;
|
||||
if WantAdd then NBtns := 4;
|
||||
if WantAdd then Inc(NBtns);
|
||||
if WantGrid then Inc(NBtns);
|
||||
StripW := RW - NBtns * BtnW - (NBtns + 1) * Gap;
|
||||
if StripW < 20 then StripW := 20;
|
||||
FPbPanZoom.SetBounds(0, BottomY, StripW, H);
|
||||
X := StripW + Gap;
|
||||
FPbPanZoom.SetBounds(X0, BottomY, StripW, H);
|
||||
X := X0 + StripW + Gap;
|
||||
FBtnZoomOut.SetBounds(X, BottomY, BtnW, H); Inc(X, BtnW + Gap);
|
||||
FBtnZoomDef.SetBounds(X, BottomY, BtnW, H); Inc(X, BtnW + Gap);
|
||||
FBtnZoomIn.SetBounds(X, BottomY, BtnW, H);
|
||||
if WantGrid then
|
||||
begin
|
||||
Inc(X, BtnW + Gap);
|
||||
FBtnGrid.SetBounds(X, BottomY, BtnW, H);
|
||||
end;
|
||||
if WantAdd then
|
||||
begin
|
||||
Inc(X, BtnW + Gap);
|
||||
@@ -571,6 +608,31 @@ begin
|
||||
FPbPanZoom.Invalidate;
|
||||
end;
|
||||
|
||||
function TPanafallPanel.MinLayoutHeight(ATopOffset: Integer;
|
||||
AShowSpectrum, AShowWaterfall: Boolean): Integer;
|
||||
const
|
||||
SPLITTER_H = 5;
|
||||
MIN_SH = 60; // = Layout
|
||||
MIN_WH = 40; // = Layout
|
||||
var
|
||||
RULER_H, PANZOOM_H, HEADER_H: Integer;
|
||||
begin
|
||||
RULER_H := MulDiv(18, Screen.PixelsPerInch, 96);
|
||||
PANZOOM_H := MulDiv(22, Screen.PixelsPerInch, 96);
|
||||
HEADER_H := MulDiv(20, Screen.PixelsPerInch, 96);
|
||||
Result := ATopOffset;
|
||||
if FHeaderVisible then Inc(Result, HEADER_H);
|
||||
if (AShowSpectrum or AShowWaterfall) and FShowZoomRow then
|
||||
Inc(Result, PANZOOM_H);
|
||||
if AShowSpectrum then Inc(Result, MIN_SH + RULER_H);
|
||||
if AShowWaterfall then
|
||||
begin
|
||||
Inc(Result, MIN_WH);
|
||||
if AShowSpectrum then Inc(Result, SPLITTER_H)
|
||||
else Inc(Result, RULER_H); // водопад-only тоже рисует линейку
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.Layout(ATopOffset: Integer;
|
||||
AShowSpectrum, AShowWaterfall: Boolean);
|
||||
const
|
||||
@@ -578,20 +640,29 @@ const
|
||||
MIN_SH = 60; // минимальная высота спектра
|
||||
MIN_WH = 40; // минимальная высота водопада
|
||||
var
|
||||
RW, RH, SH, WH, TopOff: Integer;
|
||||
RW, RH, SH, WH, TopOff, X0: Integer;
|
||||
AvailH: Integer;
|
||||
RULER_H: Integer;
|
||||
PANZOOM_H: Integer;
|
||||
HEADER_H: Integer;
|
||||
ShowPZ: Boolean;
|
||||
EffRatio: Double;
|
||||
begin
|
||||
if (FParent = nil) or (FPbSpectrum = nil) then Exit;
|
||||
FShowSpectrum := AShowSpectrum;
|
||||
FShowWaterfall := AShowWaterfall;
|
||||
FSplitAvailH := 0; // валидно только в режиме «спектр+водопад» (ниже)
|
||||
RULER_H := MulDiv(18, Screen.PixelsPerInch, 96);
|
||||
// Регион пана — прямоугольник [X0..X0+RW) × [FStackTop..RH): грид-раскладка
|
||||
// ставит паны колонками; 0/0 по каждой оси = весь родитель.
|
||||
X0 := FStackLeft;
|
||||
if FStackWidth > 0 then
|
||||
RW := FStackWidth
|
||||
else
|
||||
begin
|
||||
X0 := 0;
|
||||
RW := FParent.ClientWidth;
|
||||
// Регион пана в стеке: [FStackTop .. RH). 0/0 = весь родитель (один пан).
|
||||
end;
|
||||
if FStackHeight > 0 then
|
||||
RH := FStackTop + FStackHeight
|
||||
else
|
||||
@@ -603,14 +674,14 @@ begin
|
||||
FHeaderPanel.Visible := FHeaderVisible;
|
||||
if FHeaderVisible then
|
||||
begin
|
||||
FHeaderPanel.SetBounds(0, FStackTop, RW, HEADER_H);
|
||||
FHeaderPanel.SetBounds(X0, FStackTop, RW, HEADER_H);
|
||||
PositionHeaderChildren(RW, HEADER_H);
|
||||
end;
|
||||
end;
|
||||
// Резервируем низ под панель пана/зума (видна, если виден спектр или водопад).
|
||||
PANZOOM_H := MulDiv(22, Screen.PixelsPerInch, 96);
|
||||
ShowPZ := (AShowSpectrum or AShowWaterfall) and FShowZoomRow;
|
||||
PositionPanZoomBar(RH - PANZOOM_H, RW, PANZOOM_H, ShowPZ);
|
||||
PositionPanZoomBar(X0, RH - PANZOOM_H, RW, PANZOOM_H, ShowPZ);
|
||||
if ShowPZ then RH := RH - PANZOOM_H;
|
||||
TopOff := ATopOffset + FStackTop;
|
||||
if FHeaderVisible then Inc(TopOff, HEADER_H);
|
||||
@@ -634,9 +705,9 @@ begin
|
||||
AvailH := RH - TopOff - RULER_H;
|
||||
if AvailH < MIN_SH then AvailH := MIN_SH;
|
||||
SH := AvailH;
|
||||
FPbSpectrum.SetBounds(0, TopOff, RW, SH);
|
||||
FPbSpectrum.SetBounds(X0, TopOff, RW, SH);
|
||||
FPbSpectrum.Visible := True;
|
||||
FPbRuler.SetBounds(0, TopOff + SH, RW, RULER_H);
|
||||
FPbRuler.SetBounds(X0, TopOff + SH, RW, RULER_H);
|
||||
FPbRuler.Visible := True;
|
||||
FSplitter.Visible := False;
|
||||
FPbWaterfall.Visible := False;
|
||||
@@ -663,9 +734,9 @@ begin
|
||||
WH := AvailH;
|
||||
FPbSpectrum.Visible := False;
|
||||
FSplitter.Visible := False;
|
||||
FPbWaterfall.SetBounds(0, TopOff, RW, WH);
|
||||
FPbWaterfall.SetBounds(X0, TopOff, RW, WH);
|
||||
FPbWaterfall.Visible := True;
|
||||
FPbRuler.SetBounds(0, TopOff + WH, RW, RULER_H);
|
||||
FPbRuler.SetBounds(X0, TopOff + WH, RW, RULER_H);
|
||||
FPbRuler.Visible := True;
|
||||
FSpectrumWidth := RW;
|
||||
FSpectrumHeight := 0;
|
||||
@@ -694,24 +765,29 @@ begin
|
||||
FSplitTopOff := TopOff;
|
||||
FSplitAvailH := AvailH;
|
||||
|
||||
// Ограничиваем соотношение, чтобы каждая зона имела минимальный размер
|
||||
if FSplitterRatio < MIN_SH / AvailH then
|
||||
FSplitterRatio := MIN_SH / AvailH;
|
||||
if FSplitterRatio > 1.0 - MIN_WH / AvailH then
|
||||
FSplitterRatio := 1.0 - MIN_WH / AvailH;
|
||||
// Ограничиваем соотношение, чтобы каждая зона имела минимальный размер.
|
||||
// Кламп — в ЛОКАЛЬНУЮ переменную: FSplitterRatio правит только юзер
|
||||
// (drag сплиттера). Иначе в маленьком регионе (стек панов) кламп
|
||||
// перезаписывал ratio (60/AvailH → ~0.7) и после закрытия панов
|
||||
// пропорция «50/50» не восстанавливалась.
|
||||
EffRatio := FSplitterRatio;
|
||||
if EffRatio < MIN_SH / AvailH then
|
||||
EffRatio := MIN_SH / AvailH;
|
||||
if EffRatio > 1.0 - MIN_WH / AvailH then
|
||||
EffRatio := 1.0 - MIN_WH / AvailH;
|
||||
|
||||
SH := Round(AvailH * FSplitterRatio);
|
||||
SH := Round(AvailH * EffRatio);
|
||||
WH := AvailH - SH;
|
||||
if WH < MIN_WH then WH := MIN_WH;
|
||||
|
||||
// Спектр
|
||||
FPbSpectrum.SetBounds(0, TopOff, RW, SH);
|
||||
FPbSpectrum.SetBounds(X0, TopOff, RW, SH);
|
||||
// Линейка частот — всегда прижата к низу спектра
|
||||
FPbRuler.SetBounds(0, TopOff + SH, RW, RULER_H);
|
||||
FPbRuler.SetBounds(X0, TopOff + SH, RW, RULER_H);
|
||||
// Сплиттер — между линейкой и водопадом
|
||||
FSplitter.SetBounds(0, TopOff + SH + RULER_H, RW, SPLITTER_H);
|
||||
FSplitter.SetBounds(X0, TopOff + SH + RULER_H, RW, SPLITTER_H);
|
||||
// Водопад — под сплиттером
|
||||
FPbWaterfall.SetBounds(0, TopOff + SH + RULER_H + SPLITTER_H, RW, WH);
|
||||
FPbWaterfall.SetBounds(X0, TopOff + SH + RULER_H + SPLITTER_H, RW, WH);
|
||||
|
||||
// Обновляем переменные размеров
|
||||
FSpectrumWidth := RW;
|
||||
|
||||
@@ -573,6 +573,77 @@ RATEPROBE удалены.** Наблюдение юзера: смена rate Г
|
||||
BtnZoomIn/Def/Out всех панов). Урок: PanFromSender работает только для
|
||||
контролов с Owner=панель (TLabel шапки — да, MakeFlatBtn-кнопки — нет).
|
||||
|
||||
**СТАТУС «грид-раскладка панов» (2026-07-12): РЕАЛИЗОВАНА, собирается, НА
|
||||
ЭКРАНЕ НЕ ПРОВЕРЕНА, НЕ закоммичена.**
|
||||
- Панель: регион пана обобщён до прямоугольника — `StackLeft/StackWidth`
|
||||
(0/0 = вся ширина), Layout смещает ВСЁ по X0 (шапка/спектр/линейка/
|
||||
сплиттер/водопад/ряд зума); `PositionPanZoomBar` получил X0 и 5-ю кнопку;
|
||||
новая кнопка `FBtnGrid` («▦»/«▤», глифом рулит хозяин) + `GridClick`
|
||||
(задать до Build) + `GridBtnEnabled`; ReparentTo переносит кнопку.
|
||||
- MainForm: `FPanGridMode` (сессия), `FPanGridRowShare` (доля верхнего ряда,
|
||||
0.15..0.85, дефолт 0.5), `FPanColShare[1..]` (доли колонок, init 1.0,
|
||||
нормируются суммой), `FPanVSplitters[1..]` (вертикальный сплиттер СЛЕВА от
|
||||
колонки пана i, crHSplit, создаётся в CreatePanadapter, free в
|
||||
ClosePanadapter). `PanGridActive` = режим включён И >=2 доп. панов в стеке
|
||||
(иначе автоткат в стек, режим помнится). LayoutPanStack: ветка грида — пан 0
|
||||
верхний ряд во всю ширину (wideband внутри как в стеке), паны N колонками;
|
||||
горизонтальный сплиттер ряда = сплиттер ПЕРВОЙ колонки во всю ширину
|
||||
(PanSplitMouseDown/Up ветвятся по PanGridActive: в гриде правят
|
||||
FPanGridRowShare напрямую), сплиттеры остальных колонок скрыты; вертикальные
|
||||
PanVSplitMouse* — зеркало горизонтальных (призрак, min 15% ширины,
|
||||
DShare=DeltaX/TotalW*ShareSum). Кнопка «▦/▤» видна при FPanGridMode или
|
||||
>=3 застекованных панах (выключить можно всегда). PopOutPan обнуляет
|
||||
StackLeft/Width и прячет V-сплиттер. Темизация: V-сплиттеры T.Border,
|
||||
BtnGrid через StyleButton.
|
||||
- Флаги/мышь/зум панов грид не трогает: всё в координатах канвы спектра,
|
||||
канва целиком переезжает в регион-колонку.
|
||||
- Проверить на экране: тумблер при 3 панах, оба типа сплиттеров, wideband в
|
||||
гриде, pop-out/возврат из грида, закрытие пана в гриде (автоткат),
|
||||
узкие колонки (зум/клик-тюн/drag), отсутствие регрессий при 1-2 панах.
|
||||
|
||||
**ФИКСЫ ПО ПЕРВОМУ ЭКРАННОМУ ТЕСТУ ГРИДА (2026-07-12, собирается, не
|
||||
проверено):** три независимых бага из фидбека юзера.
|
||||
1. **Наезд панов при выходе из грида (и вообще при 4 панах в стеке).**
|
||||
LayoutPanStack делил высоту по долям (после add/close все доли равные,
|
||||
при 4 панах = 25%), а Layout панели клампит спектр/водопад к минимумам
|
||||
(MIN_SH=60/MIN_WH=40) — виджеты вылезали за регион, следующий пан
|
||||
рисовался поверх (пан 0 терял водопад и ряд кнопок). Фикс:
|
||||
`TPanafallPanel.MinLayoutHeight` (те же константы/DPI, что Layout) +
|
||||
`PanMinHeight(PanId)` в MainForm (пан 0 += wideband `FPanWideH`);
|
||||
раздача в стеке клампит каждый пан минимумом и режет раздутые так,
|
||||
чтобы оставшимся хватило на их минимумы; в гриде Row0H клампится
|
||||
минимумом пана 0 и минимумом нижнего ряда (макс. по колонкам);
|
||||
драг межпанного сплиттера ограждён `Max(15%, минимум содержимого)`
|
||||
в обеих ветках (стек/грид).
|
||||
2. **«Блуждающие» кнопки после STOP (− − ⌂ +, кнопки на линейке, ⊞ накрыт).**
|
||||
MakeFlatBtn создаёт кнопки с Owner=КОНТЕЙНЕР (не панель) → при гибели
|
||||
пана (STOP → DestroyAllExtraPans → P.Free) кнопки −/⌂/+/⊞/▦ НЕ
|
||||
освобождались и оставались жить на PanelRight в старых координатах,
|
||||
а PanOfZoomButton, не найдя пан, роутил их клики на главный зум.
|
||||
Фикс: TPanafallPanel.Destroy явно освобождает FBtnZoomOut/Def/In,
|
||||
FBtnAddPan, FBtnGrid (FBtnPopPan/ClosePan — Owner=FHeaderPanel,
|
||||
умирают с ним). Перезапуск программы «лечил», потому что утечка —
|
||||
в пределах процесса.
|
||||
3. **Жор CPU (+~40% за пан).** SyncPanViews (тик, частота кадров) звал
|
||||
SyncPanZoomUI с БЕЗУСЛОВНЫМ `PbPanZoom.Invalidate` на каждый пан каждый
|
||||
кадр (регрессия cb6bdfa) = полный софт-рендер зум-бара + Qt-рекомпозит
|
||||
@~50fps × N панов. Фикс: инвалидация только внутри
|
||||
ZoomBar.SetFreq/SetState (они no-op при тех же значениях), событийный
|
||||
Invalidate перенесён в RefreshPanZoom. Дополнительно жор усиливался
|
||||
багом 1 (наезжающие GL-виджеты сверх окна). Замер юзера — после фикса.
|
||||
|
||||
Ответ на вопрос «почему только 3 доп. пана»: MAX_PANS=4 (WDSPEngine) —
|
||||
пан 0 + 3 доп.; 6 было у СЛАЙСОВ (MAX_SLICES=6 доп.). Поднять можно (P2
|
||||
позволяет много DDC), но сначала подтвердить CPU после фиксов.
|
||||
|
||||
Фиксы 1-3 ПОДТВЕРЖДЕНЫ юзером на экране 2026-07-12 («работает», CPU со
|
||||
всеми панами 17-20%). Добит хвост:
|
||||
4. **Пропорция спектр/водопад пана 0 не восстанавливалась после закрытия
|
||||
панов (50/50 → 70/30).** Layout клампил ratio минимумами С ЗАПИСЬЮ в
|
||||
FSplitterRatio: в маленьком регионе (стек панов) MIN_SH/AvailH ≈ 0.7
|
||||
запоминался навсегда. Фикс: кламп в локальную EffRatio, FSplitterRatio
|
||||
меняет только drag сплиттера юзером.
|
||||
|
||||
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
||||
|
||||
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
||||
|
||||
Reference in New Issue
Block a user