fix(scoreboard): the strip and the social bar now really share the frame

Reported: the ticker was painting OVER the socials. The coupling rule existed and was tested
(ScoreboardLayout.ResolveEdges) but nothing ever CALLED it — the strip's Y still came from its own
preference, so both bars landed on the south edge.

Now the resolution runs at publish time: the social bar keeps its side, the scoreboard takes the
other one, and its Y follows from that edge. The pair re-sorts whenever the social bar moves (the
preview click-toggle) or shows/hides, because the rule is "resolve from what is visible", not a
stored pairing.

The layer also gains an Edge preference (Top/Bottom) in its properties, labelled "socials take the
other" — a preference, never a claim on an occupied edge.

Also stabilised the opacity assertion: it samples a plate-only pixel 5px from the left edge instead
of the first semi-transparent pixel it finds (which could be anti-aliased text).
This commit is contained in:
2026-10-30 15:11:35 -07:00
parent e27e79962c
commit 882b8551d9
5 changed files with 40 additions and 17 deletions
+28 -10
View File
@@ -27,6 +27,22 @@ public partial class MainViewModel
/// <summary>Gap between the strip and the frame edge.</summary>
private const double ScoreboardEdgeMargin = 8;
private BarEdge _scoreboardPreferredEdge = BarEdge.Bottom;
/// <summary>The edge the creator asked for. It is only a PREFERENCE: the resolver gives the
/// scoreboard the edge the social bar does not hold (creator ruling, resolved at render time).</summary>
public BarEdge ScoreboardEdge
{
get => _scoreboardPreferredEdge;
set
{
if (!SetProperty(ref _scoreboardPreferredEdge, value)) return;
RefreshScoreboardPreviews();
}
}
public IReadOnlyList<BarEdge> ScoreboardEdgeOptions { get; } = new[] { BarEdge.Top, BarEdge.Bottom };
private Source? _scoreboardPreviewSource;
internal ScoreboardRecorder ScoreboardRecorder => _scoreboard;
@@ -219,16 +235,18 @@ public partial class MainViewModel
frame.BgraPixels, frame.Width * 4, 0);
source.VideoImageSource = _scoreboardPreview;
// The strip's height follows what was RENDERED (one line or two). A fixed 56px box
// squashed a wrapped strip instead of showing it, and its Y follows from the edge.
if (Math.Abs(source.Height - frame.Height) > 0.5)
{
source.Width = frame.Width;
source.Height = frame.Height;
source.Y = _scoreboardStyle is var _ && source.Y > 540
? 1080 - frame.Height - ScoreboardEdgeMargin
: ScoreboardEdgeMargin;
}
// The strip's box follows what was RENDERED (one or two lines), and its edge comes
// from the SHARED rule: the social bar keeps its side, the scoreboard takes the other.
var socialEdge = _socials?.BarPosition == SocialBarPosition.Top ? BarEdge.Top : BarEdge.Bottom;
var (_, resolved) = ScoreboardLayout.ResolveEdges(
socialVisible: SocialBarVisible, socialPreferred: socialEdge,
scoreboardVisible: true, scoreboardPreferred: _scoreboardPreferredEdge);
source.Width = frame.Width;
source.Height = frame.Height;
source.Y = resolved == BarEdge.Top
? ScoreboardEdgeMargin
: 1080 - frame.Height - ScoreboardEdgeMargin;
_scoreboardPreviewSource = source;
}
}