design: the on-stream graphics join the standard (OutputPalette)
The broadcast picture is part of the app, so it stops carrying its own colours. Alert cards, the ticker and the branding flash now paint from OutputPalette, which Mirrors DESIGN.md for the brand tokens and owns the alert box's six-event accent language (gold/violet/pink — content, not chrome; the standard has no purple and should not grow one, but they live in ONE file instead of 57 scattered literals). It is a separate type because the renderers run on the frame-pump thread and must not touch application resources. The duplication is made safe by OutputPaletteTests: it parses Themes/Tokens.xaml and fails if any mirrored token drifts, and fails if a renderer reintroduces a colour literal or "Segoe UI". Ticker and flash glyphs now use the bundled Inter instead of Segoe UI. Neon red is the standard live red — one red in the product, including the credit. Fixed on the way: the palette's font fields were static pack-URI initialisers, which throw TypeInitializationException in any host without a WPF Application (the test process caught it); they are lazy now with a name fallback.
This commit is contained in:
+19
-18
@@ -3,6 +3,7 @@ using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services.Compositor;
|
||||
|
||||
namespace ytLive.Services;
|
||||
|
||||
@@ -27,9 +28,9 @@ public sealed class AlertRenderer
|
||||
private static readonly Color White = Colors.White;
|
||||
private static readonly Color[] ConfettiPalette =
|
||||
{
|
||||
Color.FromRgb(0xf4, 0x2f, 0x5f), Color.FromRgb(0xff, 0xd1, 0x66),
|
||||
Color.FromRgb(0x38, 0xb6, 0xf8), Color.FromRgb(0xa7, 0x8b, 0xfa),
|
||||
Color.FromRgb(0x39, 0xd3, 0x53),
|
||||
OutputPalette.Crimson, OutputPalette.Gold,
|
||||
OutputPalette.Tertiary, OutputPalette.Violet,
|
||||
OutputPalette.Positive,
|
||||
};
|
||||
|
||||
public VideoFrame Render(ChatMessage e, double t01, int width, int height)
|
||||
@@ -70,10 +71,10 @@ public sealed class AlertRenderer
|
||||
var shownAmount = e.SuperChatDisplayString
|
||||
?? (e.SuperChatCurrency is { Length: > 0 } cur ? $"{cur}{e.SuperChatAmount:0.00}" : $"${e.SuperChatAmount:0.00}");
|
||||
var fontSize = cardH * 0.24;
|
||||
DrawCentered(ctx, shownAmount, cardCX, cardY + cardH * 0.24, fontSize, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW);
|
||||
DrawCentered(ctx, shownAmount, cardCX, cardY + cardH * 0.24, fontSize, FontWeights.Bold, OutputPalette.Gold, cardW);
|
||||
|
||||
DrawCentered(ctx, e.AuthorName, cardCX, cardY + cardH * 0.56, cardH * 0.13, FontWeights.SemiBold, White, cardW);
|
||||
DrawCentered(ctx, e.Message, cardCX, cardY + cardH * 0.74, cardH * 0.10, FontWeights.Normal, Color.FromRgb(0xdd, 0xff, 0xf0), cardW * 0.92);
|
||||
DrawCentered(ctx, e.Message, cardCX, cardY + cardH * 0.74, cardH * 0.10, FontWeights.Normal, OutputPalette.MintText, cardW * 0.92);
|
||||
|
||||
if (t >= EnterEnd && t <= ExitStart)
|
||||
{
|
||||
@@ -122,10 +123,10 @@ public sealed class AlertRenderer
|
||||
var desc = string.IsNullOrWhiteSpace(e.SuperStickerDescription) ? "SUPER STICKER" : e.SuperStickerDescription.ToUpperInvariant();
|
||||
DrawCentered(ctx, desc, cardCX, h / 2.0 - cardH * 0.12, cardH * 0.17, FontWeights.Bold, White, cardW * 0.9);
|
||||
|
||||
DrawCentered(ctx, e.AuthorName, cardCX, h / 2.0 + cardH * 0.16, cardH * 0.12, FontWeights.SemiBold, Color.FromRgb(0xff, 0xb3, 0xd9), cardW * 0.9);
|
||||
DrawCentered(ctx, e.AuthorName, cardCX, h / 2.0 + cardH * 0.16, cardH * 0.12, FontWeights.SemiBold, OutputPalette.Pink, cardW * 0.9);
|
||||
var tier = e.SuperChatDisplayString ?? (e.SuperChatCurrency is { Length: > 0 } cur ? $"{cur}{e.SuperChatAmount:0.00}" : null);
|
||||
if (!string.IsNullOrWhiteSpace(tier))
|
||||
DrawCentered(ctx, tier, cardCX, h / 2.0 + cardH * 0.32, cardH * 0.10, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.9);
|
||||
DrawCentered(ctx, tier, cardCX, h / 2.0 + cardH * 0.32, cardH * 0.10, FontWeights.Bold, OutputPalette.Gold, cardW * 0.9);
|
||||
|
||||
ctx.Pop();
|
||||
ctx.Pop();
|
||||
@@ -163,10 +164,10 @@ public sealed class AlertRenderer
|
||||
}
|
||||
|
||||
DrawCentered(ctx, e.AuthorName, cardCX, cardY + cardH * 0.36, cardH * 0.22, FontWeights.Bold, White, cardW * 0.9);
|
||||
DrawCentered(ctx, "JOINED THE MEMBERSHIP", cardCX, cardY + cardH * 0.62, cardH * 0.11, FontWeights.SemiBold, Color.FromRgb(0xf5, 0xd0, 0xfe), cardW * 0.9);
|
||||
DrawCentered(ctx, "JOINED THE MEMBERSHIP", cardCX, cardY + cardH * 0.62, cardH * 0.11, FontWeights.SemiBold, OutputPalette.Lilac, cardW * 0.9);
|
||||
|
||||
var level = string.IsNullOrWhiteSpace(e.MembershipLevel) ? "MEMBER" : e.MembershipLevel.ToUpperInvariant();
|
||||
DrawCentered(ctx, level, cardCX, cardY + cardH * 0.80, cardH * 0.10, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.9);
|
||||
DrawCentered(ctx, level, cardCX, cardY + cardH * 0.80, cardH * 0.10, FontWeights.Bold, OutputPalette.Gold, cardW * 0.9);
|
||||
}
|
||||
return CreateFrame(visual, w, h, dpi);
|
||||
}
|
||||
@@ -193,7 +194,7 @@ public sealed class AlertRenderer
|
||||
var count = Math.Max(1, e.GiftNewMembersCount);
|
||||
var spread = 1.0 - exit;
|
||||
var hero = count > 1 ? $"×{count}" : "MEMBERSHIP";
|
||||
DrawCentered(ctx, hero, cardCX, margin + cardH * 0.24, cardH * 0.26, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.8);
|
||||
DrawCentered(ctx, hero, cardCX, margin + cardH * 0.24, cardH * 0.26, FontWeights.Bold, OutputPalette.Gold, cardW * 0.8);
|
||||
DrawCentered(ctx, $"{e.AuthorName} is gifting", cardCX, margin + cardH * 0.52, cardH * 0.11, FontWeights.SemiBold, White, cardW * 0.9);
|
||||
|
||||
var chipCount = Math.Min(count, 6);
|
||||
@@ -204,7 +205,7 @@ public sealed class AlertRenderer
|
||||
var chipX = cardCX + (i - slot) * gap;
|
||||
var chipY = margin + cardH * 0.70 + Math.Sin((i + t * 6) * 2.4) * cardH * 0.015;
|
||||
ctx.DrawRoundedRectangle(
|
||||
new SolidColorBrush(Color.FromRgb(0xff, 0xf3, 0xc4)),
|
||||
new SolidColorBrush(OutputPalette.GoldPale),
|
||||
null,
|
||||
new Rect(chipX - cardH * 0.05, chipY, cardH * 0.10, cardH * 0.10),
|
||||
cardH * 0.03, cardH * 0.03);
|
||||
@@ -252,10 +253,10 @@ public sealed class AlertRenderer
|
||||
}
|
||||
}
|
||||
|
||||
DrawCentered(ctx, "GIFTED MEMBERSHIP", cardCX, margin + cardH * 0.22, cardH * 0.12, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.9);
|
||||
DrawCentered(ctx, "GIFTED MEMBERSHIP", cardCX, margin + cardH * 0.22, cardH * 0.12, FontWeights.Bold, OutputPalette.Gold, cardW * 0.9);
|
||||
var giver = string.IsNullOrWhiteSpace(e.GifterName) ? e.AuthorName : e.GifterName;
|
||||
DrawCentered(ctx, giver, cardCX, margin + cardH * 0.42, cardH * 0.20, FontWeights.Bold, White, cardW * 0.9);
|
||||
DrawCentered(ctx, "gave you a membership — celebrate!", cardCX, margin + cardH * 0.68, cardH * 0.11, FontWeights.Normal, Color.FromRgb(0xdb, 0xff, 0xf0), cardW * 0.9);
|
||||
DrawCentered(ctx, "gave you a membership — celebrate!", cardCX, margin + cardH * 0.68, cardH * 0.11, FontWeights.Normal, OutputPalette.MintText, cardW * 0.9);
|
||||
|
||||
ctx.Pop();
|
||||
ctx.Pop();
|
||||
@@ -283,17 +284,17 @@ public sealed class AlertRenderer
|
||||
DrawBrand(ctx, margin, cardY, cardW, cardH);
|
||||
|
||||
var hero = e.MilestoneMonths > 0 ? $"{e.MilestoneMonths}" : "MILESTONE";
|
||||
DrawCentered(ctx, hero, cardCX, cardY + cardH * 0.26, cardH * 0.30, FontWeights.Bold, Color.FromRgb(0xff, 0xdd, 0x66), cardW * 0.8);
|
||||
DrawCentered(ctx, hero, cardCX, cardY + cardH * 0.26, cardH * 0.30, FontWeights.Bold, OutputPalette.GoldBright, cardW * 0.8);
|
||||
|
||||
var sub = e.MilestoneMonths > 0 ? "MONTHS STRONG" : "SUPPORTING THE STREAM";
|
||||
DrawCentered(ctx, sub, cardCX, cardY + cardH * 0.52, cardH * 0.11, FontWeights.SemiBold, White, cardW * 0.9);
|
||||
DrawCentered(ctx, "— " + e.AuthorName + " —", cardCX, cardY + cardH * 0.70, cardH * 0.10, FontWeights.Normal, Color.FromRgb(0xff, 0xe8, 0xd0), cardW * 0.9);
|
||||
DrawCentered(ctx, "— " + e.AuthorName + " —", cardCX, cardY + cardH * 0.70, cardH * 0.10, FontWeights.Normal, OutputPalette.WarmCream, cardW * 0.9);
|
||||
|
||||
// Tenure bar: grows through the first half, retracts on exit.
|
||||
var barP = Clamp01((t - 0.10) / 0.55) * (1 - exit);
|
||||
var barW = cardW * 0.6 * barP;
|
||||
ctx.DrawRoundedRectangle(
|
||||
new SolidColorBrush(Color.FromRgb(0xff, 0xd1, 0x66)),
|
||||
new SolidColorBrush(OutputPalette.Gold),
|
||||
null,
|
||||
new Rect(cardCX - barW / 2, cardY + cardH * 0.84, barW, cardH * 0.045),
|
||||
4, 4);
|
||||
@@ -313,7 +314,7 @@ public sealed class AlertRenderer
|
||||
{
|
||||
var formatted = new FormattedText(
|
||||
Brand, Invariant, FlowDirection.LeftToRight,
|
||||
new Typeface(new FontFamily("Segoe UI"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
new Typeface(OutputPalette.Sans, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
Math.Max(9, h * 0.055),
|
||||
new SolidColorBrush(Color.FromArgb(0x88, 0xff, 0xff, 0xff)),
|
||||
96.0);
|
||||
@@ -324,7 +325,7 @@ public sealed class AlertRenderer
|
||||
{
|
||||
var formatted = new FormattedText(
|
||||
text, Invariant, FlowDirection.LeftToRight,
|
||||
new Typeface(new FontFamily("Segoe UI"), FontStyles.Normal, weight, FontStretches.Normal),
|
||||
new Typeface(OutputPalette.Sans, FontStyles.Normal, weight, FontStretches.Normal),
|
||||
size,
|
||||
new SolidColorBrush(color),
|
||||
96.0);
|
||||
|
||||
Reference in New Issue
Block a user