Creator ruling: the Scoreboard layer exists in every scene rather than being added five times. ScoreboardHealer.Ensure keeps exactly one per scene — healed on load like the Background and Brand logo — so a scene can never end up with two, and 'off' is the ordinary per-scene eye toggle rather than a delete. It is appended as the TOPMOST layer on purpose: a full-width strip draws over the scene, and inserting it above the background would break the Brand logo's documented invariant (directly above the background). That choice is also why the layer-reorder test still passes untouched. Also: a layer-list icon for the type, so the row is not blank. Guard: ScoreboardHealerTests — one per scene, topmost, exact strip geometry, idempotent, and a duplicate healed back to one.
57 lines
2.2 KiB
C#
57 lines
2.2 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace ytLive.Models;
|
|
|
|
/// <summary>Keeps exactly one Scoreboard layer in EVERY scene.
|
|
///
|
|
/// <para>Creator ruling (2026-10-30): "YT_ACTIVITY_TICKER … is available to all scenes." The
|
|
/// scoreboard is not something to add five times — it exists once per scene like the Background and
|
|
/// the Brand logo, and a creator turns it off per scene with the ordinary eye toggle. Removing it is
|
|
/// therefore not a delete: it heals on the next load.</para>
|
|
///
|
|
/// <para>Pure over a scene collection, so the rule is testable without a view-model.</para></summary>
|
|
public static class ScoreboardHealer
|
|
{
|
|
public const string LayerName = "Scoreboard";
|
|
|
|
/// <summary>The strip's default box: full width, a strip's height, and its Y for the bottom
|
|
/// edge. Geometry only — the EDGE itself is resolved against the social bar at render time.</summary>
|
|
public const double StripHeight = 56;
|
|
public const double BottomY = 1080 - StripHeight;
|
|
|
|
/// <summary>Ensures one scoreboard per scene, appended as the TOPMOST layer: it is a full-width
|
|
/// strip drawn over the scene, and inserting it above the background would break the Brand
|
|
/// logo's documented invariant ("directly above the background"). Returns true when anything
|
|
/// changed, so the caller can save.</summary>
|
|
public static bool Ensure(IEnumerable<Scene> scenes)
|
|
{
|
|
var changed = false;
|
|
foreach (var scene in scenes)
|
|
{
|
|
var existing = scene.Elements.OfType<Source>().Where(s => s.IsScoreboard).ToList();
|
|
|
|
// More than one: keep the first, drop the rest.
|
|
foreach (var extra in existing.Skip(1))
|
|
{
|
|
scene.Elements.Remove(extra);
|
|
changed = true;
|
|
}
|
|
if (existing.Count > 0) continue;
|
|
|
|
scene.Elements.Add(new Source
|
|
{
|
|
Name = LayerName,
|
|
Type = SourceType.Scoreboard,
|
|
IsEnabled = true,
|
|
X = 0,
|
|
Y = BottomY,
|
|
Width = 1920,
|
|
Height = StripHeight,
|
|
});
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
}
|