Skip to content

White space render improvments #33

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

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
14 changes: 14 additions & 0 deletions FileDiff/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@ public static SolidColorBrush MovedToBackground
}
}

private static SolidColorBrush whiteSpaceForeground;
public static SolidColorBrush WhiteSpaceForeground
{
get { return whiteSpaceForeground; }
set
{
whiteSpaceForeground = value;
whiteSpaceForeground.Freeze();
CurrentTheme.WhiteSpaceForeground = value.Color.ToString();
}
}


// Editor colors
private static SolidColorBrush lineNumberColor;
Expand Down Expand Up @@ -881,6 +893,8 @@ private static void UpdateCachedSettings()
MovedFromBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.MovedFromBackground));
MovedToBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.MovedToBackground));

WhiteSpaceForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.WhiteSpaceForeground));

// Editor colors
LineNumberColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.LineNumberColor));
CurrentDiffColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.CurrentDiffColor));
Expand Down
12 changes: 6 additions & 6 deletions FileDiff/BackgroundCompare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void Cancel()
CompareCancelled = true;
}

public static Tuple<List<Line>, List<Line>, TimeSpan> MatchFiles(List<Line> leftLines, List<Line> rightLines)
public static Tuple<List<Line>, List<Line>, TimeSpan> MatchFiles(List<Line> leftLines, List<Line> rightLines, FileEncoding leftFileEncoding, FileEncoding rightFileEncoding)
{

Debug.WriteLine(" - MatchFiles");
Expand All @@ -44,7 +44,7 @@ public static Tuple<List<Line>, List<Line>, TimeSpan> MatchFiles(List<Line> left

MatchLines(leftLines, rightLines);

AddFillerLines(ref leftLines, ref rightLines);
AddFillerLines(ref leftLines, ref rightLines, leftFileEncoding.SaveNewline, rightFileEncoding.SaveNewline);

ShiftDown(leftLines, rightLines);

Expand Down Expand Up @@ -721,7 +721,7 @@ private static int CountMatchingCharacters(List<char> leftRange, List<char> righ
return matchLength;
}

private static void AddFillerLines(ref List<Line> leftLines, ref List<Line> rightLines)
private static void AddFillerLines(ref List<Line> leftLines, ref List<Line> rightLines, NewlineMode leftNewline, NewlineMode rightNewline)
{
int rightIndex = 0;

Expand All @@ -733,13 +733,13 @@ private static void AddFillerLines(ref List<Line> leftLines, ref List<Line> righ
if (leftLines[leftIndex].MatchingLineIndex == null)
{
newLeft.Add(leftLines[leftIndex]);
newRight.Add(new Line() { Type = TextState.Filler });
newRight.Add(new Line() { Type = TextState.Filler, Newline = rightNewline });
}
else
{
while (rightIndex < leftLines[leftIndex].MatchingLineIndex)
{
newLeft.Add(new Line() { Type = TextState.Filler });
newLeft.Add(new Line() { Type = TextState.Filler, Newline = leftNewline });
newRight.Add(rightLines[rightIndex]);
rightIndex++;
}
Expand All @@ -750,7 +750,7 @@ private static void AddFillerLines(ref List<Line> leftLines, ref List<Line> righ
}
while (rightIndex < rightLines.Count)
{
newLeft.Add(new Line() { Type = TextState.Filler });
newLeft.Add(new Line() { Type = TextState.Filler, Newline = leftNewline });
newRight.Add(rightLines[rightIndex]);
rightIndex++;
}
Expand Down
2 changes: 2 additions & 0 deletions FileDiff/ColorTheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public class ColorTheme
public required string MovedFromBackground { get; set; }
public required string MovedToBackground { get; set; }

public required string WhiteSpaceForeground { get; set; }

// Editor colors
public required string LineNumberColor { get; set; }
public required string CurrentDiffColor { get; set; }
Expand Down
4 changes: 4 additions & 0 deletions FileDiff/DefaultSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public static class DefaultSettings
MovedFromBackground = "#FF2D1B1B",
MovedToBackground = "#FF1B261B",

WhiteSpaceForeground = "#FF3871A7",

// Editor colors
LineNumberColor = "#FF797979",
CurrentDiffColor = "#FF252525",
Expand Down Expand Up @@ -106,6 +108,8 @@ public static class DefaultSettings
MovedFromBackground = "#FFFFF0F0",
MovedToBackground = "#FFF0FFF0",

WhiteSpaceForeground = "#FF2E8CB5",

// Editor colors
LineNumberColor = "#FF585858",
CurrentDiffColor = "#FFB7B7B7",
Expand Down
135 changes: 126 additions & 9 deletions FileDiff/DiffControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class DiffControl : Control

private Typeface typeface;

private NewlineMode documentNewline;

private readonly DispatcherTimer blinkTimer = new(DispatcherPriority.Render);
private readonly Stopwatch stopwatch = new();

Expand Down Expand Up @@ -109,6 +111,9 @@ protected override void OnRender(DrawingContext drawingContext)
TextUtils.CreateGlyphRun("W", typeface, this.FontSize, dpiScale, 0, out characterWidth);
characterHeight = Math.Ceiling(TextUtils.FontHeight(typeface, this.FontSize, dpiScale) / dpiScale) * dpiScale;

GlyphRun crNewline = TextUtils.CreateGlyphRun("CR", typeface, this.FontSize, dpiScale, 0, out double crNewlineWidth);
GlyphRun lfNewline = TextUtils.CreateGlyphRun("LF", typeface, this.FontSize, dpiScale, 0, out double lfNewlineWidth);

//Color semiTransparent = Color.FromArgb(100, 0, 0, 0);

//Brush currentDiffBrush = new LinearGradientBrush(semiTransparent, Colors.Transparent, 0);
Expand All @@ -129,6 +134,11 @@ protected override void OnRender(DrawingContext drawingContext)
currentDiffPen.Freeze();
GuidelineSet currentDiffGuide = CreateGuidelineSet(currentDiffPen);

Pen whiteSpacePen = new(AppSettings.WhiteSpaceForeground, RoundToWholePixels(characterHeight * .06)) { StartLineCap = PenLineCap.Flat, EndLineCap = PenLineCap.Square };
whiteSpacePen.Freeze();
GuidelineSet whiteSpacePenGuide = CreateGuidelineSet(whiteSpacePen);
double penMargin = whiteSpacePen.Thickness * 1.7;

textMargin = RoundToWholePixels(4);
lineNumberMargin = RoundToWholePixels(characterWidth * Lines.Count.ToString().Length) + (2 * textMargin) + borderPen.Thickness;

Expand Down Expand Up @@ -216,14 +226,14 @@ protected override void OnRender(DrawingContext drawingContext)
drawingContext.PushTransform(new TranslateTransform(lineNumberMargin + textMargin - HorizontalOffset, 0));
{
// Draw line
if (line.Text != "")
if (line.Text != "" || AppSettings.ShowWhiteSpaceCharacters)
{
double nextPosition = 0;
foreach (TextSegment textSegment in line.TextSegments)
{
drawingContext.PushTransform(new TranslateTransform(nextPosition, 0));

GlyphRun segmentRun = textSegment.GetRenderedText(typeface, this.FontSize, dpiScale, AppSettings.ShowWhiteSpaceCharacters, AppSettings.TabSize, nextPosition, out double runWidth);
GlyphRun segmentRun = textSegment.GetRenderedText(typeface, this.FontSize, dpiScale, AppSettings.TabSize, nextPosition, out double runWidth);

if (nextPosition - HorizontalOffset < ActualWidth && nextPosition + runWidth - HorizontalOffset > 0)
{
Expand All @@ -233,12 +243,62 @@ protected override void OnRender(DrawingContext drawingContext)
}

drawingContext.DrawGlyphRun(AppSettings.ShowLineChanges ? textSegment.ForegroundBrush : line.ForegroundBrush, segmentRun);

// Draw white space characters
if (AppSettings.ShowWhiteSpaceCharacters)
{
drawingContext.PushGuidelineSet(whiteSpacePenGuide);
{
double offset = 0;
for (int characterIndex = 0; characterIndex < textSegment.Text.Length; characterIndex++)
{
char character = textSegment.Text[characterIndex];
double characterWidth = segmentRun.AdvanceWidths[characterIndex];
double arrowSize = characterHeight * .2;
double centerY = RoundToWholePixels(characterHeight / 2);

if (character.In([' ', '\t']))
{
//drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(50, 128, 128, 128)), null, new Rect(offset, 0, characterWidth, characterHeight));

if (character == ' ')
{
drawingContext.DrawEllipse(AppSettings.WhiteSpaceForeground, null, new Point(offset + characterWidth / 2, centerY), arrowSize * .4, arrowSize * .4);
}
if (character == '\t')
{
drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - penMargin, centerY), new Point(offset + penMargin, centerY));

drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - arrowSize - penMargin, centerY - arrowSize), new Point(offset + characterWidth - penMargin, centerY));
drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - arrowSize - penMargin, centerY + arrowSize), new Point(offset + characterWidth - penMargin, centerY));
}
}

offset += characterWidth;
}
}
drawingContext.Pop();
}
}
nextPosition += runWidth;

drawingContext.Pop();
}
maxTextWidth = Math.Max(maxTextWidth, nextPosition);

