diff --git a/Controls/LeftPanel.xaml b/Controls/LeftPanel.xaml
index 1ef1ba3..ca0dd2d 100644
--- a/Controls/LeftPanel.xaml
+++ b/Controls/LeftPanel.xaml
@@ -473,6 +473,12 @@
+
+
+
Gap between the strip and the frame edge.
private const double ScoreboardEdgeMargin = 8;
+
+ private BarEdge _scoreboardPreferredEdge = BarEdge.Bottom;
+
+ /// 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).
+ public BarEdge ScoreboardEdge
+ {
+ get => _scoreboardPreferredEdge;
+ set
+ {
+ if (!SetProperty(ref _scoreboardPreferredEdge, value)) return;
+ RefreshScoreboardPreviews();
+ }
+ }
+
+ public IReadOnlyList 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;
}
}
diff --git a/ViewModels/MainViewModel.Socials.cs b/ViewModels/MainViewModel.Socials.cs
index 5a9955c..ea41b00 100644
--- a/ViewModels/MainViewModel.Socials.cs
+++ b/ViewModels/MainViewModel.Socials.cs
@@ -224,6 +224,7 @@ public partial class MainViewModel
private void NotifySocialsChanged()
{
RenderSocialBarFrame();
+ RefreshScoreboardPreviews(); // the pair re-sorts when the social bar shows/hides
OnPropertyChanged(nameof(SocialBarTextBrush));
OnPropertyChanged(nameof(SocialBarBackgroundBrush));
OnPropertyChanged(nameof(HasSocials));
@@ -238,6 +239,8 @@ public partial class MainViewModel
if (_socials == null || _socials.BarPosition == position) return;
_socials.BarPosition = position;
OnPropertyChanged(nameof(SocialBarTop));
+ // Coupled pair: moving the social bar moves the scoreboard to the other edge.
+ RefreshScoreboardPreviews();
ScheduleSave();
}
diff --git a/ytLive.Tests/ScoreboardRendererTests.cs b/ytLive.Tests/ScoreboardRendererTests.cs
index 9f397bb..a972915 100644
--- a/ytLive.Tests/ScoreboardRendererTests.cs
+++ b/ytLive.Tests/ScoreboardRendererTests.cs
@@ -55,12 +55,9 @@ public class ScoreboardRendererTests
// Opacity: 20% and 80% must produce visibly different plate transparency.
var faint = ScoreboardRenderer.Render(Tiles, new ScoreboardStyle("#FFFFFF", true, "#0000FF", 0.2));
var solid = ScoreboardRenderer.Render(Tiles, new ScoreboardStyle("#FFFFFF", true, "#0000FF", 0.8));
- int AlphaOf(VideoFrame f)
- {
- for (var i = 3; i < f.BgraPixels.Length; i += 4)
- if (f.BgraPixels[i] is > 10 and < 245) return f.BgraPixels[i];
- return 0;
- }
+ // Sample a plate-only pixel (5px in from the left edge, vertical middle): the text is
+ // centred, so nothing anti-aliased can pollute the alpha reading.
+ static int AlphaOf(VideoFrame f) => f.BgraPixels[((f.Height / 2) * f.Width + 5) * 4 + 3];
Assert.InRange(AlphaOf(faint!), 35, 70); // ~20% of 255
Assert.InRange(AlphaOf(solid!), 190, 220); // ~80% of 255
Assert.True(AlphaOf(solid!) > AlphaOf(faint!) + 100, "the opacity slider must move the alpha");
diff --git a/ytLive.Tests/ScoreboardSimulationTests.cs b/ytLive.Tests/ScoreboardSimulationTests.cs
index c76cae7..6b85044 100644
--- a/ytLive.Tests/ScoreboardSimulationTests.cs
+++ b/ytLive.Tests/ScoreboardSimulationTests.cs
@@ -43,7 +43,6 @@ public class ScoreboardSimulationTests
Assert.NotNull(type.GetProperty("ScoreboardSimulate"));
Assert.NotNull(type.GetProperty("IsDevelopmentBuild"));
// DEBUG-gated at compile time, so a Release build has no dev control at all.
- var vmSource = SourceAssert.Text("ViewModels", "MainViewModel.Scoreboard.cs");
Assert.Contains("#if DEBUG", vmSource);
Assert.Contains("private const bool DevelopmentBuild = true;", vmSource);
Assert.Contains("private const bool DevelopmentBuild = false;", vmSource);