Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2798. Add TrueColor support to CursesDriver. #3770

Merged
merged 32 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
eee4db9
Fixes #2798. Add TrueColor support to CursesDriver.
BDisp Sep 30, 2024
8c4778e
Fix unit test error.
BDisp Oct 1, 2024
b148fcc
Merge branch 'v2_develop' into v2_2798_add-true-color-to-cursesdriver
BDisp Oct 2, 2024
4b59830
Merge branch 'v2_develop' into v2_2798_add-true-color-to-cursesdriver
tig Oct 6, 2024
8d09971
Fixes #3784. SelfContained and NativeAot projects should use the loca…
BDisp Oct 9, 2024
4afe694
Run dotnet restore before build.
BDisp Oct 9, 2024
4288b71
Using local_packages folder for CI.
BDisp Oct 9, 2024
e130bfd
Add build_release_consumer.
BDisp Oct 9, 2024
bd8ff63
Remove build_release_consumer.
BDisp Oct 9, 2024
5db3af9
Fix folder for CI.
BDisp Oct 9, 2024
d77b899
Fix System.Text.Json vulnerability.
BDisp Oct 9, 2024
3361eed
Fix local_packageslocation.
BDisp Oct 9, 2024
f9f3431
Add package sources to the packageSourceMapping tag.
BDisp Oct 9, 2024
0dc3a52
Using the original configuration.
BDisp Oct 9, 2024
88e2ee6
Only add the Terminal.Gui pattern in the LocalPackages.
BDisp Oct 9, 2024
2c01b33
Fix the path folder separator with unit style.
BDisp Oct 9, 2024
820bf44
Using pack instead of build.
BDisp Oct 9, 2024
d3192df
Create LocalPackages Directory
BDisp Oct 9, 2024
ad69b9d
Add local_packages source.
BDisp Oct 9, 2024
5b108a9
Using scripts to build release for NativeAot and SelfContained.
BDisp Oct 10, 2024
0b4227b
Trying to fix path.
BDisp Oct 10, 2024
a57db4e
Again.
BDisp Oct 10, 2024
c12a2cf
Fix the path for the package,
BDisp Oct 10, 2024
214d109
Need to build before pack.
BDisp Oct 10, 2024
29b902b
Needs also build before pack locally.
BDisp Oct 10, 2024
3bcc34b
Fix build path.
BDisp Oct 10, 2024
bd307f7
Merge branch 'v2_develop' into v2_2798_add-true-color-to-cursesdriver
BDisp Oct 10, 2024
bc20229
Merge branch 'v2_develop' into v2_3784_selfcontained_nativeaot-local-…
BDisp Oct 10, 2024
1a8569b
Merge branch 'v2_develop' into v2_2798_add-true-color-to-cursesdriver
BDisp Oct 10, 2024
91c4e30
Merge branch 'v2_3784_selfcontained_nativeaot-local-release-package' …
BDisp Oct 10, 2024
14cce2e
Merge branch 'v2_develop' into v2_2798_add-true-color-to-cursesdriver
tig Oct 11, 2024
b98c6fa
Merge branch 'v2_develop' into v2_2798_add-true-color-to-cursesdriver
tig Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 210 additions & 43 deletions Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal set
}
}

public override bool SupportsTrueColor => false;
public override bool SupportsTrueColor => true;

/// <inheritdoc/>
public override bool EnsureCursorVisibility () { return false; }
Expand Down Expand Up @@ -200,8 +200,12 @@ public override void Suspend ()
if (!RunningUnitTests)
{
Platform.Suspend ();
Curses.Window.Standard.redrawwin ();
Curses.refresh ();

if (Force16Colors)
{
Curses.Window.Standard.redrawwin ();
Curses.refresh ();
}
}

StartReportingMouseMoves ();
Expand All @@ -214,74 +218,232 @@ public override void UpdateCursor ()
if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
{
Curses.move (Row, Col);
Curses.raw ();
Curses.noecho ();
Curses.refresh ();

if (Force16Colors)
{
Curses.raw ();
Curses.noecho ();
Curses.refresh ();
}
}
}


