Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
133 changes: 63 additions & 70 deletions Terminal.Gui/Core/Graphs/LineCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ namespace Terminal.Gui.Graphs {
/// </summary>
public class LineCanvas {


private List<StraightLine> lines = new List<StraightLine> ();

Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
{IntersectionRuneType.ULCorner,new ULIntersectionRuneResolver()},
{IntersectionRuneType.URCorner,new URIntersectionRuneResolver()},
{IntersectionRuneType.LLCorner,new LLIntersectionRuneResolver()},
Expand Down Expand Up @@ -65,7 +65,7 @@ public void AddLine (Point from, int length, Orientation orientation, BorderStyl
for (int x = 0; x < inArea.Width; x++) {

var intersects = lines
.Select (l => l.Intersects (x, y))
.Select (l => l.Intersects (inArea.X + x, inArea.Y + y))
.Where (i => i != null)
.ToArray ();

Expand Down Expand Up @@ -100,15 +100,14 @@ public void Draw (View view, Rect bounds)
}
}

private abstract class IntersectionRuneResolver
{
private abstract class IntersectionRuneResolver {
readonly Rune round;
readonly Rune doubleH;
readonly Rune doubleV;
readonly Rune doubleBoth;
readonly Rune normal;

public IntersectionRuneResolver(Rune round, Rune doubleH, Rune doubleV, Rune doubleBoth, Rune normal)
public IntersectionRuneResolver (Rune round, Rune doubleH, Rune doubleV, Rune doubleBoth, Rune normal)
{
this.round = round;
this.doubleH = doubleH;
Expand All @@ -121,93 +120,87 @@ public IntersectionRuneResolver(Rune round, Rune doubleH, Rune doubleV, Rune dou
{
var useRounded = intersects.Any (i => i.Line.Style == BorderStyle.Rounded && i.Line.Length != 0);

bool doubleHorizontal = intersects.Any(l=>l.Line.Orientation == Orientation.Horizontal && l.Line.Style == BorderStyle.Double);
bool doubleVertical = intersects.Any(l=>l.Line.Orientation == Orientation.Vertical && l.Line.Style == BorderStyle.Double);
bool doubleHorizontal = intersects.Any (l => l.Line.Orientation == Orientation.Horizontal && l.Line.Style == BorderStyle.Double);
bool doubleVertical = intersects.Any (l => l.Line.Orientation == Orientation.Vertical && l.Line.Style == BorderStyle.Double);


if(doubleHorizontal)
{
return doubleVertical ? doubleBoth : doubleH;
if (doubleHorizontal) {
return doubleVertical ? doubleBoth : doubleH;
}

if(doubleVertical)
{

if (doubleVertical) {
return doubleV;
}

return useRounded ? round : normal;
}
}

private class ULIntersectionRuneResolver : IntersectionRuneResolver
{
public ULIntersectionRuneResolver() :
base('╭','╒','╓','╔','┌')
private class ULIntersectionRuneResolver : IntersectionRuneResolver {
public ULIntersectionRuneResolver () :
base ('╭', '╒', '╓', '╔', '┌')
{

}
}
private class URIntersectionRuneResolver : IntersectionRuneResolver
{
private class URIntersectionRuneResolver : IntersectionRuneResolver {

public URIntersectionRuneResolver() :
base('╮','╕','╖','╗','┐')
public URIntersectionRuneResolver () :
base ('╮', '╕', '╖', '╗', '┐')
{

}
}
private class LLIntersectionRuneResolver : IntersectionRuneResolver
{
private class LLIntersectionRuneResolver : IntersectionRuneResolver {

public LLIntersectionRuneResolver() :
base('╰','╘','╙','╚','└')
public LLIntersectionRuneResolver () :
base ('╰', '╘', '╙', '╚', '└')
{

}
}
private class LRIntersectionRuneResolver : IntersectionRuneResolver
{
public LRIntersectionRuneResolver() :
base('╯','╛','╜','╝','┘')
private class LRIntersectionRuneResolver : IntersectionRuneResolver {
public LRIntersectionRuneResolver () :
base ('╯', '╛', '╜', '╝', '┘')
{

}
}

private class TopTeeIntersectionRuneResolver : IntersectionRuneResolver
{
public TopTeeIntersectionRuneResolver():
base('┬','╤','╥','╦','┬'){
}
private class TopTeeIntersectionRuneResolver : IntersectionRuneResolver {
public TopTeeIntersectionRuneResolver () :
base ('┬', '╤', '╥', '╦', '┬')
{

}
}
private class LeftTeeIntersectionRuneResolver : IntersectionRuneResolver
{
public LeftTeeIntersectionRuneResolver():
base('├','╞','╟','╠','├'){
}
private class LeftTeeIntersectionRuneResolver : IntersectionRuneResolver {
public LeftTeeIntersectionRuneResolver () :
base ('├', '╞', '╟', '╠', '├')
{

}
}
private class RightTeeIntersectionRuneResolver : IntersectionRuneResolver
{
public RightTeeIntersectionRuneResolver():
base('┤','╡','╢','╣','┤'){
}
private class RightTeeIntersectionRuneResolver : IntersectionRuneResolver {
public RightTeeIntersectionRuneResolver () :
base ('┤', '╡', '╢', '╣', '┤')
{

}
}
private class BottomTeeIntersectionRuneResolver : IntersectionRuneResolver
{
public BottomTeeIntersectionRuneResolver():
base('┴','╧','╨','╩','┴'){
}
private class BottomTeeIntersectionRuneResolver : IntersectionRuneResolver {
public BottomTeeIntersectionRuneResolver () :
base ('┴', '╧', '╨', '╩', '┴')
{

}
}
private class CrosshairIntersectionRuneResolver : IntersectionRuneResolver
{
public CrosshairIntersectionRuneResolver():
base('┼','╪','╫','╬','┼'){
}
private class CrosshairIntersectionRuneResolver : IntersectionRuneResolver {
public CrosshairIntersectionRuneResolver () :
base ('┼', '╪', '╫', '╬', '┼')
{

}
}

private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
Expand All @@ -217,7 +210,7 @@ public CrosshairIntersectionRuneResolver():

var runeType = GetRuneTypeForIntersects (intersects);

if(runeResolvers.ContainsKey (runeType)) {
if (runeResolvers.ContainsKey (runeType)) {
return runeResolvers [runeType].GetRuneForIntersects (driver, intersects);
}

Expand All @@ -228,13 +221,13 @@ public CrosshairIntersectionRuneResolver():
// TODO: maybe make these resolvers to for simplicity?
// or for dotted lines later on or that kind of thing?
switch (runeType) {
case IntersectionRuneType.None:
case IntersectionRuneType.None:
return null;
case IntersectionRuneType.Dot:
case IntersectionRuneType.Dot:
return (Rune)'.';
case IntersectionRuneType.HLine:
case IntersectionRuneType.HLine:
return useDouble ? driver.HDLine : driver.HLine;
case IntersectionRuneType.VLine:
case IntersectionRuneType.VLine:
return useDouble ? driver.VDLine : driver.VLine;
default: throw new Exception ("Could not find resolver or switch case for " + nameof (runeType) + ":" + runeType);
}
Expand All @@ -243,7 +236,7 @@ public CrosshairIntersectionRuneResolver():

private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] intersects)
{
if(intersects.All(i=>i.Line.Length == 0)) {
if (intersects.All (i => i.Line.Length == 0)) {
return IntersectionRuneType.Dot;
}

Expand Down
84 changes: 67 additions & 17 deletions UnitTests/Core/LineCanvasTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Terminal.Gui.Graphs;
using System;
using System.Collections.Generic;
using System.Text;
using Terminal.Gui.Graphs;
using Xunit;
using Xunit.Abstractions;

Expand Down Expand Up @@ -57,7 +60,7 @@ public void TestLineCanvas_Horizontal_Double ()
}

[InlineData (BorderStyle.Single)]
[InlineData(BorderStyle.Rounded)]
[InlineData (BorderStyle.Rounded)]
[Theory, AutoInitShutdown]
public void TestLineCanvas_Vertical (BorderStyle style)
{
Expand Down Expand Up @@ -93,7 +96,7 @@ public void TestLineCanvas_Vertical_Double ()
/// Not when they terminate adjacent to one another.
/// </summary>
[Fact, AutoInitShutdown]
public void TestLineCanvas_Corner_NoOverlap()
public void TestLineCanvas_Corner_NoOverlap ()
{
var v = GetCanvas (out var canvas);
canvas.AddLine (new Point (0, 0), 1, Orientation.Horizontal, BorderStyle.Single);
Expand Down Expand Up @@ -127,13 +130,14 @@ public void TestLineCanvas_Corner_Correct ()
│";
TestHelpers.AssertDriverContentsAre (looksLike, output);

}
[Fact,AutoInitShutdown]

[Fact, AutoInitShutdown]
public void TestLineCanvas_Window ()
{
var v = GetCanvas (out var canvas);

// outer box
canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Single);
canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Single);
Expand Down Expand Up @@ -168,10 +172,10 @@ public void TestLineCanvas_Window_Rounded ()

// outer box
canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Rounded);

// BorderStyle.Single is ignored because corner overlaps with the above line which is Rounded
// this results in a rounded corner being used.
canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Single);
canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Single);
canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, BorderStyle.Rounded);
canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Single);

Expand Down Expand Up @@ -220,8 +224,8 @@ public void TestLineCanvas_Window_Double ()


[Theory, AutoInitShutdown]
[InlineData(BorderStyle.Single)]
[InlineData(BorderStyle.Rounded)]
[InlineData (BorderStyle.Single)]
[InlineData (BorderStyle.Rounded)]
public void TestLineCanvas_Window_DoubleTop_SingleSides (BorderStyle thinStyle)
{
var v = GetCanvas (out var canvas);
Expand All @@ -233,7 +237,7 @@ public void TestLineCanvas_Window_DoubleTop_SingleSides (BorderStyle thinStyle)
canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, thinStyle);


canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical,thinStyle);
canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, thinStyle);
canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, BorderStyle.Double);

v.Redraw (v.Bounds);
Expand All @@ -250,17 +254,17 @@ public void TestLineCanvas_Window_DoubleTop_SingleSides (BorderStyle thinStyle)
}

[Theory, AutoInitShutdown]
[InlineData(BorderStyle.Single)]
[InlineData(BorderStyle.Rounded)]
[InlineData (BorderStyle.Single)]
[InlineData (BorderStyle.Rounded)]
public void TestLineCanvas_Window_SingleTop_DoubleSides (BorderStyle thinStyle)
{
var v = GetCanvas (out var canvas);

// outer box
canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, thinStyle);
canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Double);
canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal,thinStyle);
canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Double);
canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, thinStyle);
canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Double);


canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, BorderStyle.Double);
Expand All @@ -280,6 +284,52 @@ public void TestLineCanvas_Window_SingleTop_DoubleSides (BorderStyle thinStyle)
TestHelpers.AssertDriverContentsAre (looksLike, output);
}


