diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
index adc463cc2..e0b2e2aed 100644
--- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
+++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
@@ -1849,6 +1849,136 @@ public static class EscSeqUtils
#endregion
+ #region Text Styles
+
+ ///
+ /// Appends an ANSI SGR (Select Graphic Rendition) escape sequence to switch printed text from one to another.
+ ///
+ /// to add escape sequence to.
+ /// Previous to change away from.
+ /// Next to change to.
+ ///
+ ///
+ /// Unlike colors, most text styling options are not mutually exclusive with each other, and can be applied independently. This creates a problem when
+ /// switching from one style to another: For instance, if your previous style is just bold, and your next style is just italic, then simply adding the
+ /// sequence to enable italic text would cause the text to remain bold. This method automatically handles this problem, enabling and disabling styles as
+ /// necessary to apply exactly the next style.
+ ///
+ ///
+ internal static void CSI_AppendTextStyleChange (StringBuilder output, TextStyle prev, TextStyle next)
+ {
+ // Do nothing if styles are the same, as no changes are necessary.
+ if (prev == next)
+ {
+ return;
+ }
+
+ // Bitwise operations to determine flag changes. A ^ B are the flags different between two flag sets. These different flags that exist in the next flag
+ // set (diff & next) are the ones that were enabled in the switch, those that exist in the previous flag set (diff & prev) are the ones that were
+ // disabled.
+ var diff = prev ^ next;
+ var enabled = diff & next;
+ var disabled = diff & prev;
+
+ // List of escape codes to apply.
+ var sgr = new List ();
+
+ if (disabled != TextStyle.None)
+ {
+ // Special case: Both bold and faint have the same disabling code. While unusual, it can be valid to have both enabled at the same time, so when
+ // one and only one of them is being disabled, we need to re-enable the other afterward. We can check what flags remain enabled by taking
+ // prev & next, as this is the set of flags both have.
+ if (disabled.HasFlag (TextStyle.Bold))
+ {
+ sgr.Add (22);
+
+ if ((prev & next).HasFlag (TextStyle.Faint))
+ {
+ sgr.Add (2);
+ }
+ }
+
+ if (disabled.HasFlag (TextStyle.Faint))
+ {
+ sgr.Add (22);
+
+ if ((prev & next).HasFlag (TextStyle.Bold))
+ {
+ sgr.Add (1);
+ }
+ }
+
+ if (disabled.HasFlag (TextStyle.Italic))
+ {
+ sgr.Add (23);
+ }
+
+ if (disabled.HasFlag (TextStyle.Underline))
+ {
+ sgr.Add (24);
+ }
+
+ if (disabled.HasFlag (TextStyle.Blink))
+ {
+ sgr.Add (25);
+ }
+
+ if (disabled.HasFlag (TextStyle.Reverse))
+ {
+ sgr.Add (27);
+ }
+
+ if (disabled.HasFlag (TextStyle.Strikethrough))
+ {
+ sgr.Add (29);
+ }
+ }
+
+ if (enabled != TextStyle.None)
+ {
+ if (enabled.HasFlag (TextStyle.Bold))
+ {
+ sgr.Add (1);
+ }
+
+ if (enabled.HasFlag (TextStyle.Faint))
+ {
+ sgr.Add (2);
+ }
+
+ if (enabled.HasFlag (TextStyle.Italic))
+ {
+ sgr.Add (3);
+ }
+
+ if (enabled.HasFlag (TextStyle.Underline))
+ {
+ sgr.Add (4);
+ }
+
+ if (enabled.HasFlag (TextStyle.Blink))
+ {
+ sgr.Add (5);
+ }
+
+ if (enabled.HasFlag (TextStyle.Reverse))
+ {
+ sgr.Add (7);
+ }
+
+ if (enabled.HasFlag (TextStyle.Strikethrough))
+ {
+ sgr.Add (9);
+ }
+ }
+
+ output.Append ("\x1b[");
+ output.Append (string.Join (';', sgr));
+ output.Append ('m');
+ }
+
+ #endregion Text Styles
+
#region Requests
///
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 69defb82e..fb93f6405 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -12,6 +12,9 @@ public class NetOutput : IConsoleOutput
private CursorVisibility? _cachedCursorVisibility;
+ // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
+ private TextStyle _redrawTextStyle = TextStyle.None;
+
///
/// Creates a new instance of the class.
///
@@ -134,6 +137,10 @@ public class NetOutput : IConsoleOutput
attr.Background.G,
attr.Background.B
);
+
+ EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.TextStyle);
+
+ _redrawTextStyle = attr.TextStyle;
}
outputWidth++;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 248c266fa..1b6295985 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -33,7 +33,8 @@ public class OutputBuffer : IOutputBuffer
// TODO: This makes IConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed.
if (Application.Driver is { })
{
- _currentAttribute = new (value.Foreground, value.Background);
+ // TODO: Update this when attributes can include TextStyle in the constructor
+ _currentAttribute = new (value.Foreground, value.Background) { TextStyle = value.TextStyle };
return;
}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 81142fb83..4ec616616 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -61,6 +61,9 @@ internal partial class WindowsOutput : IConsoleOutput
private readonly nint _screenBuffer;
+ // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
+ private TextStyle _redrawTextStyle = TextStyle.None;
+
public WindowsOutput ()
{
Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}");
@@ -233,6 +236,8 @@ internal partial class WindowsOutput : IConsoleOutput
prev = attr;
EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
+ EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.TextStyle);
+ _redrawTextStyle = attr.TextStyle;
}
if (info.Char != '\x1b')
diff --git a/Terminal.Gui/Drawing/Attribute.cs b/Terminal.Gui/Drawing/Attribute.cs
index 28097f377..cba89e682 100644
--- a/Terminal.Gui/Drawing/Attribute.cs
+++ b/Terminal.Gui/Drawing/Attribute.cs
@@ -34,6 +34,10 @@ public readonly record struct Attribute : IEqualityOperatorsThe text style (bold, italic, underlined, etc.).
+ public TextStyle TextStyle { get; init; } = TextStyle.None;
+
/// Initializes a new instance with default values.
public Attribute ()
{
@@ -103,6 +107,7 @@ public readonly record struct Attribute : IEqualityOperators
public override int GetHashCode () { return HashCode.Combine (PlatformColor, Foreground, Background); }
+ // TODO: Add TextStyle to Attribute.ToString(), modify unit tests to account
///
public override string ToString ()
{
diff --git a/Terminal.Gui/Drawing/TextStyle.cs b/Terminal.Gui/Drawing/TextStyle.cs
new file mode 100644
index 000000000..5a7fbb806
--- /dev/null
+++ b/Terminal.Gui/Drawing/TextStyle.cs
@@ -0,0 +1,79 @@
+namespace Terminal.Gui;
+
+///
+/// Defines non-color text style flags for an .
+///
+///
+///
+/// Only a subset of ANSI SGR (Select Graphic Rendition) styles are represented.
+/// Styles that are poorly supported, non-visual, or redundant with other APIs are omitted.
+///
+///
+/// Multiple styles can be combined using bitwise operations. Use
+/// to get or set these styles on an .
+///
+///
+/// Note that and may be mutually exclusive depending on
+/// the user's terminal and its settings. For instance, if a terminal displays faint text as a darker color, and
+/// bold text as a lighter color, then both cannot
+/// be shown at the same time, and it will be up to the terminal to decide which to display.
+///
+///
+[Flags]
+public enum TextStyle : byte
+{
+ ///
+ /// No text style.
+ ///
+ /// Corresponds to no active SGR styles.
+ None = 0b_0000_0000,
+
+ ///
+ /// Bold text.
+ ///
+ ///
+ /// SGR code: 1 (Bold). May be mutually exclusive with , see
+ /// remarks.
+ ///
+ Bold = 0b_0000_0001,
+
+ ///
+ /// Faint (dim) text.
+ ///
+ ///
+ /// SGR code: 2 (Faint). Not widely supported on all terminals. May be mutually exclusive with
+ /// , see
+ /// remarks.
+ ///
+ Faint = 0b_0000_0010,
+
+ ///
+ /// Italic text.
+ ///
+ /// SGR code: 3 (Italic). Some terminals may not support italic rendering.
+ Italic = 0b_0000_0100,
+
+ ///
+ /// Underlined text.
+ ///
+ /// SGR code: 4 (Underline).
+ Underline = 0b_0000_1000,
+
+ ///
+ /// Slow blinking text.
+ ///
+ /// SGR code: 5 (Slow Blink). Support varies; blinking is often disabled in modern terminals.
+ Blink = 0b_0001_0000,
+
+ ///
+ /// Reverse video (swaps foreground and background colors).
+ ///
+ /// SGR code: 7 (Reverse Video).
+ Reverse = 0b_0010_0000,
+
+ ///
+ /// Strikethrough (crossed-out) text.
+ ///
+ /// SGR code: 9 (Crossed-out / Strikethrough).
+ Strikethrough = 0b_0100_0000
+}