mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(slices): per-pan дисплей — палитра/gamma/уровни на пан («◑»-поповер)
Кнопка «◑» в шапке каждого пана открывает компактный поповер настроек отображения (палитра, gamma, ref/range спектра, авто-уровень водопада + offset/ручные min-max). Модель «глобальный дефолт + override на пан»: пан 0 правит глобальные поля рендера (зеркалятся в контроллер), паны N ставят DisplayOverride и персистятся в воркспейс раздельно КВ/трансвертер. - PanDisplayPopup.pas (new): borderless fsStayOnTop форма, гаснет по OnDeactivate; Flat*-контролы; крестик «×» закрытия; дим-хинт «правки применяются и сохраняются сами»; кнопки «⟳ Reset»/«⇄ Copy to all». - TPanafallPanel: FBtnDisplay + OnDisplayClick + DisplayOverride; PositionHeaderChildren каскадит [×][⧉][◑] справа по Visible. - MainForm: OnPanDisplayClick/Applied/Reset/ApplyAll + ApplyGlobalRenderToView + MarkPanDisplayDirty. При смене палитры/сбросе чистим историю GL-водопада (TSpectrumView.ClearWaterfall) — GL хранит уже раскрашенные пиксели. - Settings/TPanCfg: под-объект 'display' (палитра/gamma/ref/range/wf-уровни), пишется только при override. Глобальными осознанно оставлены разрешение анализатора, MSAA, скорость водопада, детектор/усреднение (движок/перф, не косметика). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+170
-1
@@ -33,7 +33,7 @@ uses
|
||||
WebServer, WebAdapter,
|
||||
CATAdapter,
|
||||
SpectrumView, SpectrumViewOpengl,
|
||||
PanZoomBar, PanafallPanel,
|
||||
PanZoomBar, PanafallPanel, PanDisplayPopup,
|
||||
WidebandView,
|
||||
StatusBar,
|
||||
PlatformUtils,
|
||||
@@ -167,6 +167,7 @@ type
|
||||
FPanSpecDirty: array[0..MAX_PANS-1] of Boolean; // кадр пана готов к рендеру
|
||||
FPanWfDirty: array[0..MAX_PANS-1] of Boolean;
|
||||
FPanCbLock: SyncObjs.TCriticalSection; // жизнь FPans[i] vs пиксельные колбэки
|
||||
FPanDisplayPopup: TObject; // TPanDisplayPopup (3.6), лениво создаётся
|
||||
// Мышь на пане N>0: drag слайса или drag центра пана (ретюн DDC).
|
||||
FPanSliceDrag: Boolean;
|
||||
FPanSliceDragId: Integer;
|
||||
@@ -604,6 +605,13 @@ type
|
||||
procedure OnPanRateClick(Sender: TObject); // «NNN kHz» шапки пана N
|
||||
procedure PanRateMenuItemClick(Sender: TObject);
|
||||
procedure OnPanADCClick(Sender: TObject); // бейдж «RX1/RX2» (3.4)
|
||||
// Per-pan дисплей (3.6): «◑» → поповер палитры/gamma/уровней.
|
||||
procedure OnPanDisplayClick(Sender: TObject);
|
||||
procedure OnPanDisplayApplied(P: TPanafallPanel; FullRefresh: Boolean);
|
||||
procedure OnPanDisplayReset(P: TPanafallPanel); // сброс к глоб. дефолту
|
||||
procedure OnPanDisplayApplyAll(P: TPanafallPanel); // раскатать на все паны
|
||||
procedure ApplyGlobalRenderToView(V: TSpectrumView); // глоб. рендер → вьюха
|
||||
procedure MarkPanDisplayDirty(P: TPanafallPanel; ClearWf: Boolean); // репейнт (+сброс истории водопада)
|
||||
// Pop-out (3.5): «⧉» выносит пан в отдельное окно, [×] окна возвращает в стек.
|
||||
procedure OnPanPopOutClick(Sender: TObject);
|
||||
procedure PopOutPan(PanId: Integer);
|
||||
@@ -1111,6 +1119,7 @@ begin
|
||||
FPan.PanId := 0;
|
||||
FPan.OnAddPan := OnAddPanClick;
|
||||
FPan.OnADCClick := OnPanADCClick; // бейдж RX1/RX2 (3.4, платы с 2 АЦП)
|
||||
FPan.OnDisplayClick := OnPanDisplayClick; // «◑» дисплей-поповер (3.6)
|
||||
FPans[0] := FPan;
|
||||
FPanShare[0] := 1.0;
|
||||
FPanGridMode := False;
|
||||
@@ -2881,6 +2890,7 @@ begin
|
||||
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);
|
||||
if FPans[i].BtnDisplay <> nil then StyleButton(FPans[i].BtnDisplay, False);
|
||||
ApplyPanHeaderTheme(FPans[i]);
|
||||
if i > 0 then
|
||||
begin
|
||||
@@ -5643,6 +5653,132 @@ begin
|
||||
UpdatePanHeaders;
|
||||
end;
|
||||
|
||||
// ---- Per-pan дисплей (3.6): «◑» палитра/gamma/уровни отдельно на пан ----
|
||||
|
||||
procedure TMainForm.ApplyGlobalRenderToView(V: TSpectrumView);
|
||||
// Глобальный дефолт рендера (контроллер) → вьюха пана. Используется при
|
||||
// создании пана (дефолт) и «Сбросе к дефолту».
|
||||
begin
|
||||
if V = nil then Exit;
|
||||
V.WfPalette := FController.FWfPalette;
|
||||
V.WfGamma := FController.FWfGamma;
|
||||
V.SpecRefLevel := FController.FSpecRefLevel;
|
||||
V.SpecRange := FController.FSpecRange;
|
||||
V.SpecGridStep := FController.FSpecGridStep;
|
||||
V.WfAGCEnabled := FController.FWfAGCEnabled;
|
||||
V.WfAGCOffset := FController.FWfAGCOffset;
|
||||
V.WfManualHigh := FController.FWfManualHigh;
|
||||
V.WfManualLow := FController.FWfManualLow;
|
||||
end;
|
||||
|
||||
procedure TMainForm.MarkPanDisplayDirty(P: TPanafallPanel; ClearWf: Boolean);
|
||||
// Полный репейнт пана после смены рендера: сетка спектра + водопад
|
||||
// (LUT/палитра пересоберётся в SpectrumView по своим no-op-гардам).
|
||||
// ClearWf=True (смена палитры/gamma/авто) чистит историю водопада — GL хранит
|
||||
// уже раскрашенные пиксели, иначе старые строки остаются в прежней палитре.
|
||||
begin
|
||||
if P = nil then Exit;
|
||||
P.View.InvalidateGridCache;
|
||||
if ClearWf then P.View.ClearWaterfall;
|
||||
if P = FPan then
|
||||
begin
|
||||
FSpectrumDirty := True;
|
||||
FWaterfallDirty := True;
|
||||
if Assigned(PbSpectrum) then PbSpectrum.Invalidate;
|
||||
if Assigned(PbWaterfall) then PbWaterfall.Invalidate;
|
||||
Exit;
|
||||
end;
|
||||
P.View.SpectrumDirty := True;
|
||||
FPanSpecDirty[P.PanId] := True;
|
||||
FPanWfDirty[P.PanId] := True;
|
||||
if P.PbSpectrum <> nil then P.PbSpectrum.Invalidate;
|
||||
if P.PbWaterfall <> nil then P.PbWaterfall.Invalidate;
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnPanDisplayClick(Sender: TObject);
|
||||
// «◑» в шапке пана: показать поповер под кнопкой.
|
||||
var
|
||||
P: TPanafallPanel;
|
||||
Pop: TPanDisplayPopup;
|
||||
Pt: TPoint;
|
||||
begin
|
||||
if not (Sender is TPanafallPanel) then Exit;
|
||||
P := TPanafallPanel(Sender);
|
||||
if P.BtnDisplay = nil then Exit;
|
||||
if FPanDisplayPopup = nil then
|
||||
begin
|
||||
Pop := TPanDisplayPopup.CreateNew(Self);
|
||||
Pop.OnApplied := OnPanDisplayApplied;
|
||||
Pop.OnResetDefault := OnPanDisplayReset;
|
||||
Pop.OnApplyAll := OnPanDisplayApplyAll;
|
||||
FPanDisplayPopup := Pop;
|
||||
end
|
||||
else
|
||||
Pop := TPanDisplayPopup(FPanDisplayPopup);
|
||||
// Экранная позиция левого-нижнего угла кнопки.
|
||||
Pt := P.BtnDisplay.ClientToScreen(Point(0, P.BtnDisplay.Height + 2));
|
||||
Pop.ShowFor(P, Pt.X, Pt.Y);
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnPanDisplayApplied(P: TPanafallPanel; FullRefresh: Boolean);
|
||||
// Поповер применил правку к P.View. Пан 0 = глобальный дефолт (зеркалим в
|
||||
// контроллер, чтобы персистилось и наследовалось новыми панами); паны N —
|
||||
// ставим override-флаг (персист в воркспейс).
|
||||
begin
|
||||
if P = nil then Exit;
|
||||
if P.PanId = 0 then
|
||||
begin
|
||||
FController.FWfPalette := P.View.WfPalette;
|
||||
FController.FWfGamma := P.View.WfGamma;
|
||||
FController.FSpecRefLevel := P.View.SpecRefLevel;
|
||||
FController.FSpecRange := P.View.SpecRange;
|
||||
FController.FWfAGCEnabled := P.View.WfAGCEnabled;
|
||||
FController.FWfAGCOffset := P.View.WfAGCOffset;
|
||||
FController.FWfManualHigh := P.View.WfManualHigh;
|
||||
FController.FWfManualLow := P.View.WfManualLow;
|
||||
// На диск не пишем на каждый тик — глобальные сохранятся при закрытии
|
||||
// (FormDestroy → SaveGlobalSettings), как и для главного диалога настроек.
|
||||
end
|
||||
else
|
||||
P.DisplayOverride := True;
|
||||
MarkPanDisplayDirty(P, FullRefresh);
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnPanDisplayReset(P: TPanafallPanel);
|
||||
// «Сброс к дефолту»: пан снова наследует глобальный рендер.
|
||||
begin
|
||||
if (P = nil) or (P.PanId = 0) then Exit;
|
||||
ApplyGlobalRenderToView(P.View);
|
||||
P.DisplayOverride := False;
|
||||
MarkPanDisplayDirty(P, True); // палитра могла смениться → чистим водопад
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnPanDisplayApplyAll(P: TPanafallPanel);
|
||||
// «Применить ко всем»: рендер пана P раскатывается на все паны + становится
|
||||
// глобальным дефолтом (для пана 0 и новых панов).
|
||||
var i: Integer; Q: TPanafallPanel;
|
||||
begin
|
||||
if P = nil then Exit;
|
||||
// Глобальный дефолт ← P.
|
||||
FController.FWfPalette := P.View.WfPalette;
|
||||
FController.FWfGamma := P.View.WfGamma;
|
||||
FController.FSpecRefLevel := P.View.SpecRefLevel;
|
||||
FController.FSpecRange := P.View.SpecRange;
|
||||
FController.FWfAGCEnabled := P.View.WfAGCEnabled;
|
||||
FController.FWfAGCOffset := P.View.WfAGCOffset;
|
||||
FController.FWfManualHigh := P.View.WfManualHigh;
|
||||
FController.FWfManualLow := P.View.WfManualLow;
|
||||
FController.SaveGlobalSettings;
|
||||
for i := 0 to MAX_PANS - 1 do
|
||||
begin
|
||||
Q := FPans[i];
|
||||
if Q = nil then Continue;
|
||||
if Q <> P then ApplyGlobalRenderToView(Q.View);
|
||||
Q.DisplayOverride := False; // все синхронны с дефолтом ⇒ не override
|
||||
MarkPanDisplayDirty(Q, True); // палитра раскатана → чистим водопад
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---- Pop-out (3.5): пан N в отдельном OS-окне ----
|
||||
|
||||
function TMainForm.PanFloating(PanId: Integer): Boolean;
|
||||
@@ -5822,6 +5958,8 @@ begin
|
||||
if P.BtnClosePan <> nil then P.BtnClosePan.Visible := (N > 1) and (i > 0);
|
||||
if P.BtnPopPan <> nil then
|
||||
P.BtnPopPan.Visible := (N > 1) and (i > 0) and not PanFloating(i);
|
||||
// «◑» дисплей-поповер — на всех панах с видимой шапкой (вкл. пан 0).
|
||||
if P.BtnDisplay <> nil then P.BtnDisplay.Visible := P.HeaderVisible;
|
||||
if i = 0 then
|
||||
begin
|
||||
P.SetHeaderText(Format('PAN 0 · %.6f MHz ·',
|
||||
@@ -5888,6 +6026,7 @@ begin
|
||||
P.RateClickable := True;
|
||||
P.OnADCClick := OnPanADCClick; // «RX1/RX2» тумблер АЦП (3.4)
|
||||
P.OnPanPopOut := OnPanPopOutClick; // «⧉» в отдельное окно (3.5)
|
||||
P.OnDisplayClick := OnPanDisplayClick; // «◑» дисплей-поповер (3.6)
|
||||
P.SpectrumMouseDown := PanNSpectrumMouseDown;
|
||||
P.SpectrumMouseMove := PanNSpectrumMouseMove;
|
||||
P.SpectrumMouseUp := PanNSpectrumMouseUp;
|
||||
@@ -6010,6 +6149,9 @@ begin
|
||||
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||
P := FPans[PanId];
|
||||
if P = nil then Exit;
|
||||
// Дисплей-поповер мог быть привязан к этому пану — спрятать (FPan станет
|
||||
// висячим; поповер перепривяжется при следующем показе через LoadFrom).
|
||||
if FPanDisplayPopup <> nil then TPanDisplayPopup(FPanDisplayPopup).Hide;
|
||||
// Отписываем от пиксельных колбэков ДО удаления DDC/панели.
|
||||
FPanCbLock.Enter;
|
||||
try
|
||||
@@ -6094,6 +6236,19 @@ begin
|
||||
Cfg.Pans[i].Zoom := FController.PanZoomFactor(i);
|
||||
Cfg.Pans[i].PanSlider := FController.PanPanSlider(i);
|
||||
Cfg.Pans[i].ColShare := FPanColShare[i];
|
||||
// Per-pan дисплей (3.6): пишем override только если пан его задал.
|
||||
if (FPans[i] <> nil) and FPans[i].DisplayOverride then
|
||||
begin
|
||||
Cfg.Pans[i].HasDisplay := True;
|
||||
Cfg.Pans[i].DPalette := FPans[i].View.WfPalette;
|
||||
Cfg.Pans[i].DGamma := FPans[i].View.WfGamma;
|
||||
Cfg.Pans[i].DSpecRef := FPans[i].View.SpecRefLevel;
|
||||
Cfg.Pans[i].DSpecRange := FPans[i].View.SpecRange;
|
||||
Cfg.Pans[i].DWfAGC := FPans[i].View.WfAGCEnabled;
|
||||
Cfg.Pans[i].DWfOffset := FPans[i].View.WfAGCOffset;
|
||||
Cfg.Pans[i].DWfHigh := FPans[i].View.WfManualHigh;
|
||||
Cfg.Pans[i].DWfLow := FPans[i].View.WfManualLow;
|
||||
end;
|
||||
end;
|
||||
// Слайсы этого пана (главный VFO пана 0 — не слайс, его тут нет).
|
||||
n := 0;
|
||||
@@ -6188,6 +6343,20 @@ begin
|
||||
FPanShare[i] := Cfg.Pans[i].StackShare;
|
||||
FPanColShare[i] := Cfg.Pans[i].ColShare;
|
||||
P.SplitterRatio := Cfg.Pans[i].SplitRatio;
|
||||
// Per-pan дисплей (3.6): CreatePanCore уже залил глоб. дефолт — при наличии
|
||||
// override перекрываем сохранёнкой.
|
||||
if Cfg.Pans[i].HasDisplay then
|
||||
begin
|
||||
P.View.WfPalette := EnsureRange(Cfg.Pans[i].DPalette, 0, 2);
|
||||
P.View.WfGamma := EnsureRange(Cfg.Pans[i].DGamma, 0.30, 3.00);
|
||||
P.View.SpecRefLevel := Cfg.Pans[i].DSpecRef;
|
||||
P.View.SpecRange := Cfg.Pans[i].DSpecRange;
|
||||
P.View.WfAGCEnabled := Cfg.Pans[i].DWfAGC;
|
||||
P.View.WfAGCOffset := Cfg.Pans[i].DWfOffset;
|
||||
P.View.WfManualHigh := Cfg.Pans[i].DWfHigh;
|
||||
P.View.WfManualLow := Cfg.Pans[i].DWfLow;
|
||||
P.DisplayOverride := True;
|
||||
end;
|
||||
FController.SetPanZoomPan(i, Cfg.Pans[i].Zoom, Cfg.Pans[i].PanSlider);
|
||||
for k := 0 to High(Cfg.Pans[i].Slices) do
|
||||
RestoreSliceInto(P, i, Cfg.Pans[i].Slices[k]);
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
unit PanDisplayPopup;
|
||||
|
||||
{
|
||||
TPanDisplayPopup — компактный поповер per-pan настроек отображения
|
||||
(палитра / gamma / уровни спектра и водопада), открывается кнопкой «◑» в
|
||||
шапке пана. Правки применяются к P.View живьём; после каждой изменения зовётся
|
||||
OnApplied(P) — хозяин (MainForm) инвалидирует пан, ставит override-флаг и
|
||||
зеркалит глобальные (для пана 0). Кнопки «Сброс к дефолту» / «Применить ко
|
||||
всем» — через OnResetDefault / OnApplyAll.
|
||||
|
||||
Форма без рамки, поверх всех, гаснет по потере фокуса (OnDeactivate).
|
||||
Этап 3.6 плана мультислайсов (doc/SLICES_PLAN.md), per-pan дисплей.
|
||||
}
|
||||
|
||||
{$IFDEF FPC}
|
||||
{$MODE Delphi}
|
||||
{$ENDIF}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Math, Controls, ExtCtrls, StdCtrls, Graphics, Forms,
|
||||
AppTheme, PanafallPanel,
|
||||
FlatButton, FlatComboBox, FlatCheckBox, FlatSpinEdit, FlatFloatSpinEdit;
|
||||
|
||||
type
|
||||
TPanDisplayEvent = procedure(P: TPanafallPanel) of object;
|
||||
// FullRefresh=True ⇒ смена палитры/gamma/авто: чистим историю водопада.
|
||||
TPanDisplayApplyEvent = procedure(P: TPanafallPanel; FullRefresh: Boolean) of object;
|
||||
|
||||
TPanDisplayPopup = class(TForm)
|
||||
private
|
||||
FPan: TPanafallPanel; // пан, чьи настройки правим (не владеем)
|
||||
FLoading: Boolean; // подавляет OnApplied при программной заливке
|
||||
FBg: TPanel;
|
||||
FTitle: TLabel;
|
||||
FCbPalette: TFlatComboBox;
|
||||
FSeGamma: TFlatFloatSpinEdit;
|
||||
FSeRef: TFlatSpinEdit;
|
||||
FSeRange: TFlatSpinEdit;
|
||||
FChkAuto: TFlatCheckBox;
|
||||
FSeOffset: TFlatSpinEdit;
|
||||
FSeMin: TFlatSpinEdit;
|
||||
FSeMax: TFlatSpinEdit;
|
||||
FLbOffset: TLabel;
|
||||
FLbMin: TLabel;
|
||||
FLbMax: TLabel;
|
||||
FHint: TLabel;
|
||||
FBtnClose: TFlatButton;
|
||||
FBtnReset: TFlatButton;
|
||||
FBtnAll: TFlatButton;
|
||||
FOnApplied: TPanDisplayApplyEvent;
|
||||
FOnResetDefault: TPanDisplayEvent;
|
||||
FOnApplyAll: TPanDisplayEvent;
|
||||
|
||||
function MakeLbl(const Cap: string; ALeft, ATop, AW: Integer): TLabel;
|
||||
procedure Apply(FullRefresh: Boolean);
|
||||
procedure CtlChange(Sender: TObject);
|
||||
procedure PaletteChange(Sender: TObject);
|
||||
procedure AutoChange(Sender: TObject);
|
||||
procedure CloseClick(Sender: TObject);
|
||||
procedure ResetClick(Sender: TObject);
|
||||
procedure AllClick(Sender: TObject);
|
||||
procedure DoDeactivate(Sender: TObject);
|
||||
procedure UpdateWfRowEnable;
|
||||
public
|
||||
constructor CreateNew(AOwner: TComponent; Num: Integer = 0); override;
|
||||
procedure LoadFrom(P: TPanafallPanel);
|
||||
// Показать под кнопкой (экранные координаты левого-нижнего угла кнопки).
|
||||
procedure ShowFor(P: TPanafallPanel; ScreenX, ScreenY: Integer);
|
||||
property OnApplied: TPanDisplayApplyEvent read FOnApplied write FOnApplied;
|
||||
property OnResetDefault: TPanDisplayEvent read FOnResetDefault write FOnResetDefault;
|
||||
property OnApplyAll: TPanDisplayEvent read FOnApplyAll write FOnApplyAll;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
{$IFDEF WINDOWS}
|
||||
UI_FONT = 'Segoe UI';
|
||||
{$ELSE}
|
||||
UI_FONT = 'Sans';
|
||||
{$ENDIF}
|
||||
CLR_BG = TColor($00303030); // рамка (1px кайма формы)
|
||||
CLR_PANEL = TColor($001A1A1A);
|
||||
CLR_TEXT = TColor($00E0E0E0);
|
||||
CLR_TEXTDIM = TColor($00888888);
|
||||
CLR_ACCENT = TColor($0040FF80);
|
||||
CLR_INPUT = TColor($00FFFFFF);
|
||||
CLR_INPUT_TEXT = TColor($00202020);
|
||||
|
||||
POP_W = 244;
|
||||
PAD = 10;
|
||||
LBLW = 66;
|
||||
CTLX = 84;
|
||||
ROWH = 24;
|
||||
|
||||
WF_PALETTE_NAMES: array[0..2] of string = ('Classic', 'Inferno', 'Turbo');
|
||||
|
||||
constructor TPanDisplayPopup.CreateNew(AOwner: TComponent; Num: Integer);
|
||||
var
|
||||
Y: Integer;
|
||||
|
||||
function NextRow: Integer;
|
||||
begin
|
||||
Result := Y;
|
||||
Inc(Y, ROWH + 4);
|
||||
end;
|
||||
|
||||
function MakeIntSpin(ATop, AMin, AMax, AInc: Integer): TFlatSpinEdit;
|
||||
begin
|
||||
Result := TFlatSpinEdit.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.SetBounds(CTLX, ATop, POP_W - CTLX - PAD, ROWH - 2);
|
||||
Result.MinValue := AMin;
|
||||
Result.MaxValue := AMax;
|
||||
Result.Increment := AInc;
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := CtlChange;
|
||||
end;
|
||||
|
||||
begin
|
||||
inherited CreateNew(AOwner, Num);
|
||||
BorderStyle := bsNone;
|
||||
FormStyle := fsStayOnTop;
|
||||
Color := CLR_BG;
|
||||
Font.Name := UI_FONT;
|
||||
Font.Size := 9;
|
||||
Font.Color := CLR_TEXT;
|
||||
OnDeactivate := DoDeactivate;
|
||||
FLoading := False;
|
||||
|
||||
FBg := TPanel.Create(Self);
|
||||
FBg.Parent := Self;
|
||||
FBg.BevelOuter := bvNone;
|
||||
FBg.Color := CLR_PANEL;
|
||||
// 1px рамка = зазор до края формы (цвет формы = CLR_BG).
|
||||
FBg.SetBounds(1, 1, POP_W - 2, 10);
|
||||
|
||||
Y := PAD;
|
||||
|
||||
FTitle := MakeLbl('Display', PAD, NextRow + 2, POP_W - 2 * PAD - 18);
|
||||
FTitle.Font.Color := CLR_ACCENT;
|
||||
FTitle.Font.Style := [fsBold];
|
||||
// Крестик закрытия в правом верхнем углу («готово» — правки сохраняются сами).
|
||||
FBtnClose := MakeFlatBtn(FBg, '×', POP_W - PAD - 16, PAD, 16, 16, CloseClick);
|
||||
FBtnClose.ClrText := CLR_TEXTDIM;
|
||||
FBtnClose.ClrTextAct := CLR_TEXT;
|
||||
// Хинты на кнопках поповера отключены: THintWindow в этой borderless-форме
|
||||
// позиционирует подсказку не у элемента (у левой панели). Лейблы и так ясны.
|
||||
|
||||
// ---- Палитра ----
|
||||
MakeLbl('Palette', PAD, Y + 3, LBLW);
|
||||
FCbPalette := TFlatComboBox.Create(Self);
|
||||
FCbPalette.Parent := FBg;
|
||||
FCbPalette.SetBounds(CTLX, Y, POP_W - CTLX - PAD, ROWH - 2);
|
||||
FCbPalette.Items.Add(WF_PALETTE_NAMES[0]);
|
||||
FCbPalette.Items.Add(WF_PALETTE_NAMES[1]);
|
||||
FCbPalette.Items.Add(WF_PALETTE_NAMES[2]);
|
||||
FCbPalette.Color := CLR_INPUT;
|
||||
FCbPalette.Font.Color := CLR_INPUT_TEXT;
|
||||
FCbPalette.Font.Name := UI_FONT;
|
||||
FCbPalette.Font.Size := 9;
|
||||
FCbPalette.OnChange := PaletteChange; // смена палитры чистит историю водопада
|
||||
NextRow;
|
||||
|
||||
// ---- Gamma ----
|
||||
MakeLbl('Gamma', PAD, Y + 3, LBLW);
|
||||
FSeGamma := TFlatFloatSpinEdit.Create(Self);
|
||||
FSeGamma.Parent := FBg;
|
||||
FSeGamma.SetBounds(CTLX, Y, POP_W - CTLX - PAD, ROWH - 2);
|
||||
FSeGamma.MinValue := 0.30;
|
||||
FSeGamma.MaxValue := 3.00;
|
||||
FSeGamma.Increment := 0.05;
|
||||
FSeGamma.DecimalPlaces := 2;
|
||||
FSeGamma.Color := CLR_INPUT;
|
||||
FSeGamma.Font.Color := CLR_INPUT_TEXT;
|
||||
FSeGamma.Font.Name := UI_FONT;
|
||||
FSeGamma.Font.Size := 9;
|
||||
// gamma — непрерывный спин: чистить историю на каждый тик = мерцание;
|
||||
// применяем к новым строкам (как ref/range), без сброса водопада.
|
||||
FSeGamma.OnChange := CtlChange;
|
||||
NextRow;
|
||||
|
||||
MakeLbl('— Spectrum —', PAD, Y + 3, POP_W - 2 * PAD).Font.Color := CLR_TEXTDIM;
|
||||
NextRow;
|
||||
|
||||
// ---- Ref level / Range ----
|
||||
MakeLbl('Ref, dBm', PAD, Y + 3, LBLW);
|
||||
FSeRef := MakeIntSpin(Y, -200, 30, 5);
|
||||
NextRow;
|
||||
MakeLbl('Range, dB', PAD, Y + 3, LBLW);
|
||||
FSeRange := MakeIntSpin(Y, 20, 200, 5);
|
||||
NextRow;
|
||||
|
||||
MakeLbl('— Waterfall —', PAD, Y + 3, POP_W - 2 * PAD).Font.Color := CLR_TEXTDIM;
|
||||
NextRow;
|
||||
|
||||
// ---- Авто-уровень водопада ----
|
||||
FChkAuto := TFlatCheckBox.Create(Self);
|
||||
FChkAuto.Parent := FBg;
|
||||
FChkAuto.SetBounds(PAD, Y + 2, POP_W - 2 * PAD, ROWH - 4);
|
||||
FChkAuto.Caption := 'Auto level';
|
||||
FChkAuto.Font.Color := CLR_TEXT;
|
||||
FChkAuto.Font.Name := UI_FONT;
|
||||
FChkAuto.Font.Size := 9;
|
||||
FChkAuto.OnChange := AutoChange;
|
||||
NextRow;
|
||||
|
||||
FLbOffset := MakeLbl('Offset, dB', PAD, Y + 3, LBLW);
|
||||
FSeOffset := MakeIntSpin(Y, -60, 60, 1);
|
||||
NextRow;
|
||||
FLbMin := MakeLbl('Min, dBm', PAD, Y + 3, LBLW);
|
||||
FSeMin := MakeIntSpin(Y, -200, 0, 5);
|
||||
NextRow;
|
||||
FLbMax := MakeLbl('Max, dBm', PAD, Y + 3, LBLW);
|
||||
FSeMax := MakeIntSpin(Y, -160, 30, 5);
|
||||
NextRow;
|
||||
|
||||
// ---- Подсказка: правки применяются и сохраняются сами (не нужно «сохранять») ----
|
||||
FHint := MakeLbl('Changes apply & save automatically', PAD, Y + 2, POP_W - 2 * PAD);
|
||||
FHint.Font.Color := CLR_TEXTDIM;
|
||||
Inc(Y, 18);
|
||||
|
||||
// ---- Кнопки действий (обе — вторичные, не «сохранение») ----
|
||||
FBtnReset := MakeFlatBtn(FBg, '⟳ Reset', PAD, Y, (POP_W - 3 * PAD) div 2, ROWH, ResetClick);
|
||||
FBtnReset.ClrText := CLR_TEXT;
|
||||
FBtnReset.ClrTextAct := CLR_ACCENT;
|
||||
FBtnAll := MakeFlatBtn(FBg, '⇄ Copy to all',
|
||||
PAD + (POP_W - 3 * PAD) div 2 + PAD, Y, (POP_W - 3 * PAD) div 2, ROWH, AllClick);
|
||||
FBtnAll.ClrText := CLR_TEXT;
|
||||
FBtnAll.ClrTextAct := CLR_ACCENT;
|
||||
Inc(Y, ROWH + PAD);
|
||||
|
||||
FBg.Height := Y - 1;
|
||||
ClientWidth := POP_W;
|
||||
ClientHeight := Y + 1;
|
||||
end;
|
||||
|
||||
function TPanDisplayPopup.MakeLbl(const Cap: string; ALeft, ATop, AW: Integer): TLabel;
|
||||
begin
|
||||
Result := TLabel.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.Caption := Cap;
|
||||
Result.Left := ALeft;
|
||||
Result.Top := ATop;
|
||||
if AW > 0 then Result.Width := AW;
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.UpdateWfRowEnable;
|
||||
var Man: Boolean;
|
||||
begin
|
||||
Man := not FChkAuto.Checked;
|
||||
FLbOffset.Enabled := not Man; FSeOffset.Enabled := not Man;
|
||||
FLbMin.Enabled := Man; FSeMin.Enabled := Man;
|
||||
FLbMax.Enabled := Man; FSeMax.Enabled := Man;
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.LoadFrom(P: TPanafallPanel);
|
||||
begin
|
||||
FPan := P;
|
||||
if P = nil then Exit;
|
||||
FLoading := True;
|
||||
try
|
||||
if P.PanId = 0 then FTitle.Caption := 'Display · PAN 0'
|
||||
else FTitle.Caption := Format('Display · PAN %d', [P.PanId]);
|
||||
FCbPalette.ItemIndex := EnsureRange(P.View.WfPalette, 0, 2);
|
||||
FSeGamma.Value := EnsureRange(P.View.WfGamma, 0.30, 3.00);
|
||||
FSeRef.Value := Round(P.View.SpecRefLevel);
|
||||
FSeRange.Value := Round(P.View.SpecRange);
|
||||
FChkAuto.Checked := P.View.WfAGCEnabled;
|
||||
FSeOffset.Value := Round(P.View.WfAGCOffset);
|
||||
FSeMin.Value := Round(P.View.WfManualLow);
|
||||
FSeMax.Value := Round(P.View.WfManualHigh);
|
||||
// Сброс к дефолту осмыслен только для панов N (пан 0 = сам дефолт).
|
||||
FBtnReset.Visible := P.PanId > 0;
|
||||
UpdateWfRowEnable;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.Apply(FullRefresh: Boolean);
|
||||
begin
|
||||
if FLoading or (FPan = nil) then Exit;
|
||||
FPan.View.WfPalette := FCbPalette.ItemIndex;
|
||||
FPan.View.WfGamma := FSeGamma.Value;
|
||||
FPan.View.SpecRefLevel := FSeRef.Value;
|
||||
FPan.View.SpecRange := FSeRange.Value;
|
||||
FPan.View.WfAGCEnabled := FChkAuto.Checked;
|
||||
FPan.View.WfAGCOffset := FSeOffset.Value;
|
||||
FPan.View.WfManualLow := FSeMin.Value;
|
||||
FPan.View.WfManualHigh := FSeMax.Value;
|
||||
if Assigned(FOnApplied) then FOnApplied(FPan, FullRefresh);
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.CtlChange(Sender: TObject);
|
||||
begin
|
||||
Apply(False); // ref/range/offset/min/max: применяются к новым строкам
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.PaletteChange(Sender: TObject);
|
||||
begin
|
||||
Apply(True); // палитра (дискретный выбор): перекрасить весь водопад
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.AutoChange(Sender: TObject);
|
||||
begin
|
||||
UpdateWfRowEnable;
|
||||
Apply(True); // смена режима уровня меняет отображение всей истории
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.CloseClick(Sender: TObject);
|
||||
begin
|
||||
Hide;
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.ResetClick(Sender: TObject);
|
||||
begin
|
||||
if (FPan = nil) or not Assigned(FOnResetDefault) then Exit;
|
||||
FOnResetDefault(FPan);
|
||||
LoadFrom(FPan); // перезалить контролы значениями глобального дефолта
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.AllClick(Sender: TObject);
|
||||
begin
|
||||
if (FPan = nil) or not Assigned(FOnApplyAll) then Exit;
|
||||
FOnApplyAll(FPan);
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.DoDeactivate(Sender: TObject);
|
||||
begin
|
||||
Hide;
|
||||
end;
|
||||
|
||||
procedure TPanDisplayPopup.ShowFor(P: TPanafallPanel; ScreenX, ScreenY: Integer);
|
||||
var L, T: Integer;
|
||||
begin
|
||||
LoadFrom(P);
|
||||
L := ScreenX;
|
||||
T := ScreenY;
|
||||
// Кламп в экран.
|
||||
if L + Width > Screen.Width then L := Screen.Width - Width - 4;
|
||||
if L < 0 then L := 4;
|
||||
if T + Height > Screen.Height then T := ScreenY - Height - 4; // раскрыть вверх
|
||||
if T < 0 then T := 4;
|
||||
SetBounds(L, T, Width, Height);
|
||||
Show;
|
||||
end;
|
||||
|
||||
end.
|
||||
+34
-5
@@ -103,6 +103,9 @@ type
|
||||
FOnADCClick: TNotifyEvent;
|
||||
FBtnPopPan: TFlatButton; // «⧉» pop-out в отдельное окно (паны N>0, 3.5)
|
||||
FOnPanPopOut: TNotifyEvent;
|
||||
FBtnDisplay: TFlatButton; // «◑» per-pan настройки дисплея (3.6)
|
||||
FOnDisplayClick: TNotifyEvent;
|
||||
FDisplayOverride: Boolean; // True = пан переопределил глобальный дефолт (persist)
|
||||
FBtnClosePan: TFlatButton; // «×» в шапке (только паны N>0)
|
||||
FBtnAddPan: TFlatButton; // «⊞» в ряду пан/зума (только пан 0)
|
||||
FHeaderVisible: Boolean;
|
||||
@@ -122,6 +125,7 @@ type
|
||||
procedure HeaderADCClick(Sender: TObject);
|
||||
procedure SetADCClickable(AValue: Boolean);
|
||||
procedure BtnPopPanClick(Sender: TObject);
|
||||
procedure BtnDisplayClick(Sender: TObject);
|
||||
procedure PositionHeaderChildren(RW, H: Integer);
|
||||
// Видимое частотное окно ЭТОГО пана: пан 0 — окно главного (зум/пан),
|
||||
// паны N — центр их DDC ± rate/2 (пан-зум — этап 3.3+).
|
||||
@@ -213,6 +217,11 @@ type
|
||||
property OnADCClick: TNotifyEvent read FOnADCClick write FOnADCClick;
|
||||
// «⧉» (3.5): вынести пан в отдельное окно (хозяин).
|
||||
property OnPanPopOut: TNotifyEvent read FOnPanPopOut write FOnPanPopOut;
|
||||
// «◑» (3.6): открыть поповер per-pan настроек дисплея (хозяин).
|
||||
property OnDisplayClick: TNotifyEvent read FOnDisplayClick write FOnDisplayClick;
|
||||
property BtnDisplay: TFlatButton read FBtnDisplay;
|
||||
// Пан переопределил глобальный дефолт дисплея (для персиста панов N).
|
||||
property DisplayOverride: Boolean read FDisplayOverride write FDisplayOverride;
|
||||
property HeaderPanel: TPanel read FHeaderPanel;
|
||||
property HeaderLabel: TLabel read FHeaderLabel;
|
||||
property HeaderRateLabel: TLabel read FHeaderRateLabel;
|
||||
@@ -435,6 +444,10 @@ begin
|
||||
FHeaderADCLabel.Parent := FHeaderPanel;
|
||||
FHeaderADCLabel.AutoSize := True;
|
||||
FHeaderADCLabel.OnClick := HeaderADCClick;
|
||||
FBtnDisplay := MakeFlatBtn(FHeaderPanel, '◑', 0, 0, 10, 10, BtnDisplayClick);
|
||||
FBtnDisplay.Hint := 'Display settings';
|
||||
FBtnDisplay.ShowHint := True;
|
||||
FBtnDisplay.Visible := False; // хозяин включает при видимой шапке
|
||||
FBtnPopPan := MakeFlatBtn(FHeaderPanel, '⧉', 0, 0, 10, 10, BtnPopPanClick);
|
||||
FBtnPopPan.Hint := 'Pop out';
|
||||
FBtnPopPan.ShowHint := True;
|
||||
@@ -538,6 +551,11 @@ begin
|
||||
if Assigned(FOnPanPopOut) then FOnPanPopOut(Self);
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.BtnDisplayClick(Sender: TObject);
|
||||
begin
|
||||
if Assigned(FOnDisplayClick) then FOnDisplayClick(Self);
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.ViewWindow(out VC, VS: Double);
|
||||
begin
|
||||
VC := 0; VS := 0;
|
||||
@@ -547,7 +565,7 @@ begin
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.PositionHeaderChildren(RW, H: Integer);
|
||||
var BtnS: Integer;
|
||||
var BtnS, X: Integer;
|
||||
begin
|
||||
if FHeaderLabel <> nil then
|
||||
FHeaderLabel.Top := (H - FHeaderLabel.Height) div 2;
|
||||
@@ -561,11 +579,22 @@ begin
|
||||
FHeaderADCLabel.Top := (H - FHeaderADCLabel.Height) div 2;
|
||||
FHeaderADCLabel.Left := FHeaderRateLabel.Left + FHeaderRateLabel.Width + 10;
|
||||
end;
|
||||
// Кнопки шапки прижаты вправо, справа налево: [×] [⧉] [◑]. Позиция каждой
|
||||
// зависит от видимости соседей (у пана 0 нет × и ⧉ — ◑ уезжает к краю).
|
||||
BtnS := H - 4;
|
||||
if FBtnClosePan <> nil then
|
||||
FBtnClosePan.SetBounds(RW - BtnS - 2, 2, BtnS, BtnS);
|
||||
if FBtnPopPan <> nil then
|
||||
FBtnPopPan.SetBounds(RW - 2 * BtnS - 6, 2, BtnS, BtnS);
|
||||
X := RW - 2;
|
||||
if (FBtnClosePan <> nil) and FBtnClosePan.Visible then
|
||||
begin
|
||||
Dec(X, BtnS); FBtnClosePan.SetBounds(X, 2, BtnS, BtnS); Dec(X, 4);
|
||||
end;
|
||||
if (FBtnPopPan <> nil) and FBtnPopPan.Visible then
|
||||
begin
|
||||
Dec(X, BtnS); FBtnPopPan.SetBounds(X, 2, BtnS, BtnS); Dec(X, 4);
|
||||
end;
|
||||
if (FBtnDisplay <> nil) and FBtnDisplay.Visible then
|
||||
begin
|
||||
Dec(X, BtnS); FBtnDisplay.SetBounds(X, 2, BtnS, BtnS); Dec(X, 4);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.PositionPanZoomBar(X0, BottomY, RW, H: Integer;
|
||||
|
||||
+52
-3
@@ -110,6 +110,17 @@ type
|
||||
StackShare: Double; // доля высоты в стеке панов
|
||||
ColShare: Double; // доля ширины в грид-раскладке
|
||||
SplitRatio: Double; // доля высоты спектра (сплиттер спектр/водопад)
|
||||
// Per-pan дисплей (3.6): override глобального рендера. HasDisplay=False ⇒
|
||||
// пан наследует глобальный дефолт (поля игнорируются).
|
||||
HasDisplay: Boolean;
|
||||
DPalette: Integer; // 0=Classic/1=Inferno/2=Turbo
|
||||
DGamma: Double;
|
||||
DSpecRef: Double; // верх шкалы спектра, dBm
|
||||
DSpecRange: Double; // диапазон шкалы, dB
|
||||
DWfAGC: Boolean; // авто-уровень водопада
|
||||
DWfOffset: Double; // яркость водопада при AGC, dB
|
||||
DWfHigh: Double; // ручной верх водопада, dBm
|
||||
DWfLow: Double; // ручной низ водопада, dBm
|
||||
Slices: array of TSliceCfg;
|
||||
end;
|
||||
|
||||
@@ -1608,6 +1619,15 @@ begin
|
||||
P.Pans[i].StackShare := 1.0;
|
||||
P.Pans[i].ColShare := 1.0;
|
||||
P.Pans[i].SplitRatio := 0.40; // = TPanafallPanel дефолт
|
||||
P.Pans[i].HasDisplay := False;
|
||||
P.Pans[i].DPalette := 0;
|
||||
P.Pans[i].DGamma := 1.0;
|
||||
P.Pans[i].DSpecRef := -20.0;
|
||||
P.Pans[i].DSpecRange := 110.0;
|
||||
P.Pans[i].DWfAGC := True;
|
||||
P.Pans[i].DWfOffset := 0.0;
|
||||
P.Pans[i].DWfHigh := -60.0;
|
||||
P.Pans[i].DWfLow := -120.0;
|
||||
SetLength(P.Pans[i].Slices, 0);
|
||||
end;
|
||||
end;
|
||||
@@ -1615,7 +1635,7 @@ end;
|
||||
procedure TSettingsManager.SavePans(const MAC: array of Byte; const Ctx: string;
|
||||
const P: TPansConfig);
|
||||
var
|
||||
Root, PanObj, SliceObj: TJSONObject;
|
||||
Root, PanObj, SliceObj, DispObj: TJSONObject;
|
||||
PanArr, SliceArr: TJSONArray;
|
||||
i, j, Idx: Integer;
|
||||
begin
|
||||
@@ -1643,6 +1663,20 @@ begin
|
||||
PanObj.Add('stack_share', P.Pans[i].StackShare);
|
||||
PanObj.Add('col_share', P.Pans[i].ColShare);
|
||||
PanObj.Add('split_ratio', P.Pans[i].SplitRatio);
|
||||
// Per-pan дисплей: пишем блок только при override (иначе пан = глоб. дефолт).
|
||||
if P.Pans[i].HasDisplay then
|
||||
begin
|
||||
DispObj := TJSONObject.Create;
|
||||
PanObj.Add('display', DispObj);
|
||||
DispObj.Add('palette', P.Pans[i].DPalette);
|
||||
DispObj.Add('gamma', P.Pans[i].DGamma);
|
||||
DispObj.Add('spec_ref', P.Pans[i].DSpecRef);
|
||||
DispObj.Add('spec_range', P.Pans[i].DSpecRange);
|
||||
DispObj.Add('wf_agc', P.Pans[i].DWfAGC);
|
||||
DispObj.Add('wf_offset', P.Pans[i].DWfOffset);
|
||||
DispObj.Add('wf_high', P.Pans[i].DWfHigh);
|
||||
DispObj.Add('wf_low', P.Pans[i].DWfLow);
|
||||
end;
|
||||
SliceArr := TJSONArray.Create;
|
||||
PanObj.Add('slices', SliceArr);
|
||||
for j := 0 to High(P.Pans[i].Slices) do
|
||||
@@ -1667,8 +1701,8 @@ function TSettingsManager.LoadPans(const MAC: array of Byte; const Ctx: string;
|
||||
out P: TPansConfig): Boolean;
|
||||
var
|
||||
MacStr: string;
|
||||
DevObj, PansObj, Root, PanObj, SliceObj: TJSONObject;
|
||||
ListData, SlData: TJSONData;
|
||||
DevObj, PansObj, Root, PanObj, SliceObj, DispObj: TJSONObject;
|
||||
ListData, SlData, DispData: TJSONData;
|
||||
Arr, SArr: TJSONArray;
|
||||
i, id, k, n: Integer;
|
||||
begin
|
||||
@@ -1701,6 +1735,21 @@ begin
|
||||
P.Pans[id].StackShare := JD(PanObj, 'stack_share', 1.0);
|
||||
P.Pans[id].ColShare := JD(PanObj, 'col_share', 1.0);
|
||||
P.Pans[id].SplitRatio := EnsureRange(JD(PanObj, 'split_ratio', 0.40), 0.05, 0.95);
|
||||
// Per-pan дисплей: присутствие блока 'display' = override.
|
||||
DispData := PanObj.Find('display');
|
||||
if (DispData <> nil) and (DispData is TJSONObject) then
|
||||
begin
|
||||
DispObj := TJSONObject(DispData);
|
||||
P.Pans[id].HasDisplay := True;
|
||||
P.Pans[id].DPalette := EnsureRange(JI(DispObj, 'palette', 0), 0, 2);
|
||||
P.Pans[id].DGamma := EnsureRange(JD(DispObj, 'gamma', 1.0), 0.30, 3.00);
|
||||
P.Pans[id].DSpecRef := JD(DispObj, 'spec_ref', -20.0);
|
||||
P.Pans[id].DSpecRange := JD(DispObj, 'spec_range', 110.0);
|
||||
P.Pans[id].DWfAGC := JB(DispObj, 'wf_agc', True);
|
||||
P.Pans[id].DWfOffset := JD(DispObj, 'wf_offset', 0.0);
|
||||
P.Pans[id].DWfHigh := JD(DispObj, 'wf_high', -60.0);
|
||||
P.Pans[id].DWfLow := JD(DispObj, 'wf_low', -120.0);
|
||||
end;
|
||||
SlData := PanObj.Find('slices');
|
||||
if (SlData = nil) or not (SlData is TJSONArray) then Continue;
|
||||
SArr := TJSONArray(SlData);
|
||||
|
||||
@@ -305,6 +305,7 @@ type
|
||||
procedure InvalidateRulerCache; virtual;
|
||||
procedure ResetSpectrumBuf; virtual;
|
||||
procedure ResetWfAvgBuf; virtual;
|
||||
procedure ClearWaterfall; // сброс истории водопада (смена палитры/gamma)
|
||||
procedure FillDemoSpectrum; virtual;
|
||||
function NeedsRulerRedraw: Boolean; virtual;
|
||||
end;
|
||||
@@ -1250,6 +1251,14 @@ begin
|
||||
FWaterfall.ResetWfAvgBuf;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.ClearWaterfall;
|
||||
// Сброс истории водопада (GL: пересоздать текстуру). Нужен при смене палитры/
|
||||
// gamma: GL-водопад хранит УЖЕ раскрашенные пиксели, старые строки иначе
|
||||
// остаются в прежней палитре, и смена/сброс выглядят как «не сработало».
|
||||
begin
|
||||
FWaterfall.ResetWfBuf;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.FillDemoSpectrum;
|
||||
var i: Integer; FreqOff, Noise, Sig: Double; W: Integer;
|
||||
begin
|
||||
|
||||
+44
-1
@@ -712,13 +712,56 @@ RATEPROBE удалены.** Наблюдение юзера: смена rate Г
|
||||
- Авто-promotion (слайс уехал за край пана → предлагать новый пан) — нет,
|
||||
осознанно: только явное создание пана (решение «сначала статика» остаётся).
|
||||
- Настройки водопада (палитра/AGC/avg) per-pan или глобально — стартуем с
|
||||
наследования глобальных, разделим по запросу.
|
||||
наследования глобальных, разделим по запросу. ★РЕШЕНО/РЕАЛИЗОВАНО 2026-07-13
|
||||
(см. блок «PER-PAN ДИСПЛЕЙ» ниже).
|
||||
- **Компоновка панов сеткой** (запрос юзера 2026-07-11): не только вертикальный
|
||||
стек — например, пан 0 сверху во всю ширину, паны B и C ниже бок о бок
|
||||
(пол-ширины каждый), и т.п. Идея на после 3.3/3.5: обобщить `LayoutPanStack`
|
||||
до грид-раскладки (регион пана = прямоугольник, а не полоса; `StackTop/Height`
|
||||
→ `Rect`), сплиттеры по обеим осям. Не блокер текущего этапа.
|
||||
|
||||
**PER-PAN ДИСПЛЕЙ (2026-07-13, собирается, dispcfgtest ALL PASS, НЕ закоммичен,
|
||||
НА ЭКРАНЕ НЕ ПРОВЕРЕН)**
|
||||
|
||||
UX залочен с юзером: кнопка «◑» в шапке каждого пана открывает компактный
|
||||
поповер настроек отображения. Модель — **глобальный дефолт + override на пан**:
|
||||
- Пан 0 «◑» правит ГЛОБАЛЬНЫЕ поля рендера (`FController.FWf*`/`FSpec*`),
|
||||
зеркалятся в контроллер, persist глобальных при закрытии (FormDestroy →
|
||||
SaveGlobalSettings). Solo пан 0 (нет доп. панов ⇒ шапка скрыта) правится
|
||||
штатным диалогом настроек — «◑» виден только при видимой шапке (>=2 панов).
|
||||
- Паны N при первой правке ставят `DisplayOverride:=True`; их рендер
|
||||
персистится в воркспейс раздельно по контексту (КВ/xvtr).
|
||||
- «Reset» (только паны N) — снять override, вернуть глоб. дефолт.
|
||||
«Apply all» — раскатать текущий рендер пана на все паны + сделать глоб.
|
||||
дефолтом.
|
||||
|
||||
Что per-pan (view-side, дёшево — всё уже в `Pan.View`): палитра, gamma,
|
||||
ref level, range, водопад авто-уровень + offset + ручные min/max. Что осталось
|
||||
ГЛОБАЛЬНЫМ (движок/перф, не косметика): DisplayPixels (разрешение анализатора),
|
||||
MSAA, WfSpeedDiv, детектор/усреднение спектра и водопада.
|
||||
|
||||
Реализация:
|
||||
- Новый юнит **PanDisplayPopup.pas**: `TPanDisplayPopup` = borderless
|
||||
`fsStayOnTop` форма, гаснет по `OnDeactivate`; Flat*-контролы (combo палитры,
|
||||
float-spin gamma, int-spin уровней, чекбокс авто). Combo-дропдаун парентится
|
||||
к самой форме (child) ⇒ не деактивирует поповер. Правки применяются к
|
||||
`P.View` живьём → `OnApplied(P)`.
|
||||
- **TPanafallPanel**: `FBtnDisplay` («◑», Owner=FHeaderPanel ⇒ освобождается
|
||||
авто с шапкой, в Destroy не нужен), `OnDisplayClick`, `DisplayOverride`.
|
||||
`PositionHeaderChildren` каскадит `[×][⧉][◑]` справа налево по `Visible`.
|
||||
- **MainForm**: `OnPanDisplayClick` (лениво создаёт `FPanDisplayPopup`, показ
|
||||
под кнопкой), `OnPanDisplayApplied/Reset/ApplyAll`, `ApplyGlobalRenderToView`,
|
||||
`MarkPanDisplayDirty` (инвалидция сетки спектра + водопада; `SetWfPalette/
|
||||
Gamma` сами no-op-гардят и метят LUT к пересбору). Поповер прячется в
|
||||
`ClosePanadapter` (FPan мог висеть → перепривязка на след. показе).
|
||||
- **Settings/TPanCfg**: `HasDisplay` + `DPalette/DGamma/DSpecRef/DSpecRange/
|
||||
DWfAGC/DWfOffset/DWfHigh/DWfLow`; под-объект `'display'` пишется ТОЛЬКО при
|
||||
override; чтение = присутствие блока. Save/RestorePanWorkspaceCtx прокинуты.
|
||||
|
||||
Файлы: `PanDisplayPopup.pas` (new), `PanafallPanel.pas`, `MainForm.pas`,
|
||||
`Settings.pas`. NEXT: экранная проверка (палитра/gamma/уровни на пан, Reset/
|
||||
ApplyAll, тёплый рестарт восстанавливает override панов N раздельно КВ/xvtr).
|
||||
|
||||
## Отвергнутые альтернативы (для истории)
|
||||
|
||||
Старый черновик Этапа 3 предлагал «off-span слайсы»: слайс вне видимой
|
||||
|
||||
Reference in New Issue
Block a user