[Theory, AutoInitShutdown]
[InlineData (0, 0, @"
═══
══
═══")]
[InlineData (1, 0, @"
══
══")]
[InlineData (2, 0, @"

═")]
[InlineData (0, 1, @"
══
═══")]
[InlineData (0, 2, @"
═══")]
public void TestLineCanvasRenderOffset_NoOffset (int xOffset, int yOffset, string expect)
{
var canvas = new LineCanvas ();
canvas.AddLine (new Point (0, 0), 2, Orientation.Horizontal, BorderStyle.Double);
canvas.AddLine (new Point (0, 1), 1, Orientation.Horizontal, BorderStyle.Double);
canvas.AddLine (new Point (0, 2), 2, Orientation.Horizontal, BorderStyle.Double);

var bmp = canvas.GenerateImage (new Rect (xOffset, yOffset, 3, 3));
var actual = BmpToString (bmp);
Assert.Equal (expect.TrimStart (), actual);

}

private string BmpToString (System.Rune? [,] bmp)
{
var sb = new StringBuilder ();
for (int y = 0; y < bmp.GetLength (1); y++) {
for (int x = 0; x < bmp.GetLength (0); x++) {
sb.Append (bmp [y, x]);
}
sb.AppendLine ();
}

return sb.ToString ().TrimEnd ();
}


private View GetCanvas (out LineCanvas canvas)
{
var v = new View {
Expand All @@ -288,8 +338,8 @@ private View GetCanvas (out LineCanvas canvas)
Bounds = new Rect (0, 0, 10, 5)
};

var canvasCopy = canvas = new LineCanvas ();
v.DrawContentComplete += (r)=> canvasCopy.Draw (v, v.Bounds);
var canvasCopy = canvas = new LineCanvas ();
v.DrawContentComplete += (r) => canvasCopy.Draw (v, v.Bounds);

return v;
}
Expand Down