Terminal.Gui
Terminal.Gui copied to clipboard
Attribute System Should honor terminal settings
Terminal.Gui apps should honor user terminal color settings, similar to how ls and other commands adapt to terminal configurations, rather than relying on our default color schemes. This ensures a consistent user experience across terminal-based applications.
To achieve this, we can model our approach on PowerShell’s $PSStyle (PowerShell 7.2+), which provides robust user-defined color settings for output styling. Proposed mappings for Terminal.Gui’s Scheme attributes include:
- Normal: Map to
$PSStyle.Foregroundor$Host.UI.RawUI.ForegroundColorfor the terminal’s default text color, used in views likeLabelorListView. - Active: Map to
$PSStyle.PSReadLine.Selectionfor selected elements (e.g., highlightedListViewitems,RadioGroupoptions). - Error: Map to
$PSStyle.Formatting.Errorfor error messages or states (e.g., invalidTextFieldinput). - Warning (new): Map to
$PSStyle.Formatting.Warningfor warning messages. - Header (new): Map to
$PSStyle.Formatting.TableHeaderfor table/list headers (e.g.,TableView).
Implementation Notes:
- Querying Colors: Develop a mechanism to query
$PSStyle(e.g., via PowerShell script execution) for PowerShell terminals, or fall back to$TERMcapabilities (16, 256, or 24-bit RGB) for non-PowerShell environments like Linux/macOS. This may require platform-specific logic. - ANSI Styles: Support PowerShell’s ANSI styles (Bold, Italic, Reverse, etc.) in
Schemeto fully reflect user settings, building on truecolor support (#48). - Accessibility: Enforce contrast (e.g., WCAG 2.1’s 4.5:1 ratio) to ensure readability, with fallbacks for low-contrast user settings.
- Integration with ColorScheme Redesign: Align with the proposed
ColorSchemetoSchemerename andConfigurationManager.Themesmigration discussed in #457.
This approach focuses on output styling, deferring syntax highlighting (e.g., $PSStyle.PSReadLine tokens like Comment, Keyword) to separate efforts, such as enhancing the SyntaxHighlighting sample scenario.
Open Questions:
- How can we best query
$PSStyleor$TERMin a cross-platform way? - Should we prioritize certain
$PSStyle.Formattingoptions (e.g.,Error,Warning) over others? - How do we handle terminals with limited color support (e.g., 16 colors)?
Related: #48 (Truecolors support), #457 (ColorScheme redesign).
Community feedback on these mappings and querying approaches is welcome!
Original post:
Developers (like me) love tweaking their terminal colors/themes.
Terminal.Gui, today, overrides all this, defining it's own themes.
This means that when I run a TUI app, the effect is glaring and doesn't honor all the hard work I put into tweaking my terminal theme. E.g.:

In addition we always clear the Toplevel background vs, letting any bitmap the user has set show through.
For v2, I'd like to see Terminal.Gui pick up the colors theme of the Terminal and use it, by default.
Of course, this implies we nail #48 too.
I think this means a Tenet for v2:
- Honor The Terminal Theme - To the extent possible, Terminal.Gui will use terminal color, keyboard, and other themes vs defining it's own.
I was thinking about how a default theme could be dynamically created based only on an inherited foreground and background. This is quite difficult. FWIW, I thought I'd lay down my ideas.
.Normal would just use the current colors, of course:
fg = defFore; bg = defBack;
.Focus would just swap background and foreground [EDIT: though this collides with selection]:
fg = defBack; bg = defFore;
.Disabled, I think, should compute a grayscale foreground color based on the background, adjusted for Luma. See Luma() below. We could use the following for RGB colors, or round to the nearest grayscale ConsoleColor (though see NB below):
fg = Luma(defBack) > 0.5 ? Gray(255 * Luma(defBack) - 63) : Gray(255 * Luma(defBack) + 63);
bg = defBack;
.HotNormal is where things get tough:
fg = AccentColor(defFore); bg = defBack;
To get an accent color, my first thought was to use an inverse of the RGB of the foreground color. However, this wouldn't work for middle-grayish colors, would often result in ugly combinations, and it wouldn't take Luma into account. It's somewhat easy to handpick a color for each of the 16 ConsoleColors like my AccentColor() implementation below, though this is obviously a matter of taste, and taking both foreground and background into consideration does complicate things quite a bit.
AccentColor() would be somewhat more complicated using RGB (TrueColor or not), but I suppose the suggestions I offer below could still be used if we approximate numerically 16 zones of "reddish" or "dark-blueish" or "blackish" colors [something like the existing Color.ToConsoleColor()], but then we could adjust the accent result based on the calculated saturation and brightness.
.HotFocus is similar, but swapping background and foreground:
fg = AccentColorAdjusted(defFore); bg = defFore;
However, we probably don't want to introduce another color which might clash with HotNormal, so we wouldn't just use fg = AccentColor(defBack); So my thought is to either re-use the AccentColor(defFore) when there is sufficient difference in Luma between foreground and background, otherwise flip the accent ConsoleColor from dark to bright or bright to dark (or a similar approximation for RGB values).
NB: On the other hand, since each of the ConsoleColors can be set by the user and might even have little bearing on the actual color, it's dangerous to make assumptions. Even assuming the defaults were kept, there's variance between platforms and terminal emulations, e.g., whether bright colors are allowed as backgrounds. Under Windows, depending on the upgrade path the user might still be using legacy colors or the newer Campbell colors.
However, is it even possible to programmatically determine the RGB values of the currently used colors? E.g., using Windows Terminal, we'd have to find and read the appropriate .json file; using Windows conhost, in some circumstances these values would be in the registry, and in some circumstances they'd be in the .lnk file used to launch it; with a multitude of implementations for other shells and platforms and terminal emulations.
EDIT: Also, how is this supposed to work with the bitmap background? How do we ensure a transparent color is used so the bitmap isn't hidden when we've drawn spaces after, e.g., a window is dragged around? Does it just draw the bitmap anywhere the current background color exists? And I assume these implementation details could differ between shells... EDIT 2: It appears that my assumption was correct, for WT at least, so no need to worry about transparency there.
float Luma(Color c) {
return 0.2126 * c.ScR + 0.7152 * c.ScG + 0.0722 * c.ScB; }
Color Gray(int x) {
return Color(x, x, x); }
static ConsoleColor AccentColor(ConsoleColor fg, ConsoleColor bg) {
ConsoleColor newc = new();
switch (fg) {
case ConsoleColor.Gray:
case ConsoleColor.White:
newc = bg == ConsoleColor.DarkGreen ? ConsoleColor.Black : ConsoleColor.DarkGreen; break;
case ConsoleColor.DarkGreen:
newc = bg == ConsoleColor.White ? ConsoleColor.Green : ConsoleColor.White; break;
case ConsoleColor.DarkGray:
case ConsoleColor.Black:
newc = bg == ConsoleColor.Green ? ConsoleColor.White : ConsoleColor.Green; break;
case ConsoleColor.Green:
newc = bg == ConsoleColor.Black ? ConsoleColor.DarkCyan : ConsoleColor.Black; break;
case ConsoleColor.DarkBlue:
newc = bg == ConsoleColor.Cyan ? ConsoleColor.Yellow : ConsoleColor.Cyan; break;
case ConsoleColor.Cyan:
newc = bg == ConsoleColor.DarkBlue ? ConsoleColor.DarkGray : ConsoleColor.DarkBlue; break;
case ConsoleColor.DarkRed:
case ConsoleColor.DarkMagenta:
newc = bg == ConsoleColor.White ? ConsoleColor.Yellow : ConsoleColor.White; break;
case ConsoleColor.Magenta:
case ConsoleColor.Red:
newc = bg == ConsoleColor.Black ? ConsoleColor.DarkYellow : ConsoleColor.Black; break;
case ConsoleColor.Yellow:
newc = bg == ConsoleColor.DarkBlue ? ConsoleColor.Black : ConsoleColor.DarkBlue; break;
case ConsoleColor.DarkCyan:
case ConsoleColor.Blue:
newc = bg == ConsoleColor.Yellow ? ConsoleColor.White : ConsoleColor.Yellow; break; }
return newc; }
.Focus would just swap background and foreground:
Normally inverted colors is used for selection, like used in TextField and TextView. Usually it can be fg = other color; bg = Normal Fore;
Normally inverted colors is used for selection, like used in
TextFieldandTextView. Usually it can be fg = other color; bg = Normal Fore;
I was hoping to limit the number of accent colors to compute, but that's true.
See https://github.com/PowerShell/GraphicalTools/issues/211
This request argues for being able to use PowerShell's $PSSytyle as a source for settings.
Proposal for expanded ColorScheme members
(Just notes for now - please ignore)
Normal- The attribute for normal text in a view when the view has focus.NormalNotFocused- The attribute for normal text when the view has the focus.Hot- The attribute for text in a focused view that indicates aa hot key or shortcut.HotNotFocused- The attribute for text in a non-focused view that indicates a hot key or shortcut.Selected- The attribute for text of something selected in a focused view.SelectedNotFocused- The attribute for text of something selected in a not focused view.Editable- The attribute for normal text in a view when the view has focus.EditableNotFocused- The attribute for normal text when the view has the focus.Disabled- The default foreground and background color for text when the view is disabled. Also used for normally editable content that is readonly.
Relevant / informational:
https://chadaustin.me/2024/01/truecolor-terminal-emacs/
I think I mentioned it in the color discussion I started but in the spirit of that link, I'll link this project here, as well, as a potential resource for whatever we end up with:
https://github.com/koszeggy/KGySoft.Drawing