// Draw newline characters
if (AppSettings.ShowWhiteSpaceCharacters && !line.IsFiller)
{
if (line.Newline == NewlineMode.Windows || line.Newline == NewlineMode.Mac)
{
nextPosition = DrawNewlineCharacter(crNewline, crNewlineWidth, nextPosition);
}

if (line.Newline == NewlineMode.Windows || line.Newline == NewlineMode.Unix)
{
nextPosition = DrawNewlineCharacter(lfNewline, lfNewlineWidth, nextPosition);
}
}
}

// Draw cursor
Expand Down Expand Up @@ -341,13 +401,51 @@ protected override void OnRender(DrawingContext drawingContext)
#if DEBUG
ReportRenderTime();
#endif

double DrawNewlineCharacter(GlyphRun NewlineText, double crNewlineWidth, double nextPosition)
{
Rect nlRect = NewlineText.ComputeAlignmentBox();
//r.Top -= crNewline.BaselineOrigin.Y;
drawingContext.PushTransform(new TranslateTransform(nextPosition + penMargin * 3, 0));
{

// drawingContext.DrawRoundedRectangle(Brushes.Yellow, null, nlRect, penMargin, penMargin);


drawingContext.PushGuidelineSet(whiteSpacePenGuide);
{
Rect r = new Rect(
0,
RoundToWholePixels((characterHeight - nlRect.Height) / 2),
RoundToWholePixels(nlRect.Width),
RoundToWholePixels(nlRect.Height)
);

drawingContext.DrawRoundedRectangle(null, whiteSpacePen, r, penMargin, penMargin);
// drawingContext.DrawRoundedRectangle(null, whiteSpacePen, new Rect(0, RoundToWholePixels(whiteSpacePen.Thickness), RoundToWholePixels(crNewlineWidth + whiteSpacePen.Thickness * 3), RoundToWholePixels(characterHeight - whiteSpacePen.Thickness * 2)), penMargin, penMargin);

}
drawingContext.Pop();

drawingContext.PushTransform(new TranslateTransform(0, 0));
{
drawingContext.DrawGlyphRun(AppSettings.WhiteSpaceForeground, NewlineText);
}
drawingContext.Pop();

}
drawingContext.Pop();
nextPosition += penMargin * 3 + crNewlineWidth;
return nextPosition;
}

}

