feat(slices): этап 3.3 — UI стек панадаптеров

Несколько панадаптеров в главном окне (кнопка «⊞», до MAX_PANS):
- TPanafallPanel: регион в стеке (StackTop/Height), шапка с «×»,
  pan-aware ViewWindow, FirstSliceId; явный crArrow GL-канвам
  (WA_NativeWindow-поверхность на Wayland навсегда наследует курсор
  момента создания — прилипала «рука» кнопки «⊞»)
- MainForm: FPans[]/LayoutPanStack (доли + межпанные сплиттеры с живым
  призраком), создание/закрытие панов (DDC+слайс+вьюха), пиксели панов
  из display-потока под FPanCbLock, рендер по dirty в тике
- Мышь панов N: Ctrl+ЛКМ = слайс, drag маркера = ретюн слайса,
  drag фона = live-ретюн DDC (гистерезис 4px), клик = тюн слайса пана
  (PanClickTune, снап 100 Гц), флаг-диспетчер только на спектре
- Сплиттер спектр/водопад: drag-математика от региона пана
  (FSplitTopOff/FSplitAvailH из Layout), OnSplitterMoved у панов N
- Паны живут в Run-сессии, умирают на STOP

Проверено юзером на экране (3 раунда фидбека) + e2e против hpsdrsim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 22:56:49 +03:00
co-authored by Claude Fable 5
parent dae0c19375
commit 95b4f749be
4 changed files with 1059 additions and 54 deletions
+99 -8
View File
@@ -89,7 +89,8 @@ SmartSDR НЕ умеет — наш pop-out будет улучшением по
один слайс в его центре (поведение Flex).
3. ADC-источник — свойство **панадаптера**, не слайса (бейдж `A1`/`A2` в шапке).
В отличие от Flex показываем именно АЦП, а не антенну: на HPSDR-платах
маппинг «вход→АЦП» жёсткий, антенная абстракция не нужна.
маппинг «вход→АЦП» жёсткий. Антенны ADC0 (Alex ANT1/2/3) — отдельная
ГЛОБАЛЬНАЯ ручка, общая для всех панов этого АЦП (детали в 3.4).
4. Буквы слайсов — глобальный пул A..G через все паны (как во Flex A-H):
буква = личность слайса, пан = место жительства.
5. Возможности бэкенда описывает запись capabilities (см. таблицу) — UI по ней
@@ -342,14 +343,99 @@ ChangeSampleRate).
независим от DDC, TX-частота = частота слайса. (Полный мультислайс-TX —
отдельная старая задача, тут ничего нового не ломаем.)
### 3.4 — ADC-селектор панадаптера
**СТАТУС 3.3 (2026-07-11): БАЗОВЫЙ UI РЕАЛИЗОВАН, собирается (GUI+демон),
бэкенд-тракт проверен END-TO-END против эмулятора hpsdrsim -hermes -P2
(scratchpad pantest2: DDC1 включается, IQ течёт, спектр/водопад пана рисуются,
слайс живой S-метр −95дБм, ретюн/снятие чистые, seq-ошибок нет). НА ЭКРАНЕ
НЕ ПРОВЕРЕНО.** Что сделано:
- `TPanafallPanel`: `PanId`, регион в стеке (`StackTop/StackHeight`, Layout
считает внутри региона), шапка (TPanel+TLabel+«×»→OnPanClose; видна при
панах ≥2), кнопка «⊞» в ряду пан/зума (`AddPanEnabled`, только пан 0),
`ShowZoomRow=False` у панов N (их зум позже), pan-aware `ViewWindow`
(SliceAtPixel/LayoutFlags от окна СВОЕГО пана), `FOnFlagsInvalidate(Self)`.
- MainForm: `FPans[0..MAX_PANS-1]` (пан 0 = FPan), `LayoutPanStack` (доли
`FPanShare` + межпанные сплиттеры, пересчёт по MouseUp), «⊞» →
`OnAddPanClick` (AddPanDDC на частоте активного VFO, rate 384к, автослайс
в центре, копия display-параметров view, ApplyDarkTheme), «×» шапки →
отложенное `ClosePanadapter` в тике (нельзя free из своей кнопки).
- Пиксели: `engine.OnPanSpectrum/OnPanWaterfall` → MainForm напрямую
(display-поток, `FPanCbLock` закрывает гонку с созданием/закрытием пана);
рендер в SpectrumTimerTick по dirty-флагам, `SyncPanViews` держит
центр/span вьюх и текст шапок.
- Мышь панов N (НОВЫЕ простые обработчики, VFO/CTUN/маяк/оверлеи главного
сюда не переносились — решение по B2 выполнено): Ctrl+ЛКМ = слайс в этом
пане, ЛКМ по маркеру = drag слайса (кламп полосой пана), ЛКМ по фону =
drag ЦЕНТРА пана (live-ретюн DDC), колесо над флагом любого пана = тюн
того слайса; флаги/CurSliceId/инвалидции обобщены на все панели
(PanOfOverlay/PushFlagStateAllPans/LayoutFlagsAllPans).
- Паны живут только в Run-сессии: на STOP UI убирает их вместе с контроллером.
- Отложено: пан-зум панов N, per-pan детектор/усреднение, персист (после 3.3),
wideband при маленьком регионе пана 0 может вылезти за регион (редкий кейс),
колесо над фоном пана N крутит главный VFO (как везде вне слайсов).
**ФИКСЫ ПО ПЕРВОМУ ЭКРАННОМУ ТЕСТУ (2026-07-11, фидбек юзера):**
1. **Сплиттер спектр/водопад был сломан при ≥2 панах**: drag-математика
(`SplitterMouseMove/Up`) считала доступную высоту от ВСЕГО родителя
(`FParent.ClientHeight`, `TopOff=0`), игнорируя регион пана в стеке,
шапку и wideband. Симптомы: у пана 0 ratio занижался (спектр сжимался
при тяге вниз), у пана 1 сплиттер прыгал в регион пана 0. Фикс: Layout
запоминает `FSplitTopOff/FSplitAvailH` (геометрия «спектр+водопад»
СВОЕГО региона), drag использует только их.
2. **Межпанный сплиттер** не давал обратной связи до отпускания — теперь
живой «призрак» (сам сплиттер едет за мышью, клампится 15%-минимумом
обоих панов, подсветка при захвате), доли пересчитываются по MouseUp
от финального положения призрака.
3. **Клик по фону пана N не перестраивал частоту** (только drag центра).
Теперь пресс без движения (<4px) = клик-тюн `PanClickTune`: слайс пана
(активный, если он этого пана, иначе первый; нет — создаётся) на частоту
клика со снапом 100 Гц и клампом полосой пана. Drag центра включается
после гистерезиса 4px, курсор `crSizeWE` — только с этого момента
(раньше менялся на голый пресс).
4. **Диспетчер флагов/маркеров на панах N** получал координаты ВОДОПАДА
(прямоугольники флагов — в системе спектра): клики в верхней части
водопада могли молча съедаться невидимым флагом. Теперь флаги/hover —
только для событий спектра, курсор на водопаде не трогаем.
5. **(2-й раунд фидбека)** У панов N сплиттер спектр/водопад «залипал
призраком» (спектр не пересчитывался, при следующем drag прыжок к
старому ratio): в `OnAddPanClick` не был подвешен `OnSplitterMoved`
(у пана 0 = `RightPanelResize` → полный re-layout). Подвешен тот же
обработчик. Пан 0 и межпанные сплиттеры юзер подтвердил рабочими.
6. **Курсор-«рука» на всей площади панов N (разгадан, 3-й раунд)**: юзер
заметил, что рука = курсор кнопки «⊞». Механизм: GL-канвы — QOpenGLWidget
с `WA_NativeWindow` = отдельная Wayland-поверхность; `crDefault` в LCL
означает «курсор не задан», и поверхность НАВСЕГДА наследует курсор,
бывший под мышью в момент создания виджета. Пан N создаётся кликом по
«⊞» (`crHandPoint` у TFlatButton) → рука прилипает; пан 0 создаётся на
старте (мышь нигде) → норм. Фикс: `Build` ставит спектру/водопаду ЯВНЫЙ
`crArrow` (реальный push), обработчики панов N восстанавливают `crArrow`,
не `crDefault`. ⚠Урок на 3.5 (pop-out) и вообще: любой динамически
создаваемый нативный GL-виджет должен получать явный курсор при создании.
Статус: собирается (GUI+демон), НА ЭКРАНЕ НЕ ПРОВЕРЕНО (юзер проверяет).
Открыто из фидбека: юзер видел курсор-«палец» на водопаде пана N — в коде
панов N `crHandPoint` нигде не ставится (подозрение: артефакт сломанной
раскладки, перекрытие чужим контролом); перепроверить после фиксов.
### 3.4 — ADC-селектор панадаптера (+ антенны Alex, уточнено 2026-07-11)
Источник сигнала пана — ДВЕ независимые ручки, не одна:
1. **АЦП (per-pan)**: `TPanafall.ADCSource: 0/1`; в шапке пана селектор/бейдж
`A1`/`A2`. Показывается только при `NumADCs=2` (детект платы уже есть —
`NumADCs` в ConfigureDDCs-пути). Переключение = `ConfigureDDCs` с новым
ADCSource для DDC этого пана. Пан 0 тоже можно перекинуть на ADC1 — это
закрывает сценарий старой ветки `feature/rx2-support` как частный случай.
2. **Антенна первого АЦП (глобальная, НЕ per-pan)**: у плат с Alex на ADC0
уже есть аппаратный селектор RX-антенны **ANT1/ANT2/ANT3** (+ EXT-входы) —
реализован per-band в Antenna/Alex-табе (`Alex.RxAnt[band]`, XVTR-override).
Селектор один на весь ADC0 ⇒ антенну нельзя сделать свойством пана: смена
антенны меняет её для ВСЕХ панов, сидящих на ADC0. В UI пана максимум —
бейдж текущей антенны (например `A1·ANT2`) и/или шорткат к глобальному
переключателю с явным пониманием, что это общая ручка. ADC1 (RX2-вход) —
отдельный разъём без Alex-коммутации.
- `TPanafall.ADCSource: 0/1`; в шапке пана селектор/бейдж `A1`/`A2`.
Показывается только при `NumADCs=2` (детект платы уже есть — `NumADCs`
в ConfigureDDCs-пути).
- Переключение = `ConfigureDDCs` с новым ADCSource для DDC этого пана.
- Пан 0 тоже можно перекинуть на ADC1 — это закрывает сценарий старой ветки
`feature/rx2-support` (вторая антенна) бесплатно, как частный случай.
- Diversity (sync-DDC, оба АЦП когерентно в один канал) — НЕ здесь; протокол
умеет, дизайну панов не мешает, отдельная будущая фича.
@@ -437,6 +523,11 @@ ChangeSampleRate).
осознанно: только явное создание пана (решение «сначала статика» остаётся).
- Настройки водопада (палитра/AGC/avg) per-pan или глобально — стартуем с
наследования глобальных, разделим по запросу.
- **Компоновка панов сеткой** (запрос юзера 2026-07-11): не только вертикальный
стек — например, пан 0 сверху во всю ширину, паны B и C ниже бок о бок
(пол-ширины каждый), и т.п. Идея на после 3.3/3.5: обобщить `LayoutPanStack`
до грид-раскладки (регион пана = прямоугольник, а не полоса; `StackTop/Height`
`Rect`), сплиттеры по обеим осям. Не блокер текущего этапа.
## Отвергнутые альтернативы (для истории)