From 8f53c96c9ffedf282005c5d3efd6befabf0bc1cf Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 6 Jan 2023 10:06:14 +0000 Subject: [PATCH] Add StraightLineCanvas prototype class --- .../Core/Graphs/StraightLineCanvas.cs | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 Terminal.Gui/Core/Graphs/StraightLineCanvas.cs diff --git a/Terminal.Gui/Core/Graphs/StraightLineCanvas.cs b/Terminal.Gui/Core/Graphs/StraightLineCanvas.cs new file mode 100644 index 000000000..ba67537eb --- /dev/null +++ b/Terminal.Gui/Core/Graphs/StraightLineCanvas.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Terminal.Gui.Graphs { + + + /// + /// Facilitates box drawing and line intersection detection + /// and rendering. + /// + public class StraightLineCanvas { + + private List lines = new List (); + private ConsoleDriver driver; + + public StraightLineCanvas (ConsoleDriver driver) + { + this.driver = driver; + } + + /// + /// Add a new line to the canvas starting at . + /// Use positive for Right and negative for Left + /// when is . + /// Use positive for Down and negative for Up + /// when is . + /// + /// Starting point. + /// Length of line. 0 for a dot. + /// Positive for Down/Right. Negative for Up/Left. + /// Direction of the line. + public void AddLine (Point from, int length, Orientation orientation, BorderStyle style) + { + lines.Add (new StraightLine (from, length, orientation, style)); + } + /// + /// Evaluate all currently defined lines that lie within + /// and generate a 'bitmap' that + /// shows what characters (if any) should be rendered at each + /// point so that all lines connect up correctly with appropriate + /// intersection symbols. + /// + /// + /// + /// Map as 2D array where first index is rows and second is column + public Rune? [,] GenerateImage (Rect inArea) + { + Rune? [,] canvas = new Rune? [inArea.Height, inArea.Width]; + + // walk through each pixel of the bitmap + for (int y = 0; y < inArea.Height; y++) { + for (int x = 0; x < inArea.Width; x++) { + + var intersects = lines + .Select (l => l.Intersects (x, y)) + .Where(i=>i != null) + .ToArray(); + + // TODO: use Driver and LineStyle to map + canvas [x, y] = GetRuneForIntersects (intersects); + + } + } + + return canvas; + } + + private Rune? GetRuneForIntersects (IntersectionDefinition[] intersects) + { + if (!intersects.Any ()) + return null; + + // TODO: merge these intersection types to give correct rune + + return '.'; + } + + class IntersectionDefinition { + /// + /// The point at which the intersection happens + /// + public Point Point { get; } + + /// + /// Defines how position relates + /// to . + /// + public IntersectionType Type { get; } + + /// + /// The line that intersects + /// + public StraightLine Line { get; } + + public IntersectionDefinition (Point point, IntersectionType type, StraightLine line) + { + Point = point; + Type = type; + Line = line; + } + } + + /// + /// The type of Rune that we will use before considering + /// double width, curved borders etc + /// + enum IntersectionRuneType + { + None, + Dot, + ULCorner, + URCorner, + LLCorner, + LRCorner, + UpperT, + LowerT, + RightT, + LeftT, + Crosshair, + } + + enum IntersectionType { + /// + /// There is no intersection + /// + None, + + /// + /// A line passes directly over this point traveling along + /// the horizontal axis + /// + PassOverHorizontal, + + /// + /// A line passes directly over this point traveling along + /// the vertical axis + /// + PassOverVertical, + + /// + /// A line starts at this point and is traveling up + /// + StartUp, + + /// + /// A line starts at this point and is traveling right + /// + StartRight, + + /// + /// A line starts at this point and is traveling down + /// + StartDown, + + /// + /// A line starts at this point and is traveling left + /// + StartLeft, + + /// + /// A line exists at this point who has 0 length + /// + Dot + } + + class StraightLine { + public Point Start { get; } + public int Length { get; } + public Orientation Orientation { get; } + public BorderStyle Style { get; } + + public StraightLine (Point start, int length, Orientation orientation, BorderStyle style) + { + this.Start = start; + this.Length = length; + this.Orientation = orientation; + this.Style = style; + } + + internal IntersectionDefinition Intersects (int x, int y) + { + if (IsDot ()) { + if (StartsAt (x, y)) { + return new IntersectionDefinition (Start, IntersectionType.Dot, this); + } else { + return null; + } + } + + switch (Orientation) { + case Orientation.Horizontal: return IntersectsHorizontally (x, y); + case Orientation.Vertical: return IntersectsVertically (x, y); + default: throw new ArgumentOutOfRangeException (nameof (Orientation)); + } + + } + + private IntersectionDefinition IntersectsHorizontally (int x, int y) + { + if (Start.Y != y) { + return null; + } else { + if (StartsAt (x, y)) { + + return new IntersectionDefinition ( + Start, + Length < 0 ? IntersectionType.StartLeft : IntersectionType.StartRight, + this + ); + + } + + if (EndsAt (x, y)) { + + return new IntersectionDefinition ( + Start, + Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft, + this + ); + + } else { + var xmin = Math.Min (Start.X, Start.X + Length); + var xmax = Math.Max (Start.X, Start.X + Length); + + if (xmin < x && xmax > x) { + return new IntersectionDefinition ( + new Point (x, y), + IntersectionType.PassOverHorizontal, + this + ); + } + } + + return null; + } + } + + private IntersectionDefinition IntersectsVertically (int x, int y) + { + if (Start.X != x) { + return null; + } else { + if (StartsAt (x, y)) { + + return new IntersectionDefinition ( + Start, + Length < 0 ? IntersectionType.StartUp : IntersectionType.StartDown, + this + ); + + } + + if (EndsAt (x, y)) { + + return new IntersectionDefinition ( + Start, + Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp, + this + ); + + } else { + var ymin = Math.Min (Start.Y, Start.Y + Length); + var ymax = Math.Max (Start.Y, Start.Y + Length); + + if (ymin < y && ymax > y) { + return new IntersectionDefinition ( + new Point (x, y), + IntersectionType.PassOverVertical, + this + ); + } + } + + return null; + } + } + + private bool EndsAt (int x, int y) + { + if (Orientation == Orientation.Horizontal) { + return Start.X + Length == x && Start.Y == y; + } + + return Start.X == x && Start.Y + Length == y; + } + + private bool StartsAt (int x, int y) + { + return Start.X == x && Start.Y == y; + } + + private bool IsDot () + { + return Length == 0; + } + } + } +}