From eedaffad78a9ac3ac76a807dc5fc301cdafb3ea0 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 25 Aug 2026 08:46:08 +0300 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BD=D0=B0=20=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B5=20=D1=81=D0=BA=D1=80=D1=8B=D1=82=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20OC=20Control=20?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=B0=D0=BB=D0=B0=D1=81=D1=8C=20?= =?UTF-8?q?=D0=B4=D1=8B=D1=80=D0=B0=20=D0=B2=20=D1=81=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B5=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit У 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 Claude-Session: https://claude.ai/code/session_01M37eCTZZ9aqppKWkZRxXkP --- SettingsForm.pas | 131 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 35 deletions(-) diff --git a/SettingsForm.pas b/SettingsForm.pas index 5504c1a..a933b98 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -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;