diff --git a/.gitignore b/.gitignore
index dc0470a..e043c9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -203,6 +203,11 @@ PublishScripts/
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
+# and except a Unity project's Packages/, which is source: Unity's package manifest and its
+# resolved lock file are both meant to be committed, and a NuGet restore folder never contains
+# a file by either name.
+!**/[Pp]ackages/manifest.json
+!**/[Pp]ackages/packages-lock.json
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
@@ -651,3 +656,16 @@ Temporary Items
# ImGui.ini files
imgui.ini
+
+# Game engine projects
+#
+# Godot: the import cache, and the mono/temp bin+obj a C# build writes.
+.godot/
+
+# Unity: .meta files are source, not the Visual Studio C++ build artifact that the `*.meta` rule
+# further up targets. Unity generates one per asset and it carries the GUID that scenes, prefabs
+# and serialized references point at, so ignoring them gives every clone fresh GUIDs and silently
+# breaks those references - including for a plug-in whose .dll is itself a build output. This
+# negation has to come after that rule to win, and is scoped to the asset tree so the Visual
+# Studio artifact stays ignored everywhere else.
+!**/[Aa]ssets/**/*.meta
diff --git a/TUI.Test/SpectreConsoleProviderTests.cs b/TUI.Test/SpectreConsoleProviderTests.cs
index 232feff..d2666ad 100644
--- a/TUI.Test/SpectreConsoleProviderTests.cs
+++ b/TUI.Test/SpectreConsoleProviderTests.cs
@@ -17,13 +17,14 @@ public sealed class SpectreConsoleProviderTests
private const int Width = 10;
private const int Height = 5;
- private static (SpectreConsoleProvider Provider, StringWriter Output) CreateProvider()
+ private static (SpectreConsoleProvider Provider, StringWriter Output) CreateProvider(
+ ColorSystemSupport colorSystem = ColorSystemSupport.NoColors)
{
StringWriter output = new();
IAnsiConsole console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Yes,
- ColorSystem = ColorSystemSupport.NoColors,
+ ColorSystem = colorSystem,
Interactive = InteractionSupport.No,
Out = new AnsiConsoleOutput(output),
});
@@ -224,4 +225,62 @@ public void SetCursorVisibilityWritesToTheInjectedConsoleWithoutMovingTheCursor(
Assert.AreEqual(expected, output.ToString());
}
+
+ ///
+ /// Tests that a fully transparent background is left to the terminal rather than painted as the
+ /// opaque color its RGB channels happen to hold (ktsu-dev/TUI#155).
+ ///
+ [TestMethod]
+ public void TransparentBackgroundNameWritesNoBackground()
+ {
+ (SpectreConsoleProvider provider, StringWriter output) = CreateProvider(ColorSystemSupport.TrueColor);
+
+ provider.WriteAt("Hi", new Position(0, 0), new TextStyle { Foreground = "Yellow", Background = "Transparent" });
+
+ Assert.AreEqual("\u001b[1;1H\u001b[38;2;255;255;0mHi\u001b[0m", output.ToString());
+ }
+
+ ///
+ /// Tests that a zero-alpha background color writes no background SGR parameter, whatever its
+ /// RGB channels (ktsu-dev/TUI#155).
+ ///
+ [TestMethod]
+ public void ZeroAlphaBackgroundColorWritesNoBackground()
+ {
+ (SpectreConsoleProvider provider, StringWriter output) = CreateProvider(ColorSystemSupport.TrueColor);
+
+ provider.WriteAt("Hi", new Position(0, 0), new TextStyle { BackgroundColor = System.Drawing.Color.FromArgb(0, 0, 0, 0), IsBold = true });
+
+ Assert.DoesNotContain("48;", output.ToString());
+ Assert.Contains("Hi", output.ToString());
+ }
+
+ ///
+ /// Tests that a transparent foreground writes no foreground SGR parameter, so the text keeps the
+ /// terminal's own color (ktsu-dev/TUI#155).
+ ///
+ [TestMethod]
+ public void TransparentForegroundWritesNoForeground()
+ {
+ (SpectreConsoleProvider provider, StringWriter output) = CreateProvider(ColorSystemSupport.TrueColor);
+
+ provider.WriteAt("Hi", new Position(0, 0), new TextStyle { Foreground = "Transparent", Background = "Blue" });
+
+ Assert.DoesNotContain("38;", output.ToString());
+ Assert.Contains("48;2;0;0;255", output.ToString());
+ }
+
+ ///
+ /// Tests that a partially transparent color is drawn as if opaque, since a terminal cannot blend
+ /// (ktsu-dev/TUI#155).
+ ///
+ [TestMethod]
+ public void PartiallyTransparentBackgroundIsDrawnOpaque()
+ {
+ (SpectreConsoleProvider provider, StringWriter output) = CreateProvider(ColorSystemSupport.TrueColor);
+
+ provider.WriteAt("Hi", new Position(0, 0), new TextStyle { BackgroundColor = System.Drawing.Color.FromArgb(128, 0, 0, 255) });
+
+ Assert.Contains("48;2;0;0;255", output.ToString());
+ }
}
diff --git a/TUI/Models/TextStyle.cs b/TUI/Models/TextStyle.cs
index 107aaba..36a756f 100644
--- a/TUI/Models/TextStyle.cs
+++ b/TUI/Models/TextStyle.cs
@@ -13,11 +13,19 @@ public readonly record struct TextStyle
///
/// Gets or sets the foreground color
///
+ ///
+ /// A color with zero alpha, such as , leaves the terminal's own
+ /// foreground. Terminals cannot blend, so any other alpha is drawn as if opaque.
+ ///
public Color? ForegroundColor { get; init; }
///
/// Gets or sets the background color
///
+ ///
+ /// A color with zero alpha, such as , leaves the terminal's own
+ /// background. Terminals cannot blend, so any other alpha is drawn as if opaque.
+ ///
public Color? BackgroundColor { get; init; }
///
diff --git a/TUI/Services/SpectreConsoleProvider.cs b/TUI/Services/SpectreConsoleProvider.cs
index 90ea4de..c47c5dd 100644
--- a/TUI/Services/SpectreConsoleProvider.cs
+++ b/TUI/Services/SpectreConsoleProvider.cs
@@ -173,16 +173,17 @@ private static string BuildStyleString(TextStyle style)
{
List parts = [];
- if (style.ForegroundColor.HasValue)
+ // A terminal cannot blend, so a fully transparent color means "leave the terminal's own" and
+ // any other alpha is drawn opaque. Writing the RGB of Color.Transparent would paint white
+ // (ktsu-dev/TUI#155).
+ if (style.ForegroundColor is { A: > 0 } foreground)
{
- System.Drawing.Color color = style.ForegroundColor.Value;
- parts.Add($"#{color.R:X2}{color.G:X2}{color.B:X2}");
+ parts.Add($"#{foreground.R:X2}{foreground.G:X2}{foreground.B:X2}");
}
- if (style.BackgroundColor.HasValue)
+ if (style.BackgroundColor is { A: > 0 } background)
{
- System.Drawing.Color color = style.BackgroundColor.Value;
- parts.Add($"on #{color.R:X2}{color.G:X2}{color.B:X2}");
+ parts.Add($"on #{background.R:X2}{background.G:X2}{background.B:X2}");
}
if (style.IsBold)