NR/NB modes, SNB/ANF flags, and drive % were stored only in the widgets
themselves (Btn*.Tag, TrkDrive.Position) — the last hard blocker to a
headless/daemon mode, since without those controls the state had nowhere
to live and CAT/Web read them directly.
- NR/NB: BtnNR.Tag/BtnNB.Tag -> FController.FNRMode/FNBMode (Integer).
- SNB/ANF: BtnSNB.Tag/BtnANF.Tag (0/1) -> FController.FSNB/FANF (Boolean).
- Drive %: TrkDrive.Position -> new FController.FDrivePercent; the slider
is now pure input (TrkDriveChange pushes its value into the controller),
while FDriveLevel stays the derived hardware byte.
Buttons/sliders become pure view+input: click handlers and UpdateXButton
read/write the controller field; CAT getters, the web snapshot, and
GetSnapshot all source state from the controller, not the widgets.
No behavioral change. Builds clean (cocoa).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decouple MainForm from CAT/Web by introducing a headless core,
TRadioController, as the single home for radio state and engine objects.
This is groundwork for swappable UIs, a daemon (Web-only) mode, and a
future hardware control panel — each becomes a frontend/adapter over the
same controller API.
RadioController.pas (new):
- Radio state + engine objects (Network/DSP/Audio/Settings/Channels) as
public fields named exactly as in MainForm, so wiring is mechanical.
- Command API: absolute Set* plus relative *By/Up/Down (for encoders).
- OnStateChanged(field) notifications and GetSnapshot for frontend sync.
- Invoke/OnInvoke (TUIInvokeEvent) thread-marshalling seam (GUI uses
TThread.Synchronize; daemon supplies its own).
- Commands are scaffolding for now (update field + fire event); engine
logic stays in MainForm and migrates in later phases (TODO-wiring).
MainForm.pas:
- Owns FController; creates it first in FormCreate, frees it last in
FormDestroy. Engines are still created/freed by MainForm into the
controller's fields (ownership move is a later phase).
- ~1400 FXxx references redirected to FController.FXxx (implementation
only); dead field declarations removed from the interface.
No behavioral change — pure state relocation. Builds clean (cocoa).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>