From b577a0d67ad0e62316f3a30ac8587c310e3870f9 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Thu, 4 Jun 2026 11:01:39 +0300 Subject: [PATCH] Phase 3 (slice): event-driven UI plumbing + Mute through controller Vertical slice to establish the Phase 3 pattern before replicating it across all commands: logic moves into controller commands, the UI becomes event-driven, and frontends call commands instead of poking widgets. - TRadioController.SetMute now carries the real logic (state + FDSPEngine. SetMute), then fires OnStateChanged(rfMute). - MainForm subscribes via OnControllerState (controller -> UI). It restyles BtnMute from controller state. FSyncingFromController guards against re-entrancy when a programmatic widget change would retrigger OnChange. - BtnMuteClick -> FController.ToggleMute; SyncWebMute -> FController.SetMute. The button/web no longer own the mute logic or the restyle. Proves the command/event/refresh loop end-to-end (UI click and web both route through the controller). Remaining commands follow this pattern. Builds clean (cocoa); behavior unchanged. Co-Authored-By: Claude Opus 4.8 --- MainForm.pas | 37 ++++++++++++++++++++++++++++++------- RadioController.pas | 6 +++++- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index 3220a30..d1ae0a0 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -149,6 +149,9 @@ type // RadioController.pas. Поля состояния/движков теперь живут в // FController; обращения идут как FController.FXxx. ---- FController: TRadioController; + // True пока OnControllerState обновляет виджеты — чтобы программное + // изменение слайдера/кнопки не вызывало повторно команду из OnChange. + FSyncingFromController: Boolean; // ---- Network ---- FDevices: array of TDeviceItem; @@ -368,6 +371,7 @@ type // ---- Helpers ---- procedure DoInvoke(M: TThreadMethod); // маршалинг команд контроллера в UI-поток + procedure OnControllerState(Sender: TObject; Field: TRadioField); // контроллер → UI procedure BuildUI; function LeftPanelButtonLeft(PanelWidth, ColCount, ColIndex: Integer): Integer; function LeftPanelButtonWidth(PanelWidth, ColCount, ColIndex: Integer): Integer; @@ -1065,6 +1069,7 @@ begin // освобождаются здесь же (в полях FController) — Фаза 1 миграции. FController := TRadioController.Create; FController.OnInvoke := DoInvoke; + FController.OnStateChanged := OnControllerState; FController.FVfoA := 14200000; FController.FVfoB := 7100000; FController.FActiveVfo := 0; @@ -1410,6 +1415,28 @@ begin TThread.Synchronize(nil, M); end; +// Контроллер изменил состояние — обновляем соответствующие виджеты. +// Выполняется в UI-потоке (команды контроллера вызываются из UI-потока либо +// маршалятся адаптерами). FSyncingFromController защищает от рекурсии, когда +// программное изменение виджета триггерит его OnChange. +procedure TMainForm.OnControllerState(Sender: TObject; Field: TRadioField); +begin + FSyncingFromController := True; + try + case Field of + rfMute: + begin + StyleButton(BtnMute, FController.FMuted); + if FController.FMuted then BtnMute.Caption := 'UNMUTE' + else BtnMute.Caption := 'MUTE'; + BtnMute.Invalidate; + end; + end; + finally + FSyncingFromController := False; + end; +end; + procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin // Сохраняем позицию здесь — окно ещё полностью видимо и стабильно. @@ -5830,11 +5857,8 @@ end; procedure TMainForm.BtnMuteClick(Sender: TObject); begin - FController.FMuted := not FController.FMuted; - StyleButton(BtnMute, FController.FMuted); - if FController.FMuted then BtnMute.Caption := 'UNMUTE' - else BtnMute.Caption := 'MUTE'; - if FController.FWDSPReady then FController.FDSPEngine.SetMute(FController.FMuted); + if FSyncingFromController then Exit; + FController.ToggleMute; // логика (движок) + OnControllerState обновит кнопку end; procedure TMainForm.UpdateNRButton; @@ -6430,8 +6454,7 @@ end; procedure TMainForm.SyncWebMute; begin - if FWebSyncBool <> FController.FMuted then - BtnMuteClick(nil); + FController.SetMute(FWebSyncBool); // идемпотентно; OnControllerState обновит UI end; // ── CTUN ────────────────────────────────────────────────────────────────── diff --git a/RadioController.pas b/RadioController.pas index b845b50..4be8cda 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -397,7 +397,11 @@ procedure TRadioController.VolumeBy(Delta: Integer); begin SetVolume(FVolume + Delta); end; procedure TRadioController.SetMute(On_: Boolean); -begin FMuted := On_; Changed(rfMute); { TODO wiring } end; +begin + FMuted := On_; + if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMute(FMuted); + Changed(rfMute); +end; procedure TRadioController.ToggleMute; begin SetMute(not FMuted); end;