protected override void OnTextInput(TextCompositionEventArgs e)
{
if (EditMode && e.Text.Length > 0 && Lines.Count > 0)
{
if (e.Text == "\b")
if (e.Text == "\b") // Backspace key pressed
{
if (Selection != null)
{
Expand All @@ -362,6 +460,10 @@ protected override void OnTextInput(TextCompositionEventArgs e)
cursorCharacter = Lines[cursorLine - 1].Text.Length;
SetLineText(cursorLine - 1, Lines[cursorLine - 1].Text + Lines[cursorLine].Text);
RemoveLine(cursorLine);
if (cursorLine == Lines.Count - 1) // Backspace on last line, remove newline character from line above
{
Lines[cursorLine - 1].Newline = null;
}
cursorLine--;
}
}
Expand All @@ -373,12 +475,15 @@ protected override void OnTextInput(TextCompositionEventArgs e)
}
}
}
else if (e.Text == "\r")
else if (e.Text == "\r") // Enter key pressed
{
if (Selection != null)
{
DeleteSelection();
}

Lines[cursorLine].Newline = documentNewline;

InsertNewLine(cursorLine + 1, Lines[cursorLine].Text[cursorCharacter..]);
SetLineText(cursorLine, Lines[cursorLine].Text[..cursorCharacter]);
cursorLine++;
Expand Down Expand Up @@ -438,6 +543,11 @@ protected override void OnKeyDown(KeyEventArgs e)
SetLineText(cursorLine, Lines[cursorLine].Text + Lines[cursorLine + 1].Text);
RemoveLine(cursorLine + 1);
}
else if (cursorLine == Lines.Count - 1 && Lines[cursorLine].Newline != null) // Delete newline character on last line
{
Lines[cursorLine].Newline = null;
Edited = true;
}
}
else
{
Expand All @@ -457,7 +567,7 @@ protected override void OnKeyDown(KeyEventArgs e)
{
if (Lines[i].Text.Length > 0 && Lines[i].Text[0] == '\t')
{
Lines[i].Text = Lines[i].Text.Remove(0, 1);
Lines[i].Text = Lines[i].Text[1..];
}
}
else
Expand Down Expand Up @@ -528,7 +638,7 @@ protected override void OnKeyDown(KeyEventArgs e)
DeleteSelection();
}

