fix(ui): на месте скрытой вкладки OC Control оставалась дыра в списке настроек

У Pluto/AD936x нет Open Collector выхода, и SetOCBackend гасил пункт «OC
Control» одним Visible. Но координаты левой колонки стояли прямо в вызовах
(MakeNavButton('OC Control', 404), следом 'QO-100', 436), поэтому соседи
оставались на своих местах, и между «Transverter» и «QO-100» зиял пустой
промежуток в 32 пикселя.

Убрана сама возможность такого расхождения: раскладку считает RelayoutNav
одним проходом по списку пунктов, пропуская скрытые, а call-site больше не
называет Top. Шаги вынесены в NAV_* рядом с прочими константами формы.

Заодно закрыт тот же дефект классом выше: заголовок секции, в которой не
осталось ни одной видимой кнопки, прячется вместе с ней. Сегодня это не
проявляется (OC Control — единственный прячущийся пункт, а в секции Radio есть
и другие), но следующий такой пункт уже не оставит висеть пустую шапку.

При всех видимых пунктах новые координаты совпадают со старыми до пикселя —
на openHPSDR раскладка не изменилась.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M37eCTZZ9aqppKWkZRxXkP
This commit is contained in:
2026-08-25 08:46:08 +03:00
co-authored by Claude Opus 5
parent 2a16304b91
commit eedaffad78
+96 -35
View File
@@ -119,6 +119,10 @@ type
private
// ---- Layout ----
FNavPanel: TPanel;
// Пункты левой колонки в порядке сверху вниз — заголовки секций (TLabel)
// и кнопки (TFlatButton) вперемешку. Координаты им раздаёт RelayoutNav,
// руками Top не ставим: пункт умеет прятаться (OC Control на Pluto).
FNavItems: array of TControl;
FContentPanel: TPanel;
FFooterPanel: TPanel;
FScrollHost: TPanel;
@@ -608,8 +612,10 @@ type
function MakeScrollPage: TScrollBox;
procedure AddPageBottomSpace(APage: TScrollBox);
procedure UpdatePageBottomSpace(APage: TScrollBox);
procedure MakeNavSection(const Cap: string; ATop: Integer);
function MakeNavButton(const Cap: string; ATop: Integer): TFlatButton;
procedure AddNavItem(AItem: TControl);
procedure MakeNavSection(const Cap: string);
function MakeNavButton(const Cap: string): TFlatButton;
procedure RelayoutNav;
procedure OnFFTSizeChange(Sender: TObject);
procedure OnWindowChange(Sender: TObject);
@@ -793,6 +799,14 @@ const
SETTINGS_FOOTER_H = 52;
SETTINGS_BOTTOM_SPACE = 24;
// Левая колонка навигации: раскладку считает RelayoutNav, а не call-site —
// пункт умеет прятаться, и захардкоженные Top оставляли на его месте дыру.
NAV_Y0 = 68; // верх первого заголовка секции
NAV_BTN_H = 30; // высота кнопки пункта
NAV_STEP = 32; // шаг между кнопками (высота + зазор)
NAV_SECT_H = 18; // высота заголовка секции
NAV_SECT_GAP = 8; // отступ перед заголовком следующей секции
// Первый пункт комбо аудио слайса = «как у приложения» (пустое имя в конфиге).
SLICE_DEV_DEFAULT = 'Default (app output)';
SLICE_DEV_NONE = 'None (app input)';
@@ -1063,27 +1077,34 @@ begin
DpiScale(SETTINGS_BOTTOM_SPACE));
end;
procedure TSettingsForm.MakeNavSection(const Cap: string; ATop: Integer);
procedure TSettingsForm.AddNavItem(AItem: TControl);
begin
with TLabel.Create(Self) do
begin
Parent := FNavPanel;
Caption := UpperCase(Cap);
AutoSize := False;
SetBounds(DpiScale(14), DpiScale(ATop), DpiScale(160), DpiScale(18));
Layout := tlCenter;
Font.Size := 8;
Font.Style := [fsBold];
Font.Color := CLR_TEXTDIM;
end;
SetLength(FNavItems, Length(FNavItems) + 1);
FNavItems[High(FNavItems)] := AItem;
end;
function TSettingsForm.MakeNavButton(const Cap: string; ATop: Integer): TFlatButton;
procedure TSettingsForm.MakeNavSection(const Cap: string);
var
L: TLabel;
begin
L := TLabel.Create(Self);
L.Parent := FNavPanel;
L.Caption := UpperCase(Cap);
L.AutoSize := False;
L.SetBounds(DpiScale(14), 0, DpiScale(160), DpiScale(NAV_SECT_H));
L.Layout := tlCenter;
L.Font.Size := 8;
L.Font.Style := [fsBold];
L.Font.Color := CLR_TEXTDIM;
AddNavItem(L);
end;
function TSettingsForm.MakeNavButton(const Cap: string): TFlatButton;
begin
Result := TFlatButton.Create(Self);
Result.Parent := FNavPanel;
Result.Caption := Cap;
Result.SetBounds(DpiScale(10), DpiScale(ATop), DpiScale(166), DpiScale(30));
Result.SetBounds(DpiScale(10), 0, DpiScale(166), DpiScale(NAV_BTN_H));
Result.Anchors := [akLeft, akTop, akRight];
Result.OnClick := NavClick;
Result.ClrNorm := CLR_PANEL;
@@ -1094,6 +1115,39 @@ begin
Result.ClrTextAct := CLR_ACCENT;
Result.Font.Size := 9;
Result.Font.Style := [];
AddNavItem(Result);
end;
procedure TSettingsForm.RelayoutNav;
// Раскладывает левую колонку сверху вниз, пропуская скрытые пункты: иначе на
// месте спрятанной кнопки (OC Control у Pluto) остаётся дыра в списке.
// Заголовок секции живёт ровно столько, сколько в ней есть видимая кнопка.
var
I, J, Y: Integer;
HasVisible: Boolean;
begin
Y := NAV_Y0;
for I := 0 to High(FNavItems) do
if FNavItems[I] is TLabel then
begin
// Пустая секция заголовка не заслуживает.
HasVisible := False;
for J := I + 1 to High(FNavItems) do
begin
if FNavItems[J] is TLabel then Break;
if FNavItems[J].Visible then begin HasVisible := True; Break; end;
end;
FNavItems[I].Visible := HasVisible;
if not HasVisible then Continue;
if Y > NAV_Y0 then Inc(Y, NAV_SECT_GAP);
FNavItems[I].Top := DpiScale(Y);
Inc(Y, NAV_SECT_H + 2);
end
else if FNavItems[I].Visible then
begin
FNavItems[I].Top := DpiScale(Y);
Inc(Y, NAV_STEP);
end;
end;
// ---------------------------------------------------------------------------
@@ -1132,26 +1186,28 @@ begin
Font.Color := CLR_TEXTDIM;
end;
MakeNavSection('General', 68);
FNavAudio := MakeNavButton('Audio', 88);
FNavDisplay := MakeNavButton('Display', 120);
FNavSpectrum := MakeNavButton('Spectrum', 152);
FNavWaterfall := MakeNavButton('Waterfall', 184);
MakeNavSection('General');
FNavAudio := MakeNavButton('Audio');
FNavDisplay := MakeNavButton('Display');
FNavSpectrum := MakeNavButton('Spectrum');
FNavWaterfall := MakeNavButton('Waterfall');
MakeNavSection('Radio', 224);
FNavTransmit := MakeNavButton('Transmit', 244);
FNavPA := MakeNavButton('Power Amplifier', 276);
FNavCalib := MakeNavButton('Calibration', 308);
FNavAlex := MakeNavButton('Antenna / Alex', 340);
FNavXvtr := MakeNavButton('Transverter', 372);
FNavOC := MakeNavButton('OC Control', 404);
FNavQO := MakeNavButton('QO-100', 436);
MakeNavSection('Radio');
FNavTransmit := MakeNavButton('Transmit');
FNavPA := MakeNavButton('Power Amplifier');
FNavCalib := MakeNavButton('Calibration');
FNavAlex := MakeNavButton('Antenna / Alex');
FNavXvtr := MakeNavButton('Transverter');
FNavOC := MakeNavButton('OC Control');
FNavQO := MakeNavButton('QO-100');
MakeNavSection('Integration', 476);
FNavCAT := MakeNavButton('CAT', 496);
FNavSlices := MakeNavButton('Slices', 528);
FNavAdvanced := MakeNavButton('Advanced', 560);
FNavDXCluster := MakeNavButton('DX Cluster', 592);
MakeNavSection('Integration');
FNavCAT := MakeNavButton('CAT');
FNavSlices := MakeNavButton('Slices');
FNavAdvanced := MakeNavButton('Advanced');
FNavDXCluster := MakeNavButton('DX Cluster');
RelayoutNav;
FContentPanel := TPanel.Create(Self);
FContentPanel.Parent := Self;
@@ -6058,8 +6114,13 @@ procedure TSettingsForm.SetOCBackend(IsPluto: Boolean);
// У Pluto/AD936x нет Open Collector выхода — вкладка целиком скрывается,
// а не показывает пустое содержимое (в отличие от Antenna, где Pluto имеет
// свою таблицу разъёмов).
var
Changed: Boolean;
begin
FNavOC.Visible := not IsPluto;
Changed := FNavOC.Visible <> (not IsPluto);
FNavOC.Visible := not IsPluto;
// Пункты ниже обязаны подтянуться — иначе на месте OC Control остаётся дыра.
if Changed then RelayoutNav;
if IsPluto and (FPageOC.Visible) then
SelectPage(FPageAudio, FNavAudio);
end;