Skip to content

Commit

Permalink
get colors working, simplify SGR quite a lot.
Browse files Browse the repository at this point in the history
  • Loading branch information
doubleyewdee committed Nov 27, 2018
1 parent 7c34a5f commit 9d10e5e
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 149 deletions.
117 changes: 52 additions & 65 deletions src/ConsoleBuffer/Buffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public sealed class Buffer : INotifyPropertyChanged
private long receivedCharacters;
private long wrapCharacter;
private int currentChar;
private Character characterTemplate = new Character { Glyph = 0x20 };

/// <summary>
/// we store X/Y as 0-offset indexes for convenience. escape codes will pass these around as 1-offset (top left is 1,1)
Expand Down Expand Up @@ -72,17 +73,11 @@ public Buffer(short width, short height)
this.CursorVisible = this.CursorBlink = true;
this.cursorX = this.cursorY = 0;

var defaultChar = new Character()
{
Options = Character.BasicColorOptions(Commands.SetGraphicsRendition.Colors.White, Commands.SetGraphicsRendition.Colors.Black),
Glyph = 0x20,
};
this.HandleSGR(new Commands.SetGraphicsRendition(string.Empty));

for (var y = 0; y < this.Height; ++y)
{
var line = new Line(null);
line.Set(0, defaultChar);
this.lines.PushBack(line);
this.lines.PushBack(new Line());
}
this.topVisibleLine = 0;
this.bottomVisibleLine = this.MaxCursorY;
Expand Down Expand Up @@ -152,30 +147,9 @@ private void PrintAtCursor(int ch)
this.wrapCharacter = -1;
}

// if we have an explicit value for the current cell hold on to it, otherwise inherit from the previous cell since
// we don't know what other activity has been done with cursor shuffling/SGR/etc.
var cell = this.lines[this.CurrentLine].Get(this.cursorX);
if (cell.ForegroundExplicit || cell.BackgroundExplicit || (this.cursorX == 0 && this.cursorY == 0))
{
cell = this.ColorCharacter(cell);
cell.Glyph = ch;
this.lines[this.CurrentLine].Set(this.cursorX, cell);
}
else
{
var x = this.cursorX;
var y = this.cursorY;
if (x == 0)
{
--y;
x = this.MaxCursorX;
}
var line = this.lines[this.topVisibleLine + y];
var inheritChar = line.Get(x);
inheritChar = this.ColorCharacter(inheritChar);
inheritChar.Glyph = ch;
this.lines[this.CurrentLine].Set(this.cursorX, inheritChar);
}
var newChar = this.characterTemplate;
newChar.Glyph = ch;
this.lines[this.CurrentLine].Set(this.cursorX, newChar);

if (this.cursorX == this.MaxCursorX)
{
Expand Down Expand Up @@ -405,11 +379,51 @@ private void HandleSetCursorPosition(Commands.SetCursorPosition scp)

private void HandleSGR(Commands.SetGraphicsRendition sgr)
{
var updatedCell = this.lines[this.CurrentLine].Get(this.cursorX);
if (sgr.ForegroundBright == Commands.SetGraphicsRendition.FlagValue.Set) updatedCell.Options |= Character.ForegroundBrightFlag;
if (sgr.ForegroundBright == Commands.SetGraphicsRendition.FlagValue.Unset) updatedCell.Options &= ~Character.ForegroundBrightFlag;
var newTemplate = this.characterTemplate;

if (sgr.ForegroundBright == Commands.SetGraphicsRendition.FlagValue.Set) newTemplate.Options |= Character.ForegroundBrightFlag;
if (sgr.ForegroundBright == Commands.SetGraphicsRendition.FlagValue.Unset) newTemplate.Options &= ~Character.ForegroundBrightFlag;
if (sgr.BackgroundBright == Commands.SetGraphicsRendition.FlagValue.Set) newTemplate.Options |= Character.BackgroundBrightFlag;
if (sgr.BackgroundBright == Commands.SetGraphicsRendition.FlagValue.Unset) newTemplate.Options &= ~Character.BackgroundBrightFlag;
if (sgr.Underline == Commands.SetGraphicsRendition.FlagValue.Set) newTemplate.Options |= Character.UnderlineFlag;
if (sgr.Underline == Commands.SetGraphicsRendition.FlagValue.Unset) newTemplate.Options &= ~Character.UnderlineFlag;
if (sgr.Inverse == Commands.SetGraphicsRendition.FlagValue.Set) newTemplate.Options |= Character.InverseFlag;
if (sgr.Inverse == Commands.SetGraphicsRendition.FlagValue.Unset) newTemplate.Options &= ~Character.InverseFlag;

if (sgr.HaveBasicForeground)
{
newTemplate.Options &= ~Character.ForegroundExtendedFlag;
newTemplate.Options |= Character.ForegroundBasicColorFlag;
newTemplate.Options &= ~Character.ForegroundColorMask;
newTemplate.Options |= Character.GetColorFlags(sgr.BasicForegroundColor, false);
}
else if (sgr.HaveForeground)
{
newTemplate.Options &= ~Character.ForegroundBasicColorFlag;
newTemplate.Options &= ~Character.ForegroundColorMask;
newTemplate.Options |= Character.ForegroundExtendedFlag;
newTemplate.Foreground = sgr.ForegroundColor;
}

this.lines[this.CurrentLine].Set(this.cursorX, updatedCell);
if (sgr.HaveBasicBackground)
{
newTemplate.Options &= ~Character.BackgroundExtendedFlag;
newTemplate.Options |= Character.BackgroundBasicColorFlag;
newTemplate.Options &= ~Character.BackgroundColorMask;
newTemplate.Options |= Character.GetColorFlags(sgr.BasicBackgroundColor, true);
}
else if (sgr.HaveBackground)
{
newTemplate.Options &= ~Character.BackgroundBasicColorFlag;
newTemplate.Options &= ~Character.BackgroundColorMask;
newTemplate.Options |= Character.BackgroundExtendedFlag;
newTemplate.Background = sgr.BackgroundColor;
}

if (newTemplate.HasBasicForegroundColor) newTemplate.Foreground = this.GetColorInfoFromBasicColor(newTemplate.BasicForegroundColor, newTemplate.ForegroundBright);
if (newTemplate.HasBasicBackgroundColor) newTemplate.Background = this.GetColorInfoFromBasicColor(newTemplate.BasicBackgroundColor, newTemplate.BackgroundBright);

this.characterTemplate = newTemplate;
}

private void HandleSetMode(Commands.SetMode sm)
Expand All @@ -436,47 +450,20 @@ private void ScrollDown(int lines = 1)
--lines;
if (this.bottomVisibleLine == this.lines.Capacity - 1)
{
this.lines.PushBack(new Line(this.lines[this.bottomVisibleLine])); // will force an old line from the buffer;
this.lines.PushBack(new Line()); // will force an old line from the buffer;
}
else
{
++this.topVisibleLine;
++this.bottomVisibleLine;
if (this.lines.Size <= this.bottomVisibleLine)
{
this.lines.PushBack(new Line(this.lines[this.bottomVisibleLine - 1]));
this.lines.PushBack(new Line());
}
}
}
}

