mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
Merge pull request #2339 from tznind/line-canvas-style-mixing
LineCanvas support for mixing double and single lines
This commit is contained in:
@@ -14,6 +14,22 @@ namespace Terminal.Gui.Graphs {
|
||||
|
||||
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
|
||||
@@ -84,45 +100,144 @@ namespace Terminal.Gui.Graphs {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Terminal.Gui.Core {
|
||||
namespace Terminal.Gui.CoreTests {
|
||||
public class LineCanvasTests {
|
||||
|
||||
readonly ITestOutputHelper output;
|
||||
@@ -218,6 +218,68 @@ namespace Terminal.Gui.Core {
|
||||
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 =
|
||||
@"
|
||||
╒════╤═══╕
|
||||
│ │ │
|
||||
╞════╪═══╡
|
||||
│ │ │
|
||||
╘════╧═══╛
|
||||
";
|
||||
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 {
|
||||
Reference in New Issue
Block a user