using System; using System.Linq; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using ytLive.Helpers; using ytLive.Models; using ytLive.Services; using ytLive.Services.Compositor; namespace ytLive.ViewModels; /// The scoreboard strip: session tallies rendered as a full-width bar, per scene. /// /// It is a layer, so a creator gets the same eye toggle, /// drag and per-scene presence as every other element — "prominent on BRB, dialled back on Live" is /// geometry, not a second mechanism. public partial class MainViewModel { private readonly ScoreboardRecorder _scoreboard = new(); private ScoreboardTiles _scoreboardTiles = ScoreboardTiles.Default; private ScoreboardStyle _scoreboardStyle = new(); private ScoreboardPresenter? _scoreboardPresenter; private WriteableBitmap? _scoreboardPreview; private Source? _scoreboardPreviewSource; internal ScoreboardRecorder ScoreboardRecorder => _scoreboard; internal ScoreboardTiles ScoreboardTilesState => _scoreboardTiles; /// Composited strip frame for the output path (cached by the presenter). internal VideoFrame? ScoreboardFrame => _scoreboardPresenter?.Frame; internal void StartScoreboardSession() { _scoreboard.StartSession(); RefreshScoreboardPreviews(); } internal void EndScoreboardSession() { _scoreboard.EndSession(); RefreshScoreboardPreviews(); } private void InitScoreboard() { _scoreboardPresenter = new ScoreboardPresenter(_scoreboard, _scoreboardStyle) { // placeholder so SetTiles below drives the first render }; _scoreboardPresenter.SetTiles(_scoreboardTiles); _scoreboardPresenter.SetStyle(_scoreboardStyle); _scoreboard.Changed += RefreshScoreboardPreviews; } /// Snapshot of the strip presenters as themed properties, for the properties panel /// (unit 7) — kept here so the panel binds one object instead of six primitives. internal ScoreboardStyle ScoreboardStyleState => _scoreboardStyle; /// Publishes the strip to every scoreboard layer's preview element. The pump thread /// must never touch a WriteableBitmap, so this runs on the UI thread only. private void RefreshScoreboardPreviews() { var frame = _scoreboardPresenter?.Frame; foreach (var scene in Scenes) foreach (var source in scene.Elements.OfType().Where(s => s.IsScoreboard)) { if (frame is null) { source.VideoImageSource = null; continue; } if (_scoreboardPreview is null || _scoreboardPreview.PixelWidth != frame.Width || _scoreboardPreview.PixelHeight != frame.Height) { _scoreboardPreview = new WriteableBitmap(frame.Width, frame.Height, 96, 96, PixelFormats.Bgra32, null); } _scoreboardPreview.WritePixels(new System.Windows.Int32Rect(0, 0, frame.Width, frame.Height), frame.BgraPixels, frame.Width * 4, 0); source.VideoImageSource = _scoreboardPreview; _scoreboardPreviewSource = source; } } }