public override void UpdateScreen ()
{
for (var row = 0; row < Rows; row++)
if (Force16Colors)
{
if (!_dirtyLines [row])
for (var row = 0; row < Rows; row++)
{
continue;
}

_dirtyLines [row] = false;

for (var col = 0; col < Cols; col++)
{
if (Contents [row, col].IsDirty == false)
if (!_dirtyLines [row])
{
continue;
}

if (RunningUnitTests)
_dirtyLines [row] = false;

for (var col = 0; col < Cols; col++)
{
// In unit tests, we don't want to actually write to the screen.
continue;
}
if (Contents [row, col].IsDirty == false)
{
continue;
}

Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
if (RunningUnitTests)
{
// In unit tests, we don't want to actually write to the screen.
continue;
}

Rune rune = Contents [row, col].Rune;
Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);

if (rune.IsBmp)
{
// BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
if (rune.GetColumns () < 2)
Rune rune = Contents [row, col].Rune;

if (rune.IsBmp)
{
Curses.mvaddch (row, col, rune.Value);
// BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
if (rune.GetColumns () < 2)
{
Curses.mvaddch (row, col, rune.Value);
}
else /*if (col + 1 < Cols)*/
{
Curses.mvaddwstr (row, col, rune.ToString ());
}
}
else /*if (col + 1 < Cols)*/
else
{
Curses.mvaddwstr (row, col, rune.ToString ());

if (rune.GetColumns () > 1 && col + 1 < Cols)
{
// TODO: This is a hack to deal with non-BMP and wide characters.
//col++;
Curses.mvaddch (row, ++col, '*');
}
}
}
else
}

if (!RunningUnitTests)
{
Curses.move (Row, Col);
_window.wrefresh ();
}
}
else
{
if (RunningUnitTests
|| Console.WindowHeight < 1
|| Contents.Length != Rows * Cols
|| Rows != Console.WindowHeight)
{
return;
}

var top = 0;
var left = 0;
int rows = Rows;
int cols = Cols;
var output = new StringBuilder ();
Attribute? redrawAttr = null;
int lastCol = -1;

CursorVisibility? savedVisibility = _currentCursorVisibility;
SetCursorVisibility (CursorVisibility.Invisible);

for (int row = top; row < rows; row++)
{
if (Console.WindowHeight < 1)
{
return;
}

if (!_dirtyLines [row])
{
continue;
}

if (!SetCursorPosition (0, row))
{
Curses.mvaddwstr (row, col, rune.ToString ());
return;
}

_dirtyLines [row] = false;
output.Clear ();

if (rune.GetColumns () > 1 && col + 1 < Cols)
for (int col = left; col < cols; col++)
{
lastCol = -1;
var outputWidth = 0;

for (; col < cols; col++)
{
// TODO: This is a hack to deal with non-BMP and wide characters.
//col++;
Curses.mvaddch (row, ++col, '*');
if (!Contents [row, col].IsDirty)
{
if (output.Length > 0)
{
WriteToConsole (output, ref lastCol, row, ref outputWidth);
}
else if (lastCol == -1)
{
lastCol = col;
}

if (lastCol + 1 < cols)
{
lastCol++;
}

continue;
}

if (lastCol == -1)
{
lastCol = col;
}

Attribute attr = Contents [row, col].Attribute.Value;

// Performance: Only send the escape sequence if the attribute has changed.
if (attr != redrawAttr)
{
redrawAttr = attr;

output.Append (
EscSeqUtils.CSI_SetForegroundColorRGB (
attr.Foreground.R,
attr.Foreground.G,
attr.Foreground.B
)
);

output.Append (
EscSeqUtils.CSI_SetBackgroundColorRGB (
attr.Background.R,
attr.Background.G,
attr.Background.B
)
);
}

outputWidth++;
Rune rune = Contents [row, col].Rune;
output.Append (rune);

if (Contents [row, col].CombiningMarks.Count > 0)
{
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
// compatible with the driver architecture. Any CMs (except in the first col)
// are correctly combined with the base char, but are ALSO treated as 1 column
// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
//
// For now, we just ignore the list of CMs.
//foreach (var combMark in Contents [row, col].CombiningMarks) {
// output.Append (combMark);
//}
// WriteToConsole (output, ref lastCol, row, ref outputWidth);
}
else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
{
WriteToConsole (output, ref lastCol, row, ref outputWidth);
SetCursorPosition (col - 1, row);
}

Contents [row, col].IsDirty = false;
}
}

if (output.Length > 0)
{
SetCursorPosition (lastCol, row);
Console.Write (output);
}
}
}

if (!RunningUnitTests)
{
Curses.move (Row, Col);
_window.wrefresh ();
SetCursorPosition (0, 0);

_currentCursorVisibility = savedVisibility;

void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
{
SetCursorPosition (lastCol, row);
Console.Write (output);
output.Clear ();
lastCol += outputWidth;
outputWidth = 0;
}
}
}

private bool SetCursorPosition (int col, int row)
{
// + 1 is needed because non-Windows is based on 1 instead of 0 and
// Console.CursorTop/CursorLeft isn't reliable.
Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));

return true;
}

internal override void End ()
{
StopReportingMouseMoves ();
Expand Down Expand Up @@ -405,7 +567,11 @@ internal override MainLoop Init ()
if (!RunningUnitTests)
{
Curses.CheckWinChange ();
Curses.refresh ();

if (Force16Colors)
{
Curses.refresh ();
}
}

return new MainLoop (_mainLoopDriver);
Expand Down Expand Up @@ -852,7 +1018,8 @@ bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
/// <returns></returns>
private static Attribute MakeColor (short foreground, short background)
{
var v = (short)((ushort)foreground | (background << 4));
//var v = (short)((ushort)foreground | (background << 4));
var v = (short)(((ushort)(foreground & 0xffff) << 16) | (background & 0xffff));

// TODO: for TrueColor - Use InitExtendedPair
Curses.InitColorPair (v, foreground, background);
Expand All @@ -872,7 +1039,7 @@ private static Attribute MakeColor (short foreground, short background)
/// </remarks>
public override Attribute MakeColor (in Color foreground, in Color background)
{
if (!RunningUnitTests)
if (!RunningUnitTests && Force16Colors)
{
return MakeColor (
ColorNameToCursesColorNumber (foreground.GetClosestNamedColor16 ()),
Expand Down
2 changes: 1 addition & 1 deletion UnitTests/ConsoleDrivers/DriverColorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void SetColors_Changes_Colors (Type driverType)

//[InlineData (typeof (ANSIDriver), true)]
[InlineData (typeof (WindowsDriver), true)]
[InlineData (typeof (CursesDriver), false)]
[InlineData (typeof (CursesDriver), true)]
public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting)
{
var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
Expand Down
Loading