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:
+104
-28
@@ -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 кнопки управляется здесь вместе с рядом).
|
||||
WantAdd := FAddPanWanted and (FBtnAddPan <> nil);
|
||||
// «⊞» добавления пана и «▦/▤» грид-тумблер живут в этом же ряду; хозяин
|
||||
// включает их флагами 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);
|
||||
RW := FParent.ClientWidth;
|
||||
// Регион пана в стеке: [FStackTop .. RH). 0/0 = весь родитель (один пан).
|
||||
// Регион пана — прямоугольник [X0..X0+RW) × [FStackTop..RH): грид-раскладка
|
||||
// ставит паны колонками; 0/0 по каждой оси = весь родитель.
|
||||
X0 := FStackLeft;
|
||||
if FStackWidth > 0 then
|
||||
RW := FStackWidth
|
||||
else
|
||||
begin
|
||||
X0 := 0;
|
||||
RW := FParent.ClientWidth;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user