Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
155 changes: 135 additions & 20 deletions Terminal.Gui/Core/Graphs/LineCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ public class LineCanvas {

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

Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
{IntersectionRuneType.ULCorner,new ULIntersectionRuneResolver()},
{IntersectionRuneType.URCorner,new URIntersectionRuneResolver()},
{IntersectionRuneType.LLCorner,new LLIntersectionRuneResolver()},
{IntersectionRuneType.LRCorner,new LRIntersectionRuneResolver()},

{IntersectionRuneType.TopTee,new TopTeeIntersectionRuneResolver()},
{IntersectionRuneType.LeftTee,new LeftTeeIntersectionRuneResolver()},
{IntersectionRuneType.RightTee,new RightTeeIntersectionRuneResolver()},
{IntersectionRuneType.BottomTee,new BottomTeeIntersectionRuneResolver()},


{IntersectionRuneType.Crosshair,new CrosshairIntersectionRuneResolver()},
// TODO: Add other resolvers
};

/// <summary>
/// Add a new line to the canvas starting at <paramref name="from"/>.
/// Use positive <paramref name="length"/> for Right and negative for Left
Expand Down Expand Up @@ -84,45 +100,144 @@ public void Draw (View view, Rect bounds)
}
}

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)
{
this.round = round;
this.doubleH = doubleH;
this.doubleV = doubleV;
this.doubleBoth = doubleBoth;
this.normal = normal;
}

public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
{
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);


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

if(doubleVertical)
{
return doubleV;
}

return useRounded ? round : normal;
}
}

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

}
}
private class URIntersectionRuneResolver : IntersectionRuneResolver
{

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

}
}
private class LLIntersectionRuneResolver : IntersectionRuneResolver
{

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

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

}
}

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

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

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

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

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

}
}

private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
{
if (!intersects.Any ())
return null;

var runeType = GetRuneTypeForIntersects (intersects);

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

// TODO: Remove these two once we have all of the below ported to IntersectionRuneResolvers
var useDouble = intersects.Any (i => i.Line.Style == BorderStyle.Double && i.Line.Length != 0);
var useRounded = intersects.Any (i => i.Line.Style == BorderStyle.Rounded && i.Line.Length != 0);

// TODO: maybe make these resolvers to for simplicity?
// or for dotted lines later on or that kind of thing?
switch (runeType) {
case IntersectionRuneType.None:
return null;
case IntersectionRuneType.Dot:
return (Rune)'.';
case IntersectionRuneType.ULCorner:
return useDouble ? driver.ULDCorner : useRounded ? driver.ULRCorner : driver.ULCorner;
case IntersectionRuneType.URCorner:
return useDouble ? driver.URDCorner : useRounded ? driver.URRCorner : driver.URCorner;
case IntersectionRuneType.LLCorner:
return useDouble ? driver.LLDCorner : useRounded ? driver.LLRCorner : driver.LLCorner;
case IntersectionRuneType.LRCorner:
return useDouble ? driver.LRDCorner : useRounded ? driver.LRRCorner : driver.LRCorner;
case IntersectionRuneType.TopTee:
return useDouble ? '╦' : driver.TopTee;
case IntersectionRuneType.BottomTee:
return useDouble ? '╩' : driver.BottomTee;
case IntersectionRuneType.RightTee:
return useDouble ? '╣' : driver.RightTee;
case IntersectionRuneType.LeftTee:
return useDouble ? '╠' : driver.LeftTee;
case IntersectionRuneType.Crosshair:
return useDouble ? '╬' : '┼';
case IntersectionRuneType.HLine:
return useDouble ? driver.HDLine : driver.HLine;
case IntersectionRuneType.VLine:
return useDouble ? driver.VDLine : driver.VLine;
default: throw new ArgumentOutOfRangeException (nameof (runeType));
default: throw new Exception ("Could not find resolver or switch case for " + nameof (runeType) + ":" + runeType);
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Xunit;
using Xunit.Abstractions;

namespace Terminal.Gui.Core {
namespace Terminal.Gui.CoreTests {
public class LineCanvasTests {

readonly ITestOutputHelper output;
Expand Down Expand Up @@ -218,6 +218,68 @@ public void TestLineCanvas_Window_Double ()
TestHelpers.AssertDriverContentsAre (looksLike, output);
}


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

// outer box
canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Double);
canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, thinStyle);
canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, BorderStyle.Double);
canvas.AddLine (new Point (0, 4), -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);

string looksLike =
@"
╒════╤═══╕
Comment thread
tig marked this conversation as resolved.
│ │ │
╞════╪═══╡
│ │ │
╘════╧═══╛
";
TestHelpers.AssertDriverContentsAre (looksLike, output);
}

[Theory, AutoInitShutdown]
[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 (5, 0), 4, Orientation.Vertical, BorderStyle.Double);
canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, thinStyle);

v.Redraw (v.Bounds);

string looksLike =
@"
╓────╥───╖
║ ║ ║
╟────╫───╢
║ ║ ║
╙────╨───╜

";
TestHelpers.AssertDriverContentsAre (looksLike, output);
}

private View GetCanvas (out LineCanvas canvas)
{
var v = new View {
Expand Down