diff --git a/Terminal.Gui/Core/Graphs/LineCanvas.cs b/Terminal.Gui/Core/Graphs/LineCanvas.cs index 04583518e..ab45c0264 100644 --- a/Terminal.Gui/Core/Graphs/LineCanvas.cs +++ b/Terminal.Gui/Core/Graphs/LineCanvas.cs @@ -14,6 +14,22 @@ namespace Terminal.Gui.Graphs { private List lines = new List (); + Dictionary runeResolvers = new Dictionary { + {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 + }; + /// /// Add a new line to the canvas starting at . /// Use positive 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); } - } diff --git a/UnitTests/LineCanvasTests.cs b/UnitTests/Core/LineCanvasTests.cs similarity index 77% rename from UnitTests/LineCanvasTests.cs rename to UnitTests/Core/LineCanvasTests.cs index 35c657361..7e988b0de 100644 --- a/UnitTests/LineCanvasTests.cs +++ b/UnitTests/Core/LineCanvasTests.cs @@ -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 {