using System.Buffers.Binary;
using System.IO.Compression;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Core.Tests;
public sealed class WindowChromeRasterTests
{
private static readonly DesktopBounds Window = new(20, 16, 360, 220);
private sealed class Bitmap(int scale, uint background) : IDesktopCoverageCanvas
{
public int Width => 420;
public int Height => 280;
public int NativeWidth => Width * scale;
public int NativeHeight => Height * scale;
public int ScalePercent => scale * 100;
public uint[] Pixels { get; } = Enumerable.Repeat(background, 420 * 280 * scale * scale).ToArray();
public void Fill(uint rgb, int x, int y, int width, int height)
{
for (int row = Math.Max(0, y * scale); row < Math.Min(NativeHeight, (y + height) * scale); row++)
for (int col = Math.Max(0, x * scale); col < Math.Min(NativeWidth, (x + width) * scale); col++) Pixels[row * NativeWidth + col] = rgb;
}
public void BlendCoverage(uint rgb, int x, int y, int width, int height, ReadOnlySpan<byte> mask)
{
Assert.Equal(width * height, mask.Length);
for (int row = Math.Max(0, y); row < Math.Min(NativeHeight, y + height); row++)
for (int col = Math.Max(0, x); col < Math.Min(NativeWidth, x + width); col++)
{
byte alpha = mask[(row - y) * width + col - x];
if (alpha != 0) Pixels[row * NativeWidth + col] = Mix(Pixels[row * NativeWidth + col], rgb, alpha);
}
}
public uint At(double x, double y) => Pixels[(int)(y * scale) * NativeWidth + (int)(x * scale)];
public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new InvalidOperationException("Native strokes must use alpha coverage.");
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
private static uint Mix(uint background, uint color, int alpha)
{
uint result = 0;
for (int shift = 0; shift <= 16; shift += 8)
result |= (uint)(((color >> shift & 255) * alpha + (background >> shift & 255) * (255 - alpha) + 127) / 255) << shift;
return result;
}
private static Bitmap Render(int scale, DesktopTheme theme, double min = 0, double close = 0,
double title = 0, double resize = 0, bool disabledResize = false)
{
var canvas = new Bitmap(scale, theme.Panel);
DesktopWindowChromeRenderer.Draw(canvas, Window, theme, true, min, close, title, resize, disabledResize);
return canvas;
}
private static uint[] Region(Bitmap canvas, DesktopBounds bounds)
{
int scale = canvas.ScalePercent / 100;
return Enumerable.Range(bounds.Y * scale, bounds.Height * scale)
.SelectMany(row => canvas.Pixels.AsSpan(row * canvas.NativeWidth + bounds.X * scale, bounds.Width * scale).ToArray()).ToArray();
}
[Theory]
[InlineData(1, DesktopThemeMode.Light)]
[InlineData(2, DesktopThemeMode.Light)]
[InlineData(1, DesktopThemeMode.Dark)]
[InlineData(2, DesktopThemeMode.Dark)]
public void CaptionControlsShareCenterlineAndCloseHasPermanentWhiteCircle(int scale, DesktopThemeMode mode)
{
var theme = new DesktopTheme(mode);
var canvas = Render(scale, theme);
double right = Window.X + Window.Width, cy = Window.Y + 16;
Assert.Equal(0xFFFFFFu, canvas.At(right - 16, cy - 8));
Assert.Equal(0xFFFFFFu, canvas.At(right - 16, cy + 8));
Assert.NotEqual(theme.Panel, canvas.At(right - 48, cy));
Assert.Equal(theme.Panel, canvas.At(right - 48, cy - 7));
Assert.Equal(theme.Panel, canvas.At(right - 48, cy + 7));
Assert.Contains(Region(canvas, DesktopWindowChromeRenderer.CaptionDamage(Window, true)),
value => value != theme.Panel && value != 0xFFFFFF && value != theme.AccentStroke);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void CaptionIntermediateFramesDifferFromBothEndpoints(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(scale, theme);
var middle = Render(scale, theme, .25, .25);
var complete = Render(scale, theme, 1, 1);
var minBounds = DesktopWindowChromeRenderer.CaptionDamage(Window, false);
var closeBounds = DesktopWindowChromeRenderer.CaptionDamage(Window, true);
Assert.False(Region(idle, minBounds).SequenceEqual(Region(middle, minBounds)));
Assert.False(Region(complete, minBounds).SequenceEqual(Region(middle, minBounds)));
Assert.False(Region(idle, closeBounds).SequenceEqual(Region(middle, closeBounds)));
Assert.True(Region(idle, closeBounds).SequenceEqual(Region(complete, closeBounds))); // Full 180-degree X symmetry.
Assert.Equal(theme.Panel, complete.At(Window.X + Window.Width - 54, Window.Y + 16));
Assert.NotEqual(theme.Panel, complete.At(Window.X + Window.Width - 48, Window.Y + 21));
}
[Fact]
public void CaptionInkRemainsVisibleInsideWhiteCircleForCustomWhiteDarkAccent()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark, 0xFFFFFF);
var canvas = Render(2, theme);
uint center = canvas.At(Window.X + Window.Width - 16, Window.Y + 16);
Assert.True(DesktopTheme.Contrast(center, 0xFFFFFF) >= 3);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TitleDoubleBarsAndBothCaptionControlsShareTheSameCenterline(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var canvas = Render(scale, theme);
double CenterOfInk(DesktopBounds bounds)
{
double yTotal = 0; int count = 0;
for (int row = bounds.Y * scale; row < (bounds.Y + bounds.Height) * scale; row++)
for (int col = bounds.X * scale; col < (bounds.X + bounds.Width) * scale; col++)
if (canvas.Pixels[row * canvas.NativeWidth + col] != theme.Panel)
{ yTotal += (row + .5) / scale; count++; }
Assert.True(count > 0);
return yTotal / count;
}
Assert.Equal(Window.Y + 16, CenterOfInk(DesktopWindowChromeRenderer.TitleDamage(Window)), 5);
Assert.Equal(Window.Y + 16, CenterOfInk(DesktopWindowChromeRenderer.CaptionDamage(Window, false)), 5);
Assert.Equal(Window.Y + 16, CenterOfInk(DesktopWindowChromeRenderer.CaptionDamage(Window, true)), 5);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TitleAndResizePanelsFadeAgainstActualBackgroundWithoutOpaqueWhiteTiles(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(scale, theme);
var half = Render(scale, theme, title: .5, resize: .5);
var full = Render(scale, theme, title: 1, resize: 1);
double cx = Window.X + Window.Width / 2d;
Assert.Equal(theme.Panel, idle.At(cx + 25, Window.Y + 20));
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 20), half.At(cx + 25, Window.Y + 20));
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 40), full.At(cx + 25, Window.Y + 20));
double right = Window.X + Window.Width, bottom = Window.Y + Window.Height;
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 44), full.At(right - 13, bottom - 13));
Assert.NotEqual(0xFFFFFFu, full.At(right - 13, bottom - 13));
Assert.Equal(theme.Panel, full.At(right - 30, bottom - 15));
Assert.True(Region(full, DesktopWindowChromeRenderer.TitleDamage(Window)).SequenceEqual(
Region(Render(scale, theme, title: 1, resize: 1), DesktopWindowChromeRenderer.TitleDamage(Window))));
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void DefaultResizeAffordanceFollowsQuarterRadiusAndDisablesForMaximizedWindow(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var normal = Render(scale, theme);
var maximized = Render(scale, theme, disabledResize: true);
var bounds = DesktopWindowChromeRenderer.ResizeDamage(Window);
Assert.Contains(Region(normal, bounds), value => value != theme.Panel);
Assert.All(Region(maximized, bounds), value => Assert.Equal(theme.Panel, value));
double centerX = Window.X + Window.Width - 18, centerY = Window.Y + Window.Height - 18;
for (int row = bounds.Y * scale; row < (bounds.Y + bounds.Height) * scale; row++)
for (int col = bounds.X * scale; col < (bounds.X + bounds.Width) * scale; col++)
{
if (normal.Pixels[row * normal.NativeWidth + col] == theme.Panel) continue;
double x = (col + .5) / scale - centerX, y = (row + .5) / scale - centerY;
Assert.InRange(Math.Sqrt(x * x + y * y), 13.5, 17.5);
}
}
[Fact]
public void AnimationChangesStayInsideDeclaredSmallDamageRegions()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(2, theme);
var active = Render(2, theme, .4, .3, .7, .8);
var regions = new[] { DesktopWindowChromeRenderer.TitleDamage(Window),
DesktopWindowChromeRenderer.CaptionDamage(Window, false), DesktopWindowChromeRenderer.CaptionDamage(Window, true),
DesktopWindowChromeRenderer.ResizeDamage(Window) };
Assert.InRange(regions[0].Width, Window.Width * 2 / 3, Window.Width * 2 / 3 + 2);
for (int row = 0; row < active.NativeHeight; row++)
for (int col = 0; col < active.NativeWidth; col++)
if (active.Pixels[row * active.NativeWidth + col] != idle.Pixels[row * active.NativeWidth + col])
Assert.Contains(regions, region => region.Contains(col / 2, row / 2));
}
[Fact]
public void PressedFeedbackChangesOnlyCaptionGlyphsAndCircleWithoutSquareTiles()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(2, theme); var pressed = new Bitmap(2, theme.Panel);
DesktopWindowChromeRenderer.Draw(pressed, Window, theme, true, 0, 0, 0, 0,
minimizePressed: true, closePressed: true);
var minimize = DesktopWindowChromeRenderer.CaptionDamage(Window, false);
var close = DesktopWindowChromeRenderer.CaptionDamage(Window, true);
Assert.False(Region(idle, minimize).SequenceEqual(Region(pressed, minimize)));
Assert.False(Region(idle, close).SequenceEqual(Region(pressed, close)));
Assert.DoesNotContain(0xFFFFFFu, Region(pressed, minimize));
for (int row = 0; row < pressed.NativeHeight; row++)
for (int col = 0; col < pressed.NativeWidth; col++)
if (pressed.Pixels[row * pressed.NativeWidth + col] != idle.Pixels[row * idle.NativeWidth + col])
Assert.True(minimize.Contains(col / 2, row / 2) || close.Contains(col / 2, row / 2));
}
[Fact]
public void LightHoverPanelHasSubtleThemeEdgeWithoutChangingItsAreaOrWhiteInterior()
{
var theme = new DesktopTheme(DesktopThemeMode.Light);
var idle = Render(2, theme); var hover = Render(2, theme, title: 1);
double center = Window.X + Window.Width / 2d;
double left = center - (Window.Width * 2 / 3) / 2d;
uint edge = hover.At(left, Window.Y + 10);
Assert.True((idle.At(left, Window.Y + 10) >> 16 & 255) - (edge >> 16 & 255) > 5);
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 40), hover.At(center + 25, Window.Y + 20));
var damage = DesktopWindowChromeRenderer.TitleDamage(Window);
for (int row = 0; row < hover.NativeHeight; row++)
for (int col = 0; col < hover.NativeWidth; col++)
if (hover.Pixels[row * hover.NativeWidth + col] != idle.Pixels[row * idle.NativeWidth + col])
Assert.True(damage.Contains(col / 2, row / 2));
}
[Fact]
public void RotatedTwoHundredPercentStrokeIsRasterizedAtNativePixelPrecision()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var frame = Render(2, theme, min: .25);
var bounds = DesktopWindowChromeRenderer.CaptionDamage(Window, false);
int varied = 0;
for (int row = bounds.Y * 2; row < (bounds.Y + bounds.Height) * 2; row += 2)
for (int col = bounds.X * 2; col < (bounds.X + bounds.Width) * 2; col += 2)
{
uint value = frame.Pixels[row * frame.NativeWidth + col];
if (value != frame.Pixels[row * frame.NativeWidth + col + 1] ||
value != frame.Pixels[(row + 1) * frame.NativeWidth + col] ||
value != frame.Pixels[(row + 1) * frame.NativeWidth + col + 1]) varied++;
}
Assert.True(varied > 12);
}
private sealed class Probe : IDesktopCoverageCanvas
{
public int Width => 420;
public int Height => 280;
public int ScalePercent => 200;
public void Fill(uint rgb, int x, int y, int width, int height) { }
public void BlendCoverage(uint rgb, int x, int y, int width, int height, ReadOnlySpan<byte> mask) { }
public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new InvalidOperationException();
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
[Fact]
public void WarmQuantizedAnimationDoesNotAllocatePerFrameMasks()
{
var canvas = new Probe(); var theme = new DesktopTheme();
for (int pass = 0; pass < 2; pass++)
for (int step = 0; step <= DesktopWindowChromeRenderer.AnimationSteps; step++)
DesktopWindowChromeRenderer.Draw(canvas, Window, theme, true, step / 32d, step / 32d, step / 32d, step / 32d);
long before = GC.GetAllocatedBytesForCurrentThread();
for (int frame = 0; frame < 200; frame++)
{
double phase = frame % 33 / 32d;
DesktopWindowChromeRenderer.Draw(canvas, Window, theme, true, phase, phase, phase, phase);
}
Assert.Equal(0, GC.GetAllocatedBytesForCurrentThread() - before);
}
[Fact]
public void OptionalNativeInspectionPreviewUsesProductionRenderer()
{
string? directory = Environment.GetEnvironmentVariable("XFE_CHROME_PROOF_PATH");
if (directory is null) return;
Assert.True(Path.IsPathFullyQualified(directory)); Directory.CreateDirectory(directory);
foreach (int scale in new[] { 1, 2 })
foreach (DesktopThemeMode mode in Enum.GetValues<DesktopThemeMode>())
foreach (int step in new[] { 0, 8, 32 })
{
var bitmap = Render(scale, new DesktopTheme(mode), step / 32d, step / 32d, step / 32d, step / 32d);
WritePng(Path.Combine(directory, $"Chrome-{mode}-{scale}x-{step}.png"), bitmap.NativeWidth, bitmap.NativeHeight, bitmap.Pixels);
}
}
private static void WritePng(string path, int width, int height, uint[] pixels)
{
using var output = File.Create(path); output.Write(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 });
Span<byte> header = stackalloc byte[13]; header.Clear();
BinaryPrimitives.WriteUInt32BigEndian(header, (uint)width); BinaryPrimitives.WriteUInt32BigEndian(header[4..], (uint)height);
header[8] = 8; header[9] = 2; Chunk(output, "IHDR"u8, header);
using var compressed = new MemoryStream();
using (var zlib = new ZLibStream(compressed, CompressionLevel.Fastest, true))
for (int row = 0; row < height; row++)
{
zlib.WriteByte(0);
for (int col = 0; col < width; col++)
{
uint rgb = pixels[row * width + col];
zlib.WriteByte((byte)(rgb >> 16)); zlib.WriteByte((byte)(rgb >> 8)); zlib.WriteByte((byte)rgb);
}
}
Chunk(output, "IDAT"u8, compressed.ToArray()); Chunk(output, "IEND"u8, []);
}
private static void Chunk(Stream output, ReadOnlySpan<byte> name, ReadOnlySpan<byte> bytes)
{
Span<byte> word = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(word, (uint)bytes.Length);
output.Write(word); output.Write(name); output.Write(bytes); uint crc = uint.MaxValue;
foreach (byte value in name) crc = Crc(crc, value);
foreach (byte value in bytes) crc = Crc(crc, value);
BinaryPrimitives.WriteUInt32BigEndian(word, ~crc); output.Write(word);
}
private static uint Crc(uint crc, byte value)
{
crc ^= value;
for (int bit = 0; bit < 8; bit++) crc = crc >> 1 ^ ((crc & 1) == 0 ? 0 : 0xEDB88320u);
return crc;
}
}
using System.Buffers.Binary;
using System.IO.Compression;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Core.Tests;
public sealed class WindowChromeRasterTests
{
private static readonly DesktopBounds Window = new(20, 16, 360, 220);
private sealed class Bitmap(int scale, uint background) : IDesktopCoverageCanvas
{
public int Width => 420;
public int Height => 280;
public int NativeWidth => Width * scale;
public int NativeHeight => Height * scale;
public int ScalePercent => scale * 100;
public uint[] Pixels { get; } = Enumerable.Repeat(background, 420 * 280 * scale * scale).ToArray();
public void Fill(uint rgb, int x, int y, int width, int height)
{
for (int row = Math.Max(0, y * scale); row < Math.Min(NativeHeight, (y + height) * scale); row++)
for (int col = Math.Max(0, x * scale); col < Math.Min(NativeWidth, (x + width) * scale); col++) Pixels[row * NativeWidth + col] = rgb;
}
public void BlendCoverage(uint rgb, int x, int y, int width, int height, ReadOnlySpan<byte> mask)
{
Assert.Equal(width * height, mask.Length);
for (int row = Math.Max(0, y); row < Math.Min(NativeHeight, y + height); row++)
for (int col = Math.Max(0, x); col < Math.Min(NativeWidth, x + width); col++)
{
byte alpha = mask[(row - y) * width + col - x];
if (alpha != 0) Pixels[row * NativeWidth + col] = Mix(Pixels[row * NativeWidth + col], rgb, alpha);
}
}
public uint At(double x, double y) => Pixels[(int)(y * scale) * NativeWidth + (int)(x * scale)];
public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new InvalidOperationException("Native strokes must use alpha coverage.");
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
private static uint Mix(uint background, uint color, int alpha)
{
uint result = 0;
for (int shift = 0; shift <= 16; shift += 8)
result |= (uint)(((color >> shift & 255) * alpha + (background >> shift & 255) * (255 - alpha) + 127) / 255) << shift;
return result;
}
private static Bitmap Render(int scale, DesktopTheme theme, double min = 0, double close = 0,
double title = 0, double resize = 0, bool disabledResize = false)
{
var canvas = new Bitmap(scale, theme.Panel);
DesktopWindowChromeRenderer.Draw(canvas, Window, theme, true, min, close, title, resize, disabledResize);
return canvas;
}
private static uint[] Region(Bitmap canvas, DesktopBounds bounds)
{
int scale = canvas.ScalePercent / 100;
return Enumerable.Range(bounds.Y * scale, bounds.Height * scale)
.SelectMany(row => canvas.Pixels.AsSpan(row * canvas.NativeWidth + bounds.X * scale, bounds.Width * scale).ToArray()).ToArray();
}
[Theory]
[InlineData(1, DesktopThemeMode.Light)]
[InlineData(2, DesktopThemeMode.Light)]
[InlineData(1, DesktopThemeMode.Dark)]
[InlineData(2, DesktopThemeMode.Dark)]
public void CaptionControlsShareCenterlineAndCloseHasPermanentWhiteCircle(int scale, DesktopThemeMode mode)
{
var theme = new DesktopTheme(mode);
var canvas = Render(scale, theme);
double right = Window.X + Window.Width, cy = Window.Y + 16;
Assert.Equal(0xFFFFFFu, canvas.At(right - 16, cy - 8));
Assert.Equal(0xFFFFFFu, canvas.At(right - 16, cy + 8));
Assert.NotEqual(theme.Panel, canvas.At(right - 48, cy));
Assert.Equal(theme.Panel, canvas.At(right - 48, cy - 7));
Assert.Equal(theme.Panel, canvas.At(right - 48, cy + 7));
Assert.Contains(Region(canvas, DesktopWindowChromeRenderer.CaptionDamage(Window, true)),
value => value != theme.Panel && value != 0xFFFFFF && value != theme.AccentStroke);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void CaptionIntermediateFramesDifferFromBothEndpoints(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(scale, theme);
var middle = Render(scale, theme, .25, .25);
var complete = Render(scale, theme, 1, 1);
var minBounds = DesktopWindowChromeRenderer.CaptionDamage(Window, false);
var closeBounds = DesktopWindowChromeRenderer.CaptionDamage(Window, true);
Assert.False(Region(idle, minBounds).SequenceEqual(Region(middle, minBounds)));
Assert.False(Region(complete, minBounds).SequenceEqual(Region(middle, minBounds)));
Assert.False(Region(idle, closeBounds).SequenceEqual(Region(middle, closeBounds)));
Assert.True(Region(idle, closeBounds).SequenceEqual(Region(complete, closeBounds))); // Full 180-degree X symmetry.
Assert.Equal(theme.Panel, complete.At(Window.X + Window.Width - 54, Window.Y + 16));
Assert.NotEqual(theme.Panel, complete.At(Window.X + Window.Width - 48, Window.Y + 21));
}
[Fact]
public void CaptionInkRemainsVisibleInsideWhiteCircleForCustomWhiteDarkAccent()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark, 0xFFFFFF);
var canvas = Render(2, theme);
uint center = canvas.At(Window.X + Window.Width - 16, Window.Y + 16);
Assert.True(DesktopTheme.Contrast(center, 0xFFFFFF) >= 3);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TitleDoubleBarsAndBothCaptionControlsShareTheSameCenterline(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var canvas = Render(scale, theme);
double CenterOfInk(DesktopBounds bounds)
{
double yTotal = 0; int count = 0;
for (int row = bounds.Y * scale; row < (bounds.Y + bounds.Height) * scale; row++)
for (int col = bounds.X * scale; col < (bounds.X + bounds.Width) * scale; col++)
if (canvas.Pixels[row * canvas.NativeWidth + col] != theme.Panel)
{ yTotal += (row + .5) / scale; count++; }
Assert.True(count > 0);
return yTotal / count;
}
Assert.Equal(Window.Y + 16, CenterOfInk(DesktopWindowChromeRenderer.TitleDamage(Window)), 5);
Assert.Equal(Window.Y + 16, CenterOfInk(DesktopWindowChromeRenderer.CaptionDamage(Window, false)), 5);
Assert.Equal(Window.Y + 16, CenterOfInk(DesktopWindowChromeRenderer.CaptionDamage(Window, true)), 5);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TitleAndResizePanelsFadeAgainstActualBackgroundWithoutOpaqueWhiteTiles(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(scale, theme);
var half = Render(scale, theme, title: .5, resize: .5);
var full = Render(scale, theme, title: 1, resize: 1);
double cx = Window.X + Window.Width / 2d;
Assert.Equal(theme.Panel, idle.At(cx + 25, Window.Y + 20));
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 20), half.At(cx + 25, Window.Y + 20));
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 40), full.At(cx + 25, Window.Y + 20));
double right = Window.X + Window.Width, bottom = Window.Y + Window.Height;
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 44), full.At(right - 13, bottom - 13));
Assert.NotEqual(0xFFFFFFu, full.At(right - 13, bottom - 13));
Assert.Equal(theme.Panel, full.At(right - 30, bottom - 15));
Assert.True(Region(full, DesktopWindowChromeRenderer.TitleDamage(Window)).SequenceEqual(
Region(Render(scale, theme, title: 1, resize: 1), DesktopWindowChromeRenderer.TitleDamage(Window))));
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void DefaultResizeAffordanceFollowsQuarterRadiusAndDisablesForMaximizedWindow(int scale)
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var normal = Render(scale, theme);
var maximized = Render(scale, theme, disabledResize: true);
var bounds = DesktopWindowChromeRenderer.ResizeDamage(Window);
Assert.Contains(Region(normal, bounds), value => value != theme.Panel);
Assert.All(Region(maximized, bounds), value => Assert.Equal(theme.Panel, value));
double centerX = Window.X + Window.Width - 18, centerY = Window.Y + Window.Height - 18;
for (int row = bounds.Y * scale; row < (bounds.Y + bounds.Height) * scale; row++)
for (int col = bounds.X * scale; col < (bounds.X + bounds.Width) * scale; col++)
{
if (normal.Pixels[row * normal.NativeWidth + col] == theme.Panel) continue;
double x = (col + .5) / scale - centerX, y = (row + .5) / scale - centerY;
Assert.InRange(Math.Sqrt(x * x + y * y), 13.5, 17.5);
}
}
[Fact]
public void AnimationChangesStayInsideDeclaredSmallDamageRegions()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(2, theme);
var active = Render(2, theme, .4, .3, .7, .8);
var regions = new[] { DesktopWindowChromeRenderer.TitleDamage(Window),
DesktopWindowChromeRenderer.CaptionDamage(Window, false), DesktopWindowChromeRenderer.CaptionDamage(Window, true),
DesktopWindowChromeRenderer.ResizeDamage(Window) };
Assert.InRange(regions[0].Width, Window.Width * 2 / 3, Window.Width * 2 / 3 + 2);
for (int row = 0; row < active.NativeHeight; row++)
for (int col = 0; col < active.NativeWidth; col++)
if (active.Pixels[row * active.NativeWidth + col] != idle.Pixels[row * active.NativeWidth + col])
Assert.Contains(regions, region => region.Contains(col / 2, row / 2));
}
[Fact]
public void PressedFeedbackChangesOnlyCaptionGlyphsAndCircleWithoutSquareTiles()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var idle = Render(2, theme); var pressed = new Bitmap(2, theme.Panel);
DesktopWindowChromeRenderer.Draw(pressed, Window, theme, true, 0, 0, 0, 0,
minimizePressed: true, closePressed: true);
var minimize = DesktopWindowChromeRenderer.CaptionDamage(Window, false);
var close = DesktopWindowChromeRenderer.CaptionDamage(Window, true);
Assert.False(Region(idle, minimize).SequenceEqual(Region(pressed, minimize)));
Assert.False(Region(idle, close).SequenceEqual(Region(pressed, close)));
Assert.DoesNotContain(0xFFFFFFu, Region(pressed, minimize));
for (int row = 0; row < pressed.NativeHeight; row++)
for (int col = 0; col < pressed.NativeWidth; col++)
if (pressed.Pixels[row * pressed.NativeWidth + col] != idle.Pixels[row * idle.NativeWidth + col])
Assert.True(minimize.Contains(col / 2, row / 2) || close.Contains(col / 2, row / 2));
}
[Fact]
public void LightHoverPanelHasSubtleThemeEdgeWithoutChangingItsAreaOrWhiteInterior()
{
var theme = new DesktopTheme(DesktopThemeMode.Light);
var idle = Render(2, theme); var hover = Render(2, theme, title: 1);
double center = Window.X + Window.Width / 2d;
double left = center - (Window.Width * 2 / 3) / 2d;
uint edge = hover.At(left, Window.Y + 10);
Assert.True((idle.At(left, Window.Y + 10) >> 16 & 255) - (edge >> 16 & 255) > 5);
Assert.Equal(Mix(theme.Panel, 0xFFFFFF, 40), hover.At(center + 25, Window.Y + 20));
var damage = DesktopWindowChromeRenderer.TitleDamage(Window);
for (int row = 0; row < hover.NativeHeight; row++)
for (int col = 0; col < hover.NativeWidth; col++)
if (hover.Pixels[row * hover.NativeWidth + col] != idle.Pixels[row * idle.NativeWidth + col])
Assert.True(damage.Contains(col / 2, row / 2));
}
[Fact]
public void RotatedTwoHundredPercentStrokeIsRasterizedAtNativePixelPrecision()
{
var theme = new DesktopTheme(DesktopThemeMode.Dark);
var frame = Render(2, theme, min: .25);
var bounds = DesktopWindowChromeRenderer.CaptionDamage(Window, false);
int varied = 0;
for (int row = bounds.Y * 2; row < (bounds.Y + bounds.Height) * 2; row += 2)
for (int col = bounds.X * 2; col < (bounds.X + bounds.Width) * 2; col += 2)
{
uint value = frame.Pixels[row * frame.NativeWidth + col];
if (value != frame.Pixels[row * frame.NativeWidth + col + 1] ||
value != frame.Pixels[(row + 1) * frame.NativeWidth + col] ||
value != frame.Pixels[(row + 1) * frame.NativeWidth + col + 1]) varied++;
}
Assert.True(varied > 12);
}
private sealed class Probe : IDesktopCoverageCanvas
{
public int Width => 420;
public int Height => 280;
public int ScalePercent => 200;
public void Fill(uint rgb, int x, int y, int width, int height) { }
public void BlendCoverage(uint rgb, int x, int y, int width, int height, ReadOnlySpan<byte> mask) { }
public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new InvalidOperationException();
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
[Fact]
public void WarmQuantizedAnimationDoesNotAllocatePerFrameMasks()
{
var canvas = new Probe(); var theme = new DesktopTheme();
for (int pass = 0; pass < 2; pass++)
for (int step = 0; step <= DesktopWindowChromeRenderer.AnimationSteps; step++)
DesktopWindowChromeRenderer.Draw(canvas, Window, theme, true, step / 32d, step / 32d, step / 32d, step / 32d);
long before = GC.GetAllocatedBytesForCurrentThread();
for (int frame = 0; frame < 200; frame++)
{
double phase = frame % 33 / 32d;
DesktopWindowChromeRenderer.Draw(canvas, Window, theme, true, phase, phase, phase, phase);
}
Assert.Equal(0, GC.GetAllocatedBytesForCurrentThread() - before);
}
[Fact]
public void OptionalNativeInspectionPreviewUsesProductionRenderer()
{
string? directory = Environment.GetEnvironmentVariable("XFE_CHROME_PROOF_PATH");
if (directory is null) return;
Assert.True(Path.IsPathFullyQualified(directory)); Directory.CreateDirectory(directory);
foreach (int scale in new[] { 1, 2 })
foreach (DesktopThemeMode mode in Enum.GetValues<DesktopThemeMode>())
foreach (int step in new[] { 0, 8, 32 })
{
var bitmap = Render(scale, new DesktopTheme(mode), step / 32d, step / 32d, step / 32d, step / 32d);
WritePng(Path.Combine(directory, $"Chrome-{mode}-{scale}x-{step}.png"), bitmap.NativeWidth, bitmap.NativeHeight, bitmap.Pixels);
}
}
private static void WritePng(string path, int width, int height, uint[] pixels)
{
using var output = File.Create(path); output.Write(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 });
Span<byte> header = stackalloc byte[13]; header.Clear();
BinaryPrimitives.WriteUInt32BigEndian(header, (uint)width); BinaryPrimitives.WriteUInt32BigEndian(header[4..], (uint)height);
header[8] = 8; header[9] = 2; Chunk(output, "IHDR"u8, header);
using var compressed = new MemoryStream();
using (var zlib = new ZLibStream(compressed, CompressionLevel.Fastest, true))
for (int row = 0; row < height; row++)
{
zlib.WriteByte(0);
for (int col = 0; col < width; col++)
{
uint rgb = pixels[row * width + col];
zlib.WriteByte((byte)(rgb >> 16)); zlib.WriteByte((byte)(rgb >> 8)); zlib.WriteByte((byte)rgb);
}
}
Chunk(output, "IDAT"u8, compressed.ToArray()); Chunk(output, "IEND"u8, []);
}
private static void Chunk(Stream output, ReadOnlySpan<byte> name, ReadOnlySpan<byte> bytes)
{
Span<byte> word = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(word, (uint)bytes.Length);
output.Write(word); output.Write(name); output.Write(bytes); uint crc = uint.MaxValue;
foreach (byte value in name) crc = Crc(crc, value);
foreach (byte value in bytes) crc = Crc(crc, value);
BinaryPrimitives.WriteUInt32BigEndian(word, ~crc); output.Write(word);
}
private static uint Crc(uint crc, byte value)
{
crc ^= value;
for (int bit = 0; bit < 8; bit++) crc = crc >> 1 ^ ((crc & 1) == 0 ? 0 : 0xEDB88320u);
return crc;
}
}