string[] pastedRows = Clipboard.GetText().Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
string[] pastedRows = Clipboard.GetText().Split(["\r\n", "\n"], StringSplitOptions.None);

string leftOfCursor = Lines[cursorLine].Text[..cursorCharacter];
string rightOfCursor = Lines[cursorLine].Text[cursorCharacter..];
Expand Down Expand Up @@ -861,7 +971,14 @@ protected override void OnToolTipOpening(ToolTipEventArgs e)
public ObservableCollection<Line> Lines
{
get { return (ObservableCollection<Line>)GetValue(LinesProperty); }
set { SetValue(LinesProperty, value); }
set
{
SetValue(LinesProperty, value);
if (value.Count > 0)
{
this.documentNewline = value[0].Newline ?? NewlineMode.Windows;
}
}
}


Expand Down Expand Up @@ -1026,7 +1143,7 @@ private void InsertNewLine(int index, string newText)
{
this.CurrentDiff = null;

Lines.Insert(index, new Line() { Text = newText, LineIndex = -1 });
Lines.Insert(index, new Line() { Text = newText, LineIndex = -1, Newline = cursorLine == Lines.Count - 1 ? null : documentNewline });
Edited = true;
}

Expand Down Expand Up @@ -1146,7 +1263,7 @@ private double CharacterPosition(int lineIndex, int characterIndex)

foreach (TextSegment textSegment in Lines[lineIndex].TextSegments)
{
if (textSegment.GetRenderedText(typeface, this.FontSize, dpiScale, AppSettings.ShowWhiteSpaceCharacters, AppSettings.TabSize, startPosition, out double runWidth) != null)
if (textSegment.GetRenderedText(typeface, this.FontSize, dpiScale, AppSettings.TabSize, startPosition, out double runWidth) != null)
{
foreach (double x in textSegment.RenderedText.AdvanceWidths)
{
Expand Down
Loading