/// <summary>
/// Fills in the R/G/B values for a character based on the associated flags.
/// </summary>
/// <param name="ch">Character to color.</param>
/// <returns>The colored version of the character.</returns>
private Character ColorCharacter(Character ch)
{
var ret = new Character(ch);
if (ret.Inverse)
{
// NB: leaves the inverse bit on background, shouldn't matter.
ret.Foreground = ch.Background;
ret.Background = ch.Foreground;
}

if (ch.HasBasicForegroundColor)
{
ret.Foreground = this.GetColorInfoFromBasicColor(ret.BasicForegroundColor, ret.ForegroundBright);
}
if (ch.HasBasicBackgroundColor)
{
ret.Background = this.GetColorInfoFromBasicColor(ret.BasicBackgroundColor, ret.BackgroundBright);
}

return ret;
}

private Character.ColorInfo GetColorInfoFromBasicColor(Commands.SetGraphicsRendition.Colors basicColor, bool isBright)
{
var paletteOffset = isBright ? 8 : 0;
Expand Down
83 changes: 28 additions & 55 deletions src/ConsoleBuffer/Character.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ConsoleBuffer
{
using System;
using System.Text;

// XXX: Gonna end up with a lot of these and they're really freakin' big.
// could consider a morphable type with different sizes to avoid the (currently) 12 bytes-per-character issue.
Expand All @@ -25,93 +26,65 @@ public override string ToString()

// traditional colors occupy 3 bits, we keep two sets (foreground + background).
// the actual colors are declared in the SGR command.
private const short ForegroundColorMask = 0x0007;
internal const short ForegroundColorMask = 0x0007;
private const short BackgroundBitShift = 3;
private const short BackgroundColorMask = ForegroundColorMask << BackgroundBitShift;
internal const short BackgroundColorMask = ForegroundColorMask << BackgroundBitShift;
// flags
internal const short ForegroundBasicColorFlag = 0x0001 << 6;
internal const short BackgroundBasicColorFlag = 0x0002 << 6;
internal const short ForegroundBrightFlag = 0x0004 << 6;
internal const short BackgroundBrightFlag = 0x0008 << 6;
internal const short UnderlineFlag = 0x0010 << 6;
internal const short InverseFlag = 0x0020 << 6;
internal const short ForegroundExplicitFlag = 0x0040 << 6;
internal const short BackgroundExplicitFlag = 0x0080 << 6;
internal const short ExplicitFlags = (ForegroundExplicitFlag | BackgroundBrightFlag);
internal const short ForegroundExtendedFlag = 0x0100 << 6;
internal const short BackgroundExtendedFlag = unchecked((short)(0x0200 << 6));
internal const short ForegroundExtendedFlag = 0x0040 << 6;
internal const short BackgroundExtendedFlag = 0x0080 << 6;

internal short Options;

internal static short BasicColorOptions(Commands.SetGraphicsRendition.Colors foreground = Commands.SetGraphicsRendition.Colors.None,
Commands.SetGraphicsRendition.Colors background = Commands.SetGraphicsRendition.Colors.None)
internal static short GetColorFlags(Commands.SetGraphicsRendition.Colors color, bool background)
{
short options = 0;
if (foreground != Commands.SetGraphicsRendition.Colors.None)
var options = (short)color;
#if DEBUG
if (options < (short)Commands.SetGraphicsRendition.Colors.Black || options > (short)Commands.SetGraphicsRendition.Colors.White)
{
options |= (short)((short)foreground | ForegroundExplicitFlag | ForegroundBasicColorFlag);
}
if (background != Commands.SetGraphicsRendition.Colors.None)
{
options |= (short)(((short)background << BackgroundBitShift) | BackgroundExplicitFlag | BackgroundBasicColorFlag);
throw new ArgumentOutOfRangeException(nameof(color));
}
#endif

return options;
return background ? (short)(options << BackgroundBitShift) : options;
}

internal Commands.SetGraphicsRendition.Colors BasicForegroundColor => (Commands.SetGraphicsRendition.Colors)(this.Options & ForegroundColorMask);
internal bool HasBasicForegroundColor => (this.Options & ForegroundBasicColorFlag) != 0;
internal Commands.SetGraphicsRendition.Colors BasicBackgroundColor => (Commands.SetGraphicsRendition.Colors)(this.Options & BackgroundColorMask);
internal Commands.SetGraphicsRendition.Colors BasicBackgroundColor => (Commands.SetGraphicsRendition.Colors)((this.Options & BackgroundColorMask) >> BackgroundBitShift);
internal bool HasBasicBackgroundColor => (this.Options & BackgroundBasicColorFlag) != 0;
internal bool ForegroundBright => (this.Options & ForegroundBrightFlag) != 0;
internal bool BackgroundBright => (this.Options & BackgroundBrightFlag) != 0;
internal bool Underline => (this.Options & UnderlineFlag) != 0;
// this is the only property we cannot handle rendering of internally by setting appropriate RGB color values.
public bool Underline => (this.Options & UnderlineFlag) != 0;
internal bool Inverse => (this.Options & InverseFlag) != 0;
internal bool ForegroundExplicit => (this.Options & ForegroundExplicitFlag) != 0;
internal bool BackgroundExplicit => (this.Options & BackgroundExplicitFlag) != 0;
internal bool ForegroundExtended => (this.Options & ForegroundExtendedFlag) != 0;
internal bool BackgroundExtended => (this.Options & BackgroundExtendedFlag) != 0;

internal short InheritedOptions => (short)(this.Options & ~(ForegroundExplicitFlag | BackgroundExplicitFlag));

/// <summary>
/// The unicode glyph for this character.
/// </summary>
public int Glyph { get; set; } // XXX: a single int isn't sufficient to represent emoji with ZWJ. fix later.

public Character(Character parent)
{
this.Foreground = parent.Foreground;
this.Background = parent.Background;
this.Glyph = parent.Glyph;
this.Options = parent.Options;
this.Options &= ~ExplicitFlags;
}

public Character(Character parent, Commands.SetGraphicsRendition sgr)
: this(parent)
public override string ToString()
{
switch (sgr.ForegroundBright)
{
case Commands.SetGraphicsRendition.FlagValue.Set:
this.Options |= (ForegroundBrightFlag | ForegroundExplicitFlag);
break;
case Commands.SetGraphicsRendition.FlagValue.Unset:
this.Options &= ~ForegroundBrightFlag;
this.Options |= ForegroundExplicitFlag;
break;
}

switch (sgr.BackgroundBright)
{
case Commands.SetGraphicsRendition.FlagValue.Set:
this.Options |= (BackgroundBrightFlag | BackgroundExplicitFlag);
break;
case Commands.SetGraphicsRendition.FlagValue.Unset:
this.Options &= ~BackgroundBrightFlag;
this.Options |= BackgroundExplicitFlag;
break;
}
var sb = new StringBuilder();
sb.Append($"'{(char)this.Glyph}' (opt:");
if (this.ForegroundBright) sb.Append(" bright");
if (this.Underline) sb.Append(" ul");
if (this.Inverse) sb.Append(" inv");
if (this.BackgroundBright) sb.Append(" bgBright");
if (this.HasBasicForegroundColor) sb.Append($" fg:{this.BasicForegroundColor}");
if (this.HasBasicBackgroundColor) sb.Append($" bg:{this.BasicBackgroundColor}");
if (this.ForegroundExtended) sb.Append($" efg:#{this.Foreground.R:x2}{this.Foreground.G:x2}{this.Foreground.B:x2}");
if (this.BackgroundExtended) sb.Append($" ebg:#{this.Background.R:x2}{this.Background.G:x2}{this.Background.B:x2}");
sb.Append(')');
return sb.ToString();
}
}
}
Loading

0 comments on commit 9d10e5e

Please sign in to comment.