mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(slices): пан-зум панов N — свой бар/кнопки, span-clip анализатора пана
Движок: TPanChan.ZoomFactor/PanSlider, span-clip в ApplyPanAnalyzerSettings (математика главного, ZOOM_LIMIT 100), SetPanZoomPan с no-op guard (SetAnalyzer сбрасывает FFT-историю). Зум переживает смену rate/ADC пана. Контроллер: формула окна вынесена в ViewWindowFor (главный GetViewWindow тоже через неё), GetPanViewWindow (PanId=0 делегирует), PanZoomIn/Out по лестнице кратностей с якорем на центре окна, PanZoomDefault, геттеры. UI: TPanZoomEvent получил Sender; у панов N включён ряд зума, бар диспетчеризуется по объекту, кнопки −/⌂/+ общие через PanOfZoomButton (матч по идентичности — MakeFlatBtn создаёт кнопки с Owner=контейнер, PanFromSender их не видит); SyncPanZoomUI/RefreshPanZoom, вся мышь панов зум-корректна через GetPanViewWindow. Темизация баров панов N. Смоук panzoomtest ALL PASS (пик тона совпадает с формулой окна ±0.6%), подтверждено на экране. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+98
-14
@@ -481,7 +481,8 @@ type
|
|||||||
procedure DoSpectrumDrag(PixelX: Integer; PanelWidth: Integer);
|
procedure DoSpectrumDrag(PixelX: Integer; PanelWidth: Integer);
|
||||||
procedure DoWidebandClick(PixelX: Integer; PanelWidth: Integer);
|
procedure DoWidebandClick(PixelX: Integer; PanelWidth: Integer);
|
||||||
// ---- Пан/зум (мышь полосы/сплиттера — внутри TPanafallPanel) ----
|
// ---- Пан/зум (мышь полосы/сплиттера — внутри TPanafallPanel) ----
|
||||||
procedure OnPanZoomWindow(AZoom, APan: Double);
|
function PanOfZoomButton(Sender: TObject): TPanafallPanel;
|
||||||
|
procedure OnPanZoomWindow(Sender: TObject; AZoom, APan: Double);
|
||||||
procedure BtnZoomInClick(Sender: TObject);
|
procedure BtnZoomInClick(Sender: TObject);
|
||||||
procedure BtnZoomDefClick(Sender: TObject);
|
procedure BtnZoomDefClick(Sender: TObject);
|
||||||
procedure BtnZoomOutClick(Sender: TObject);
|
procedure BtnZoomOutClick(Sender: TObject);
|
||||||
@@ -590,6 +591,8 @@ type
|
|||||||
procedure AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
procedure AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
||||||
procedure PanClickTune(P: TPanafallPanel; X, W: Integer); // клик по фону пана N
|
procedure PanClickTune(P: TPanafallPanel; X, W: Integer); // клик по фону пана N
|
||||||
procedure PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
|
procedure PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
|
||||||
|
procedure SyncPanZoomUI(P: TPanafallPanel); // вьюха+зум-бар ← контроллер
|
||||||
|
procedure RefreshPanZoom(P: TPanafallPanel); // + флаги/инвалидции
|
||||||
procedure SyncPanViews; // центр/span вьюх панов
|
procedure SyncPanViews; // центр/span вьюх панов
|
||||||
procedure MarkPanDirty(P: TPanafallPanel); // репейнт пана N
|
procedure MarkPanDirty(P: TPanafallPanel); // репейнт пана N
|
||||||
// Мышь панов N>0 (у пана 0 семантика ниже, PbSpectrum*).
|
// Мышь панов N>0 (у пана 0 семантика ниже, PbSpectrum*).
|
||||||
@@ -2706,6 +2709,9 @@ begin
|
|||||||
FPans[i].SetFlagsTheme(T);
|
FPans[i].SetFlagsTheme(T);
|
||||||
if FPanSplitters[i] <> nil then FPanSplitters[i].Color := T.Border;
|
if FPanSplitters[i] <> nil then FPanSplitters[i].Color := T.Border;
|
||||||
if FPanFloatForm[i] <> nil then FPanFloatForm[i].Color := T.BG;
|
if FPanFloatForm[i] <> nil then FPanFloatForm[i].Color := T.BG;
|
||||||
|
// Зум-бар пана N (пан 0 — через алиас FPanZoomBar ниже).
|
||||||
|
if FPans[i].ZoomBar <> nil then FPans[i].ZoomBar.SetTheme(T);
|
||||||
|
if FPans[i].PbPanZoom <> nil then FPans[i].PbPanZoom.Color := T.SliderBG;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if FPanZoomBar <> nil then
|
if FPanZoomBar <> nil then
|
||||||
@@ -4338,25 +4344,74 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnPanZoomWindow(AZoom, APan: Double);
|
procedure TMainForm.OnPanZoomWindow(Sender: TObject; AZoom, APan: Double);
|
||||||
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
|
// Чей бар? Паны N — свой зум (пан-локальный, без band-персиста);
|
||||||
|
// иначе пан 0 — контроллерный зум (rfZoom обновит UI).
|
||||||
|
for i := 1 to MAX_PANS - 1 do
|
||||||
|
if (FPans[i] <> nil) and (TObject(FPans[i].ZoomBar) = Sender) then
|
||||||
|
begin
|
||||||
|
FController.SetPanZoomPan(i, AZoom, APan);
|
||||||
|
RefreshPanZoom(FPans[i]);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
// Окно-селектор задаёт и зум, и пан напрямую (без якоря на VFO).
|
// Окно-селектор задаёт и зум, и пан напрямую (без якоря на VFO).
|
||||||
FController.ApplyZoom(AZoom, APan);
|
FController.ApplyZoom(AZoom, APan);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.BtnZoomInClick(Sender: TObject);
|
function TMainForm.PanOfZoomButton(Sender: TObject): TPanafallPanel;
|
||||||
|
// Зум-кнопки создаются MakeFlatBtn с Owner = контейнер (PanelRight/окно), а не
|
||||||
|
// панель — PanFromSender их НЕ находит (клик уходил в зум главного пана).
|
||||||
|
// Матчим по идентичности контролов.
|
||||||
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
FController.ZoomIn;
|
Result := nil;
|
||||||
|
for i := 0 to MAX_PANS - 1 do
|
||||||
|
if FPans[i] <> nil then
|
||||||
|
if (Sender = TObject(FPans[i].BtnZoomIn)) or
|
||||||
|
(Sender = TObject(FPans[i].BtnZoomDef)) or
|
||||||
|
(Sender = TObject(FPans[i].BtnZoomOut)) then
|
||||||
|
Exit(FPans[i]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.BtnZoomInClick(Sender: TObject);
|
||||||
|
var P: TPanafallPanel;
|
||||||
|
begin
|
||||||
|
P := PanOfZoomButton(Sender);
|
||||||
|
if (P <> nil) and (P.PanId > 0) then
|
||||||
|
begin
|
||||||
|
FController.PanZoomIn(P.PanId);
|
||||||
|
RefreshPanZoom(P);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FController.ZoomIn;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.BtnZoomDefClick(Sender: TObject);
|
procedure TMainForm.BtnZoomDefClick(Sender: TObject);
|
||||||
|
var P: TPanafallPanel;
|
||||||
begin
|
begin
|
||||||
FController.ZoomDefault;
|
P := PanOfZoomButton(Sender);
|
||||||
|
if (P <> nil) and (P.PanId > 0) then
|
||||||
|
begin
|
||||||
|
FController.PanZoomDefault(P.PanId);
|
||||||
|
RefreshPanZoom(P);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FController.ZoomDefault;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.BtnZoomOutClick(Sender: TObject);
|
procedure TMainForm.BtnZoomOutClick(Sender: TObject);
|
||||||
|
var P: TPanafallPanel;
|
||||||
begin
|
begin
|
||||||
FController.ZoomOut;
|
P := PanOfZoomButton(Sender);
|
||||||
|
if (P <> nil) and (P.PanId > 0) then
|
||||||
|
begin
|
||||||
|
FController.PanZoomOut(P.PanId);
|
||||||
|
RefreshPanZoom(P);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FController.ZoomOut;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Спектр
|
// Спектр
|
||||||
@@ -5298,13 +5353,37 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
|
procedure TMainForm.PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
|
||||||
// Видимое окно доп. пана: центр DDC ± rate/2 (пан-зума пока нет).
|
// Видимое окно доп. пана (с учётом его пан-зума).
|
||||||
begin
|
begin
|
||||||
VC := FController.PanDDCFreq(P.PanId);
|
FController.GetPanViewWindow(P.PanId, VC, VS);
|
||||||
VS := FController.PanDDCRateKHz(P.PanId) * 1000.0;
|
|
||||||
if VS <= 0 then VS := 384000;
|
if VS <= 0 then VS := 384000;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.SyncPanZoomUI(P: TPanafallPanel);
|
||||||
|
// Вьюха и зум-бар пана ← контроллер (после смены зума/центра/rate пана).
|
||||||
|
var VC, VS: Double;
|
||||||
|
begin
|
||||||
|
if (P = nil) or (P.PanId = 0) then Exit;
|
||||||
|
FController.GetPanViewWindow(P.PanId, VC, VS);
|
||||||
|
P.View.CenterFreq := VC;
|
||||||
|
P.View.SpanHz := VS;
|
||||||
|
P.ZoomBar.SetFreq(FController.PanDDCFreq(P.PanId),
|
||||||
|
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;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.RefreshPanZoom(P: TPanafallPanel);
|
||||||
|
// Полное обновление пана после смены зума: окно вьюхи, бар, флаги, спектр.
|
||||||
|
begin
|
||||||
|
if P = nil then Exit;
|
||||||
|
SyncPanZoomUI(P);
|
||||||
|
P.LayoutFlags;
|
||||||
|
MarkPanDirty(P);
|
||||||
|
if P.PbWaterfall <> nil then P.PbWaterfall.Invalidate;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
procedure TMainForm.AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
||||||
// Слайс в доп. пане (режим/полоса копируются с главного, звуковуха default).
|
// Слайс в доп. пане (режим/полоса копируются с главного, звуковуха default).
|
||||||
var Lo, Hi, Id: Integer;
|
var Lo, Hi, Id: Integer;
|
||||||
@@ -5624,7 +5703,10 @@ begin
|
|||||||
P := TPanafallPanel.Create(nil, FUseOpenGLSpectrum);
|
P := TPanafallPanel.Create(nil, FUseOpenGLSpectrum);
|
||||||
P.PanId := PanId;
|
P.PanId := PanId;
|
||||||
P.Controller := FController;
|
P.Controller := FController;
|
||||||
P.ShowZoomRow := False; // пан-зум доп. панов — этап 3.3+
|
P.ShowZoomRow := True; // пан-зум пана: свой бар/кнопки (диспетчер по Sender)
|
||||||
|
P.ZoomInClick := BtnZoomInClick; // до Build — кнопки создаются в Build
|
||||||
|
P.ZoomDefClick := BtnZoomDefClick;
|
||||||
|
P.ZoomOutClick := BtnZoomOutClick;
|
||||||
P.OnWireFlag := WireOverlayEvents;
|
P.OnWireFlag := WireOverlayEvents;
|
||||||
P.OnSliceClosed := OnPanSliceClosed;
|
P.OnSliceClosed := OnPanSliceClosed;
|
||||||
P.OnFlagsInvalidate := OnVfoOverlayInvalidate;
|
P.OnFlagsInvalidate := OnVfoOverlayInvalidate;
|
||||||
@@ -5643,6 +5725,9 @@ begin
|
|||||||
P.WaterfallMouseMove := PanNSpectrumMouseMove;
|
P.WaterfallMouseMove := PanNSpectrumMouseMove;
|
||||||
P.WaterfallMouseUp := PanNSpectrumMouseUp;
|
P.WaterfallMouseUp := PanNSpectrumMouseUp;
|
||||||
P.Build(PanelRight, Max(1, FController.FSpecMSAA));
|
P.Build(PanelRight, Max(1, FController.FSpecMSAA));
|
||||||
|
P.ZoomBar.OnZoomPan := OnPanZoomWindow; // drag окна-селектора
|
||||||
|
P.ZoomBar.SetFreq(Freq, FController.PanDDCRateKHz(PanId) * 1000.0);
|
||||||
|
P.ZoomBar.SetState(0.0, 0.5);
|
||||||
|
|
||||||
// Параметры отображения — с главного view (пан-персональные — позже).
|
// Параметры отображения — с главного view (пан-персональные — позже).
|
||||||
P.View.WfAGCEnabled := FController.FWfAGCEnabled;
|
P.View.WfAGCEnabled := FController.FWfAGCEnabled;
|
||||||
@@ -5753,9 +5838,8 @@ begin
|
|||||||
begin
|
begin
|
||||||
P := FPans[i];
|
P := FPans[i];
|
||||||
if P = nil then Continue;
|
if P = nil then Continue;
|
||||||
P.View.CenterFreq := FController.PanDDCFreq(i);
|
SyncPanZoomUI(P); // центр/span вьюхи (с пан-зумом) + зум-бар
|
||||||
P.View.SpanHz := FController.PanDDCRateKHz(i) * 1000.0;
|
P.View.WDSPReady := FController.FWDSPReady;
|
||||||
P.View.WDSPReady := FController.FWDSPReady;
|
|
||||||
end;
|
end;
|
||||||
// Шапки следуют за частотами (Caption-сеттер — no-op при том же тексте).
|
// Шапки следуют за частотами (Caption-сеттер — no-op при том же тексте).
|
||||||
if PanCount > 1 then UpdatePanHeaders;
|
if PanCount > 1 then UpdatePanHeaders;
|
||||||
@@ -5891,7 +5975,7 @@ begin
|
|||||||
NewHz := Max(FController.BackendCaps.MinFreqHz,
|
NewHz := Max(FController.BackendCaps.MinFreqHz,
|
||||||
Min(FController.BackendCaps.MaxFreqHz, NewHz));
|
Min(FController.BackendCaps.MaxFreqHz, NewHz));
|
||||||
FController.SetPanDDCFreq(P.PanId, NewHz);
|
FController.SetPanDDCFreq(P.PanId, NewHz);
|
||||||
P.View.CenterFreq := NewHz;
|
SyncPanZoomUI(P); // окно вьюхи с учётом зума + freq зум-бара
|
||||||
P.LayoutFlags;
|
P.LayoutFlags;
|
||||||
UpdatePanHeaders;
|
UpdatePanHeaders;
|
||||||
MarkPanDirty(P);
|
MarkPanDirty(P);
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ uses
|
|||||||
LCLIntf, LCLType, IntfGraphics, fpImage, AppTheme, PlatformUtils;
|
LCLIntf, LCLType, IntfGraphics, fpImage, AppTheme, PlatformUtils;
|
||||||
|
|
||||||
type
|
type
|
||||||
TPanZoomEvent = procedure(AZoom, APan: Double) of object;
|
TPanZoomEvent = procedure(Sender: TObject; AZoom, APan: Double) of object;
|
||||||
|
|
||||||
TDragMode = (dmNone, dmBody, dmLeft, dmRight);
|
TDragMode = (dmNone, dmBody, dmLeft, dmRight);
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ begin
|
|||||||
// Локально обновляем сразу — драг плавный даже до round-trip контроллера.
|
// Локально обновляем сразу — драг плавный даже до round-trip контроллера.
|
||||||
FZoom := Z; FPan := P;
|
FZoom := Z; FPan := P;
|
||||||
if Assigned(FPb) then FPb.Invalidate;
|
if Assigned(FPb) then FPb.Invalidate;
|
||||||
if Assigned(FOnZoomPan) then FOnZoomPan(Z, P);
|
if Assigned(FOnZoomPan) then FOnZoomPan(Self, Z, P);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPanZoomBar.DrawRulerInto(W, H: Integer);
|
procedure TPanZoomBar.DrawRulerInto(W, H: Integer);
|
||||||
|
|||||||
+2
-7
@@ -512,13 +512,8 @@ procedure TPanafallPanel.ViewWindow(out VC, VS: Double);
|
|||||||
begin
|
begin
|
||||||
VC := 0; VS := 0;
|
VC := 0; VS := 0;
|
||||||
if FController = nil then Exit;
|
if FController = nil then Exit;
|
||||||
if FPanId = 0 then
|
// Единая точка видимого окна: PanId=0 → главный зум, паны N → свой пан-зум.
|
||||||
FController.GetViewWindow(VC, VS)
|
FController.GetPanViewWindow(FPanId, VC, VS);
|
||||||
else
|
|
||||||
begin
|
|
||||||
VC := FController.PanDDCFreq(FPanId);
|
|
||||||
VS := FController.PanDDCRateKHz(FPanId) * 1000.0;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPanafallPanel.PositionHeaderChildren(RW, H: Integer);
|
procedure TPanafallPanel.PositionHeaderChildren(RW, H: Integer);
|
||||||
|
|||||||
+139
-10
@@ -167,6 +167,7 @@ type
|
|||||||
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
||||||
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
||||||
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
||||||
|
procedure PanZoomStepTo(PanId: Integer; NewZ: Double); // зум с якорем на центре окна
|
||||||
function PanCenterHz(PanId: Integer): Double; // центр пана (0 = FCenterFreq)
|
function PanCenterHz(PanId: Integer): Double; // центр пана (0 = FCenterFreq)
|
||||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||||
@@ -353,6 +354,8 @@ type
|
|||||||
FPanFreqHz: array[1..MAX_PANS-1] of Double;
|
FPanFreqHz: array[1..MAX_PANS-1] of Double;
|
||||||
FPanRateKHz: array[1..MAX_PANS-1] of Word;
|
FPanRateKHz: array[1..MAX_PANS-1] of Word;
|
||||||
FPanADC: array[1..MAX_PANS-1] of Byte; // ADC-источник пана (3.4)
|
FPanADC: array[1..MAX_PANS-1] of Byte; // ADC-источник пана (3.4)
|
||||||
|
FPanZoomF: array[1..MAX_PANS-1] of Double; // зум пана 0..1 (0 = весь span)
|
||||||
|
FPanPanSl: array[1..MAX_PANS-1] of Double; // положение окна пана 0..1
|
||||||
FMainADCSrc: Byte; // ADC главного DDC (пан 0)
|
FMainADCSrc: Byte; // ADC главного DDC (пан 0)
|
||||||
FDDCPanMap: array[0..MAX_DDCS-1] of Integer;
|
FDDCPanMap: array[0..MAX_DDCS-1] of Integer;
|
||||||
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
|
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
|
||||||
@@ -581,6 +584,15 @@ type
|
|||||||
// N>0 = его пан-DDC (SetPanDDC). ADC ∈ [0..Caps.NumADCs-1].
|
// N>0 = его пан-DDC (SetPanDDC). ADC ∈ [0..Caps.NumADCs-1].
|
||||||
function PanADC(PanId: Integer): Byte;
|
function PanADC(PanId: Integer): Byte;
|
||||||
function SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
function SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
||||||
|
// ---- Зум/пан доп. панов (PanId=0 делегирует главному зуму) ----
|
||||||
|
// Видимое окно пана — та же формула, что span-clip его анализатора.
|
||||||
|
procedure GetPanViewWindow(PanId: Integer; out ACenter, ASpan: Double);
|
||||||
|
procedure SetPanZoomPan(PanId: Integer; AZoom, APan: Double);
|
||||||
|
procedure PanZoomIn(PanId: Integer); // шаг по лестнице, якорь = центр окна
|
||||||
|
procedure PanZoomOut(PanId: Integer);
|
||||||
|
procedure PanZoomDefault(PanId: Integer);
|
||||||
|
function PanZoomFactor(PanId: Integer): Double;
|
||||||
|
function PanPanSlider(PanId: Integer): Double;
|
||||||
|
|
||||||
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
||||||
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
||||||
@@ -1189,6 +1201,8 @@ begin
|
|||||||
FPanFreqHz[i] := 0;
|
FPanFreqHz[i] := 0;
|
||||||
FPanRateKHz[i] := 0;
|
FPanRateKHz[i] := 0;
|
||||||
FPanADC[i] := 0;
|
FPanADC[i] := 0;
|
||||||
|
FPanZoomF[i] := 0.0;
|
||||||
|
FPanPanSl[i] := 0.5;
|
||||||
end;
|
end;
|
||||||
for i := 0 to MAX_DDCS - 1 do
|
for i := 0 to MAX_DDCS - 1 do
|
||||||
FDDCPanMap[i] := -1;
|
FDDCPanMap[i] := -1;
|
||||||
@@ -1235,6 +1249,8 @@ begin
|
|||||||
FPanFreqHz[PanId] := FreqHz;
|
FPanFreqHz[PanId] := FreqHz;
|
||||||
FPanRateKHz[PanId] := RateKHz;
|
FPanRateKHz[PanId] := RateKHz;
|
||||||
FPanADC[PanId] := ADCSource;
|
FPanADC[PanId] := ADCSource;
|
||||||
|
FPanZoomF[PanId] := 0.0;
|
||||||
|
FPanPanSl[PanId] := 0.5;
|
||||||
FDDCPanMap[ddc] := PanId;
|
FDDCPanMap[ddc] := PanId;
|
||||||
FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource);
|
FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource);
|
||||||
Result := True;
|
Result := True;
|
||||||
@@ -1282,6 +1298,8 @@ begin
|
|||||||
FPanFreqHz[PanId] := 0;
|
FPanFreqHz[PanId] := 0;
|
||||||
FPanRateKHz[PanId] := 0;
|
FPanRateKHz[PanId] := 0;
|
||||||
FPanADC[PanId] := 0;
|
FPanADC[PanId] := 0;
|
||||||
|
FPanZoomF[PanId] := 0.0;
|
||||||
|
FPanPanSl[PanId] := 0.5;
|
||||||
for i := 0 to MAX_SLICES - 1 do
|
for i := 0 to MAX_SLICES - 1 do
|
||||||
if FSlices[i].Active and (FSlices[i].PanId = PanId) then
|
if FSlices[i].Active and (FSlices[i].PanId = PanId) then
|
||||||
RemoveSlice(FSlices[i].Id);
|
RemoveSlice(FSlices[i].Id);
|
||||||
@@ -3041,29 +3059,35 @@ begin Result := FZoomFactor; end;
|
|||||||
function TRadioController.PanSlider: Double;
|
function TRadioController.PanSlider: Double;
|
||||||
begin Result := FPanSlider; end;
|
begin Result := FPanSlider; end;
|
||||||
|
|
||||||
procedure TRadioController.GetViewWindow(out ACenter: Double; out ASpan: Double);
|
procedure ViewWindowFor(BaseC, BaseS, AZoom, APan: Double;
|
||||||
|
out ACenter, ASpan: Double);
|
||||||
// Видимое окно (центр+span, Гц) с учётом зума. Формула совпадает с расчётом
|
// Видимое окно (центр+span, Гц) с учётом зума. Формула совпадает с расчётом
|
||||||
// span-clip в WDSPEngine.ApplyRXAnalyzerSettings (bw=FSpanHz, clp=0), поэтому
|
// span-clip в WDSPEngine.Apply{RX,Pan}AnalyzerSettings (bw=BaseS, clp=0),
|
||||||
// оверлеи точно ложатся на пиксели. Без зума → базовые центр/span.
|
// поэтому оверлеи точно ложатся на пиксели. Без зума → базовые центр/span.
|
||||||
var
|
var
|
||||||
FVis, OneMinusFVis, VisSpan, LowEdgeOff, CenterOff: Double;
|
FVis, OneMinusFVis, VisSpan, LowEdgeOff, CenterOff: Double;
|
||||||
begin
|
begin
|
||||||
if (FZoomFactor <= 0.0) or (FSpanHz <= 0.0) then
|
if (AZoom <= 0.0) or (BaseS <= 0.0) then
|
||||||
begin
|
begin
|
||||||
ACenter := FCenterFreq;
|
ACenter := BaseC;
|
||||||
ASpan := FSpanHz;
|
ASpan := BaseS;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
OneMinusFVis := 0.99 * Log10(9.0 * EnsureRange(FZoomFactor, 0.0, 1.0) + 1.0);
|
OneMinusFVis := 0.99 * Log10(9.0 * EnsureRange(AZoom, 0.0, 1.0) + 1.0);
|
||||||
FVis := 1.0 - OneMinusFVis; // доля span после зума
|
FVis := 1.0 - OneMinusFVis; // доля span после зума
|
||||||
if FVis < 1.0 / 100.0 then FVis := 1.0 / 100.0; // ZOOM_LIMIT
|
if FVis < 1.0 / 100.0 then FVis := 1.0 / 100.0; // ZOOM_LIMIT
|
||||||
VisSpan := FVis * FSpanHz;
|
VisSpan := FVis * BaseS;
|
||||||
LowEdgeOff := -FSpanHz / 2.0 + EnsureRange(FPanSlider, 0.0, 1.0) * OneMinusFVis * FSpanHz;
|
LowEdgeOff := -BaseS / 2.0 + EnsureRange(APan, 0.0, 1.0) * OneMinusFVis * BaseS;
|
||||||
CenterOff := LowEdgeOff + VisSpan / 2.0;
|
CenterOff := LowEdgeOff + VisSpan / 2.0;
|
||||||
ACenter := FCenterFreq + CenterOff;
|
ACenter := BaseC + CenterOff;
|
||||||
ASpan := VisSpan;
|
ASpan := VisSpan;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.GetViewWindow(out ACenter: Double; out ASpan: Double);
|
||||||
|
begin
|
||||||
|
ViewWindowFor(FCenterFreq, FSpanHz, FZoomFactor, FPanSlider, ACenter, ASpan);
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.AnchoredPan(AZoom: Double): Double;
|
function TRadioController.AnchoredPan(AZoom: Double): Double;
|
||||||
// Положение пана, при котором видимое окно центрировано на маркере активного
|
// Положение пана, при котором видимое окно центрировано на маркере активного
|
||||||
// VFO. Клампится в [0..1], чтобы окно не вылезало за span (у краёв маркер
|
// VFO. Клампится в [0..1], чтобы окно не вылезало за span (у краёв маркер
|
||||||
@@ -3156,6 +3180,111 @@ begin
|
|||||||
ApplyZoom(0.0, 0.5);
|
ApplyZoom(0.0, 0.5);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ ---- Зум/пан доп. панов (PanId=0 делегирует главному) ---- }
|
||||||
|
|
||||||
|
procedure TRadioController.GetPanViewWindow(PanId: Integer;
|
||||||
|
out ACenter, ASpan: Double);
|
||||||
|
begin
|
||||||
|
if PanId = 0 then
|
||||||
|
begin
|
||||||
|
GetViewWindow(ACenter, ASpan);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
ACenter := 0; ASpan := 0;
|
||||||
|
if not PanDDCActive(PanId) then Exit;
|
||||||
|
ViewWindowFor(FPanFreqHz[PanId], FPanRateKHz[PanId] * 1000.0,
|
||||||
|
FPanZoomF[PanId], FPanPanSl[PanId], ACenter, ASpan);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.SetPanZoomPan(PanId: Integer; AZoom, APan: Double);
|
||||||
|
// Зум пана — состояние сессии пана (не персистится с бандом, в отличие от
|
||||||
|
// главного ApplyZoom): пан живёт до STOP.
|
||||||
|
begin
|
||||||
|
if PanId = 0 then
|
||||||
|
begin
|
||||||
|
ApplyZoom(AZoom, APan);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
if not PanDDCActive(PanId) then Exit;
|
||||||
|
FPanZoomF[PanId] := EnsureRange(AZoom, 0.0, 1.0);
|
||||||
|
FPanPanSl[PanId] := EnsureRange(APan, 0.0, 1.0);
|
||||||
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
|
FDSPEngine.SetPanZoomPan(PanId, FPanZoomF[PanId], FPanPanSl[PanId]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.PanZoomStepTo(PanId: Integer; NewZ: Double);
|
||||||
|
// Ставит новый зум, удерживая ЦЕНТР текущего видимого окна (у пана нет VFO-якоря
|
||||||
|
// главного AnchoredPan; фиксация центра — естественный аналог).
|
||||||
|
var
|
||||||
|
C, S, BaseC, BaseS, U, OneMinus, FVis, NewPan: Double;
|
||||||
|
begin
|
||||||
|
if not PanDDCActive(PanId) then Exit;
|
||||||
|
GetPanViewWindow(PanId, C, S);
|
||||||
|
BaseC := FPanFreqHz[PanId];
|
||||||
|
BaseS := FPanRateKHz[PanId] * 1000.0;
|
||||||
|
if BaseS <= 0 then Exit;
|
||||||
|
NewZ := EnsureRange(NewZ, 0.0, 1.0);
|
||||||
|
// Инверсия ViewWindowFor: U = CenterOff/BaseS = −0.5 + Pan·OneMinus + FVis/2.
|
||||||
|
U := (C - BaseC) / BaseS;
|
||||||
|
OneMinus := 0.99 * Log10(9.0 * NewZ + 1.0);
|
||||||
|
if OneMinus <= 1e-9 then
|
||||||
|
NewPan := 0.5
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
FVis := 1.0 - OneMinus;
|
||||||
|
if FVis < 1.0 / 100.0 then FVis := 1.0 / 100.0;
|
||||||
|
NewPan := EnsureRange((U + 0.5 - FVis / 2.0) / OneMinus, 0.0, 1.0);
|
||||||
|
end;
|
||||||
|
SetPanZoomPan(PanId, NewZ, NewPan);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.PanZoomIn(PanId: Integer);
|
||||||
|
var CurMag: Double; i: Integer;
|
||||||
|
begin
|
||||||
|
if not PanDDCActive(PanId) then Exit;
|
||||||
|
CurMag := ZoomZToMag(FPanZoomF[PanId]);
|
||||||
|
for i := 0 to High(ZOOM_MAG_LADDER) do
|
||||||
|
if ZOOM_MAG_LADDER[i] > CurMag * 1.001 then
|
||||||
|
begin
|
||||||
|
PanZoomStepTo(PanId, ZoomMagToZ(ZOOM_MAG_LADDER[i]));
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.PanZoomOut(PanId: Integer);
|
||||||
|
var CurMag: Double; i: Integer;
|
||||||
|
begin
|
||||||
|
if not PanDDCActive(PanId) then Exit;
|
||||||
|
CurMag := ZoomZToMag(FPanZoomF[PanId]);
|
||||||
|
for i := High(ZOOM_MAG_LADDER) downto 0 do
|
||||||
|
if ZOOM_MAG_LADDER[i] < CurMag / 1.001 then
|
||||||
|
begin
|
||||||
|
if ZOOM_MAG_LADDER[i] <= 1.0 then SetPanZoomPan(PanId, 0.0, 0.5)
|
||||||
|
else PanZoomStepTo(PanId, ZoomMagToZ(ZOOM_MAG_LADDER[i]));
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
SetPanZoomPan(PanId, 0.0, 0.5);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.PanZoomDefault(PanId: Integer);
|
||||||
|
begin
|
||||||
|
SetPanZoomPan(PanId, 0.0, 0.5);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TRadioController.PanZoomFactor(PanId: Integer): Double;
|
||||||
|
begin
|
||||||
|
if PanId = 0 then Result := FZoomFactor
|
||||||
|
else if PanDDCActive(PanId) then Result := FPanZoomF[PanId]
|
||||||
|
else Result := 0.0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TRadioController.PanPanSlider(PanId: Integer): Double;
|
||||||
|
begin
|
||||||
|
if PanId = 0 then Result := FPanSlider
|
||||||
|
else if PanDDCActive(PanId) then Result := FPanPanSl[PanId]
|
||||||
|
else Result := 0.5;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSampleRate(Hz: Integer);
|
procedure TRadioController.SetSampleRate(Hz: Integer);
|
||||||
// Смена частоты дискретизации DDC. В этом приложении span ≡ sample rate.
|
// Смена частоты дискретизации DDC. В этом приложении span ≡ sample rate.
|
||||||
// Несёт: пересоздание WDSP-канала (Close+FlushQueue+Open), новый rate в
|
// Несёт: пересоздание WDSP-канала (Close+FlushQueue+Open), новый rate в
|
||||||
|
|||||||
+43
-3
@@ -235,6 +235,8 @@ type
|
|||||||
DispBuf: array of Double; // feed анализатора (DISPLAY_BLOCK_SIZE пар)
|
DispBuf: array of Double; // feed анализатора (DISPLAY_BLOCK_SIZE пар)
|
||||||
DispPos: Integer;
|
DispPos: Integer;
|
||||||
DispOpen: Boolean; // XCreateAnalyzer(PAN_DISP_BASE+PanId) жив
|
DispOpen: Boolean; // XCreateAnalyzer(PAN_DISP_BASE+PanId) жив
|
||||||
|
ZoomFactor: Double; // зум пана 0..1 (0 = весь span), как FZoomFactor
|
||||||
|
PanSlider: Double; // положение окна 0..1
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TWDSPEngine }
|
{ TWDSPEngine }
|
||||||
@@ -646,6 +648,9 @@ type
|
|||||||
// Zoom/pan: задать коэффициент зума и положение пана и переармировать
|
// Zoom/pan: задать коэффициент зума и положение пана и переармировать
|
||||||
// RX-анализатор. View* — границы видимого окна (Гц отн. DDC-центра).
|
// RX-анализатор. View* — границы видимого окна (Гц отн. DDC-центра).
|
||||||
procedure SetZoomPan(AZoom, APan: Double);
|
procedure SetZoomPan(AZoom, APan: Double);
|
||||||
|
// Зум/пан доп. пана (span-clip его анализатора). Видимое окно считает
|
||||||
|
// контроллер той же формулой (GetPanViewWindow).
|
||||||
|
procedure SetPanZoomPan(PanId: Integer; AZoom, APan: Double);
|
||||||
property ZoomFactor: Double read FZoomFactor;
|
property ZoomFactor: Double read FZoomFactor;
|
||||||
property PanSlider: Double read FPanSlider;
|
property PanSlider: Double read FPanSlider;
|
||||||
property ViewLowHz: Double read FViewLowHz;
|
property ViewLowHz: Double read FViewLowHz;
|
||||||
@@ -1383,14 +1388,17 @@ end;
|
|||||||
|
|
||||||
procedure TWDSPEngine.ApplyPanAnalyzerSettings(PanId: Integer);
|
procedure TWDSPEngine.ApplyPanAnalyzerSettings(PanId: Integer);
|
||||||
// Параметризованная копия ApplyRXAnalyzerSettings для analyzer'а доп. пана:
|
// Параметризованная копия ApplyRXAnalyzerSettings для analyzer'а доп. пана:
|
||||||
// rate пана, БЕЗ span-clip/zoom (пан-зум — этап 3.3+), FFT/детектор/усреднение
|
// rate пана + СВОЙ span-clip/zoom (ZoomFactor/PanSlider записи пана);
|
||||||
// пока общие с главным. Вызывать под FSliceLock.
|
// FFT/детектор/усреднение пока общие с главным. Вызывать под FSliceLock.
|
||||||
var
|
var
|
||||||
AnalyzerFFT, AvgCount, WfAvgCount, MaxW, Overlap, DisplayPixels, Disp: Integer;
|
AnalyzerFFT, AvgCount, WfAvgCount, MaxW, Overlap, DisplayPixels, Disp: Integer;
|
||||||
Backmult, WfBackmult: Double;
|
Backmult, WfBackmult: Double;
|
||||||
EffFPS: Double;
|
EffFPS: Double;
|
||||||
|
Bins, SpanClipL, SpanClipH, ZWidth: Integer;
|
||||||
|
ZoomSlider: Double;
|
||||||
const
|
const
|
||||||
KEEP_TIME_SEC = 0.10;
|
KEEP_TIME_SEC = 0.10;
|
||||||
|
ZOOM_LIMIT = 100.0; // как у главного (ApplyRXAnalyzerSettings)
|
||||||
begin
|
begin
|
||||||
if not FPans[PanId].DispOpen then Exit;
|
if not FPans[PanId].DispOpen then Exit;
|
||||||
Disp := PAN_DISP_BASE + PanId;
|
Disp := PAN_DISP_BASE + PanId;
|
||||||
@@ -1405,8 +1413,17 @@ begin
|
|||||||
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
|
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
|
||||||
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WfAvgCount, WfBackmult);
|
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WfAvgCount, WfBackmult);
|
||||||
|
|
||||||
|
// Span-clip зума пана — та же математика, что у главного (clp=0, зум только
|
||||||
|
// клипует края FFT). Контроллер считает видимое окно той же формулой.
|
||||||
|
Bins := AnalyzerFFT;
|
||||||
|
ZoomSlider := Log10(9.0 * EnsureRange(FPans[PanId].ZoomFactor, 0.0, 1.0) + 1.0);
|
||||||
|
ZWidth := Round(Bins * (1.0 - (1.0 - 1.0 / ZOOM_LIMIT) * ZoomSlider));
|
||||||
|
ZWidth := EnsureRange(ZWidth, 1, Bins);
|
||||||
|
SpanClipL := Floor(EnsureRange(FPans[PanId].PanSlider, 0.0, 1.0) * (Bins - ZWidth));
|
||||||
|
SpanClipH := Bins - ZWidth - SpanClipL;
|
||||||
|
|
||||||
SetAnalyzer(Disp, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE,
|
SetAnalyzer(Disp, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE,
|
||||||
FWindowType, 14.0, Overlap, 0, 0, 0, DisplayPixels,
|
FWindowType, 14.0, Overlap, 0, SpanClipL, SpanClipH, DisplayPixels,
|
||||||
1, 0, 0.0, 0.0, MaxW);
|
1, 0, 0.0, 0.0, MaxW);
|
||||||
SetDisplayDetectorMode(Disp, 0, FSpecDetector);
|
SetDisplayDetectorMode(Disp, 0, FSpecDetector);
|
||||||
SetDisplayAverageMode (Disp, 0, FSpecAvgMode);
|
SetDisplayAverageMode (Disp, 0, FSpecAvgMode);
|
||||||
@@ -2292,6 +2309,8 @@ begin
|
|||||||
FPans[PanId].AccPos := 0;
|
FPans[PanId].AccPos := 0;
|
||||||
FPans[PanId].DispPos := 0;
|
FPans[PanId].DispPos := 0;
|
||||||
FPans[PanId].DispOpen := False;
|
FPans[PanId].DispOpen := False;
|
||||||
|
FPans[PanId].ZoomFactor := 0.0; // без зума
|
||||||
|
FPans[PanId].PanSlider := 0.5;
|
||||||
FPans[PanId].Active := True;
|
FPans[PanId].Active := True;
|
||||||
AllocSliceBuffers; // FSliceIn/Out под новый макс. блок
|
AllocSliceBuffers; // FSliceIn/Out под новый макс. блок
|
||||||
if FAnalyzerOpen then CreatePanAnalyzer(PanId);
|
if FAnalyzerOpen then CreatePanAnalyzer(PanId);
|
||||||
@@ -2361,6 +2380,27 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TWDSPEngine.SetPanZoomPan(PanId: Integer; AZoom, APan: Double);
|
||||||
|
// Переармирует анализатор пана под новый span-clip. No-op guard обязателен:
|
||||||
|
// SetAnalyzer сбрасывает FFT-историю (урок SetZoomPan главного, dae0c19).
|
||||||
|
var NZ, NP: Double;
|
||||||
|
begin
|
||||||
|
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||||
|
NZ := EnsureRange(AZoom, 0.0, 1.0);
|
||||||
|
NP := EnsureRange(APan, 0.0, 1.0);
|
||||||
|
FSliceLock.Enter;
|
||||||
|
try
|
||||||
|
if not FPans[PanId].Active then Exit;
|
||||||
|
if (Abs(NZ - FPans[PanId].ZoomFactor) < 1E-9) and
|
||||||
|
(Abs(NP - FPans[PanId].PanSlider) < 1E-9) then Exit;
|
||||||
|
FPans[PanId].ZoomFactor := NZ;
|
||||||
|
FPans[PanId].PanSlider := NP;
|
||||||
|
ApplyPanAnalyzerSettings(PanId); // внутри гейт по DispOpen
|
||||||
|
finally
|
||||||
|
FSliceLock.Leave;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TWDSPEngine.PanActive(PanId: Integer): Boolean;
|
function TWDSPEngine.PanActive(PanId: Integer): Boolean;
|
||||||
begin
|
begin
|
||||||
Result := (PanId >= 1) and (PanId < MAX_PANS) and FPans[PanId].Active;
|
Result := (PanId >= 1) and (PanId < MAX_PANS) and FPans[PanId].Active;
|
||||||
|
|||||||
@@ -545,6 +545,34 @@ RATEPROBE удалены.** Наблюдение юзера: смена rate Г
|
|||||||
паузы >41мс, main-аудио/слайс пана 0/слайс пана 1 живы и слышат тон после
|
паузы >41мс, main-аудио/слайс пана 0/слайс пана 1 живы и слышат тон после
|
||||||
смены. Оба бинарника собираются.
|
смены. Оба бинарника собираются.
|
||||||
|
|
||||||
|
**СТАТУС «пан-зум панов N» (2026-07-12): ГОТОВО — смоук panzoomtest ALL PASS,
|
||||||
|
юзер подтвердил на экране, коммит cb6bdfa.**
|
||||||
|
- Движок: `TPanChan.ZoomFactor/PanSlider`, span-clip в
|
||||||
|
`ApplyPanAnalyzerSettings` (та же математика, что у главного; ZOOM_LIMIT 100),
|
||||||
|
`SetPanZoomPan` (no-op guard — SetAnalyzer сбрасывает FFT-историю, урок
|
||||||
|
dae0c19). Зум переживает смену rate пана (SetPanRate пересоздаёт анализатор
|
||||||
|
из записи пана).
|
||||||
|
- Контроллер: `FPanZoomF/FPanPanSl` (сессия пана, сброс в Add/RemovePanDDC —
|
||||||
|
band-персиста у панов нет), формула окна вынесена в юнит-хелпер
|
||||||
|
`ViewWindowFor` (GetViewWindow главного теперь тоже через него),
|
||||||
|
`GetPanViewWindow(0)` делегирует главному; `PanZoomIn/Out` по той же
|
||||||
|
лестнице ZOOM_MAG_LADDER, якорь = ЦЕНТР текущего окна (`PanZoomStepTo`,
|
||||||
|
у панов нет VFO-якоря главного), `PanZoomDefault`, геттеры.
|
||||||
|
- UI: `TPanZoomEvent` получил Sender (PanZoomBar), `OnPanZoomWindow`
|
||||||
|
диспетчеризует бар→пан; кнопки −/⌂/+ общие (`PanFromSender`); у панов N
|
||||||
|
включён ShowZoomRow, бар проводится в CreatePanadapter; единые
|
||||||
|
`SyncPanZoomUI/RefreshPanZoom` (вьюха+бар ← контроллер) используются в
|
||||||
|
SyncPanViews и drag-ретюне; `TPanafallPanel.ViewWindow` и `PanViewWindow` →
|
||||||
|
`GetPanViewWindow` ⇒ вся мышь/флаги панов автоматически зум-корректны.
|
||||||
|
Темизация баров панов N в ApplyDarkTheme.
|
||||||
|
- Смоук panzoomtest.pas: пик тона +50к на пане 384к совпадает с формулой окна
|
||||||
|
при зуме 0 / 0.5+пан 0.7 / сбросе (±0.6%).
|
||||||
|
- ⚠Фикс по первому экранному тесту: кнопки −/⌂/+ панов N рулили ГЛАВНЫМ зумом —
|
||||||
|
MakeFlatBtn создаёт кнопки с Owner=контейнер (не панель), PanFromSender их
|
||||||
|
не находит. Диспетчер кнопок = PanOfZoomButton (матч по идентичности
|
||||||
|
BtnZoomIn/Def/Out всех панов). Урок: PanFromSender работает только для
|
||||||
|
контролов с Owner=панель (TLabel шапки — да, MakeFlatBtn-кнопки — нет).
|
||||||
|
|
||||||
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
||||||
|
|
||||||
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
||||||
|
|||||||
Reference in New Issue
Block a user