From c03acd0aa0c91855278bd589c52ca7b23dbfdc69 Mon Sep 17 00:00:00 2001 From: gramps Date: Fri, 30 Oct 2026 15:24:14 -0700 Subject: [PATCH] =?UTF-8?q?fix(scoreboard):=20the=20defaults=20were=20neve?= =?UTF-8?q?r=20applied=20=E2=80=94=20a=20record=20struct=20zero-initialise?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creator's defaults: white text, Background ON (black, 20%), speed capped at 200 px/s, text size shown in real pixels, and "plate" dropped from the label. The important find is underneath those: `ScoreboardStyle` is a record STRUCT, and `new ScoreboardStyle()` zero-initialises — it does NOT run the constructor's parameter defaults. So the shipped strip had null text colour, NO background and zero opacity, whatever the source said. That is why the Background checkbox and the opacity slider looked like they had no code behind them: the renderer was faithfully drawing the zeros it was handed. An explicit parameterless constructor now carries the real defaults, and the renderer test asserts them (white / on / black / 0.20 / max speed 200) so this class of silent-zero cannot return. Readouts fixed too: the opacity and text-size setters never raised their own property, so the numbers never moved while the render did. Text size now reads "24 px" instead of a bare slider value, and snaps to whole pixels. --- Controls/LeftPanel.xaml | 10 +++++----- Services/Compositor/ScoreboardPresenter.cs | 2 +- Services/Compositor/ScoreboardRenderer.cs | 13 +++++++++++-- ViewModels/MainViewModel.Scoreboard.cs | 8 ++++++++ ytLive.Tests/ScoreboardPropertiesTests.cs | 11 ++++++++++- ytLive.Tests/ScoreboardRendererTests.cs | 8 ++++++++ 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/Controls/LeftPanel.xaml b/Controls/LeftPanel.xaml index b0cd4b5..7b505c3 100644 --- a/Controls/LeftPanel.xaml +++ b/Controls/LeftPanel.xaml @@ -485,7 +485,7 @@ - @@ -514,7 +514,7 @@ ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name" SelectedValuePath="Hex" SelectedValue="{Binding ScoreboardTextColor, Mode=TwoWay}"/> - - - + diff --git a/Services/Compositor/ScoreboardPresenter.cs b/Services/Compositor/ScoreboardPresenter.cs index ebdce80..1155608 100644 --- a/Services/Compositor/ScoreboardPresenter.cs +++ b/Services/Compositor/ScoreboardPresenter.cs @@ -37,7 +37,7 @@ public sealed class ScoreboardPresenter public const double DefaultPixelsPerSecond = 120; public const double MinPixelsPerSecond = 20; - public const double MaxPixelsPerSecond = 400; + public const double MaxPixelsPerSecond = 200; // creator cap: faster than this is unreadable /// Advances the scroll by . The strip is a marquee: one /// measured run, cropped to a moving window, so a tick is a row-wise byte copy rather than a diff --git a/Services/Compositor/ScoreboardRenderer.cs b/Services/Compositor/ScoreboardRenderer.cs index 578d26f..def6f4d 100644 --- a/Services/Compositor/ScoreboardRenderer.cs +++ b/Services/Compositor/ScoreboardRenderer.cs @@ -15,9 +15,18 @@ public readonly record struct ScoreboardStyle( string TextColor = "#FFFFFF", bool BackgroundEnabled = true, string BackgroundColor = "#000000", - double BackgroundOpacity = 0.55, + double BackgroundOpacity = 0.20, double FontSize = ScoreboardLayout.MaxFontSize, - double SeparatorOpacity = 0.45); + double SeparatorOpacity = 0.45) +{ + /// A record STRUCT's implicit parameterless constructor zero-initialises — it does NOT + /// run the parameter defaults. Without this, `new ScoreboardStyle()` produced null text colour, + /// no background and zero opacity: every "default" in this file was a lie, and the plate/opacity + /// controls looked dead because the renderer was faithfully drawing what it was given. + public ScoreboardStyle() : this("#FFFFFF", true, "#000000", 0.20, ScoreboardLayout.MaxFontSize, 0.45) + { + } +} /// Rasterises the scoreboard strip (one line, shrinking/wrapping via /// ) into a straight-alpha BGRA frame the compositor blits. diff --git a/ViewModels/MainViewModel.Scoreboard.cs b/ViewModels/MainViewModel.Scoreboard.cs index b6a4560..8c8cedf 100644 --- a/ViewModels/MainViewModel.Scoreboard.cs +++ b/ViewModels/MainViewModel.Scoreboard.cs @@ -193,6 +193,9 @@ public partial class MainViewModel { _scoreboardStyle = _scoreboardStyle with { BackgroundOpacity = Math.Clamp(value, 0, 1) }; PushStyle(); + // The readout binds THIS property: without its own raise the number never moves even + // though the render does (creator: "bzzzt!"). + OnPropertyChanged(nameof(ScoreboardBackgroundOpacity)); } } @@ -206,9 +209,14 @@ public partial class MainViewModel FontSize = Math.Clamp(value, ScoreboardLayout.FloorFontSize, ScoreboardLayout.MaxFontSize), }; PushStyle(); + OnPropertyChanged(nameof(ScoreboardFontSize)); // same bug as the opacity readout + OnPropertyChanged(nameof(ScoreboardFontSizeLabel)); } } + /// "24 px" — the panel shows real pixels, not a bare slider number. + public string ScoreboardFontSizeLabel => $"{_scoreboardStyle.FontSize:0} px"; + /// The colours offered for text and plate: the app's one palette. public IReadOnlyList ScoreboardColorOptions => BorderColorPalette.All; diff --git a/ytLive.Tests/ScoreboardPropertiesTests.cs b/ytLive.Tests/ScoreboardPropertiesTests.cs index ce76603..a3cda0d 100644 --- a/ytLive.Tests/ScoreboardPropertiesTests.cs +++ b/ytLive.Tests/ScoreboardPropertiesTests.cs @@ -25,13 +25,22 @@ public class ScoreboardPropertiesTests Assert.Contains("Sign in to YouTube to use the scoreboard.", block); Assert.Contains("CanUseScoreboard, Converter={StaticResource InverseBoolToVis}", block); + // Defaults the creator asked for, and readouts that actually move. + Assert.Contains("Background", block); + Assert.DoesNotContain("Background plate", block); + Assert.Contains("ScoreboardFontSizeLabel", block); // "24 px", not a bare number + + var vm = SourceAssert.Text("ViewModels", "MainViewModel.Scoreboard.cs"); + Assert.Contains("OnPropertyChanged(nameof(ScoreboardBackgroundOpacity));", vm); + Assert.Contains("OnPropertyChanged(nameof(ScoreboardFontSize));", vm); + // Dev-only labels are ORANGE, so a development affordance cannot pass for product. Assert.Contains("Simulate (dev)", block); Assert.Contains("Brush.Attention", block); // Scroll speed: a real dial, persisted, with its range owned by the presenter. Assert.Contains("ScoreboardScrollSpeed", block); - Assert.Contains("Minimum=\"20\" Maximum=\"400\"", block); + Assert.Contains("Minimum=\"20\" Maximum=\"200\"", block); Assert.Contains("Scoreboard.ScrollSpeed", SourceAssert.Text("Services", "LayoutStore.Settings.cs")); // The master switch sits in the same panel: off hides the strip and stops the polling. diff --git a/ytLive.Tests/ScoreboardRendererTests.cs b/ytLive.Tests/ScoreboardRendererTests.cs index a972915..c20a0a6 100644 --- a/ytLive.Tests/ScoreboardRendererTests.cs +++ b/ytLive.Tests/ScoreboardRendererTests.cs @@ -17,6 +17,14 @@ public class ScoreboardRendererTests [Fact] public void The_strip_paints_the_plate_and_the_text_and_never_needs_a_scroll() { + // The shipped defaults: white text, background ON, black, 20%. + var defaults = new ScoreboardStyle(); + Assert.Equal("#FFFFFF", defaults.TextColor); + Assert.True(defaults.BackgroundEnabled); + Assert.Equal("#000000", defaults.BackgroundColor); + Assert.Equal(0.20, defaults.BackgroundOpacity, 3); + Assert.Equal(200, ytLive.Services.Compositor.ScoreboardPresenter.MaxPixelsPerSecond, 3); + Exception? failure = null; var thread = new Thread(() =>