|
@@ -1,62 +1,9 @@
|
|
|
#nullable enable
|
|
|
namespace Terminal.Gui;
|
|
|
|
|
|
-/// <summary>Facilitates box drawing and line intersection detection and rendering. Does not support diagonal lines.</summary>
|
|
|
+/// <summary>Facilitates box drawing and line intersection detection and rendering. Does not support diagonal lines.</summary>
|
|
|
public class LineCanvas : IDisposable
|
|
|
{
|
|
|
- /// <summary>
|
|
|
- /// Optional <see cref="FillPair"/> which when present overrides the <see cref="StraightLine.Attribute"/>
|
|
|
- /// (colors) of lines in the canvas. This can be used e.g. to apply a global <see cref="GradientFill"/>
|
|
|
- /// across all lines.
|
|
|
- /// </summary>
|
|
|
- public FillPair? Fill { get; set; }
|
|
|
-
|
|
|
- private readonly List<StraightLine> _lines = [];
|
|
|
-
|
|
|
- private readonly Dictionary<IntersectionRuneType, IntersectionRuneResolver> _runeResolvers = new ()
|
|
|
- {
|
|
|
- {
|
|
|
- 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.Cross,
|
|
|
- new CrossIntersectionRuneResolver ()
|
|
|
- }
|
|
|
-
|
|
|
- // TODO: Add other resolvers
|
|
|
- };
|
|
|
-
|
|
|
- private Rectangle _cachedViewport;
|
|
|
-
|
|
|
/// <summary>Creates a new instance.</summary>
|
|
|
public LineCanvas ()
|
|
|
{
|
|
@@ -66,54 +13,62 @@ public class LineCanvas : IDisposable
|
|
|
Applied += ConfigurationManager_Applied;
|
|
|
}
|
|
|
|
|
|
+ private readonly List<StraightLine> _lines = [];
|
|
|
+
|
|
|
/// <summary>Creates a new instance with the given <paramref name="lines"/>.</summary>
|
|
|
/// <param name="lines">Initial lines for the canvas.</param>
|
|
|
public LineCanvas (IEnumerable<StraightLine> lines) : this () { _lines = lines.ToList (); }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Optional <see cref="FillPair"/> which when present overrides the <see cref="StraightLine.Attribute"/>
|
|
|
+ /// (colors) of lines in the canvas. This can be used e.g. to apply a global <see cref="GradientFill"/>
|
|
|
+ /// across all lines.
|
|
|
+ /// </summary>
|
|
|
+ public FillPair? Fill { get; set; }
|
|
|
+
|
|
|
+ private Rectangle _cachedBounds;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
|
|
|
- /// furthest left/top and Size is defined by the line that extends the furthest right/bottom.
|
|
|
+ /// the furthest left/top and Size is defined by the line that extends the furthest right/bottom.
|
|
|
/// </summary>
|
|
|
- public Rectangle Viewport
|
|
|
+ public Rectangle Bounds
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
- if (_cachedViewport.IsEmpty)
|
|
|
+ if (_cachedBounds.IsEmpty)
|
|
|
{
|
|
|
if (_lines.Count == 0)
|
|
|
{
|
|
|
- return _cachedViewport;
|
|
|
+ return _cachedBounds;
|
|
|
}
|
|
|
|
|
|
- Rectangle viewport = _lines [0].Viewport;
|
|
|
+ Rectangle bounds = _lines [0].Bounds;
|
|
|
|
|
|
for (var i = 1; i < _lines.Count; i++)
|
|
|
{
|
|
|
- viewport = Rectangle.Union (viewport, _lines [i].Viewport);
|
|
|
+ bounds = Rectangle.Union (bounds, _lines [i].Bounds);
|
|
|
}
|
|
|
|
|
|
- if (viewport is { Width: 0 } or { Height: 0 })
|
|
|
+ if (bounds is { Width: 0 } or { Height: 0 })
|
|
|
{
|
|
|
- viewport = viewport with
|
|
|
+ bounds = bounds with
|
|
|
{
|
|
|
- Width = Math.Clamp (viewport.Width, 1, short.MaxValue),
|
|
|
- Height = Math.Clamp (viewport.Height, 1, short.MaxValue)
|
|
|
+ Width = Math.Clamp (bounds.Width, 1, short.MaxValue),
|
|
|
+ Height = Math.Clamp (bounds.Height, 1, short.MaxValue)
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- _cachedViewport = viewport;
|
|
|
+ _cachedBounds = bounds;
|
|
|
}
|
|
|
|
|
|
- return _cachedViewport;
|
|
|
+ return _cachedBounds;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>Gets the lines in the canvas.</summary>
|
|
|
public IReadOnlyCollection<StraightLine> Lines => _lines.AsReadOnly ();
|
|
|
|
|
|
- /// <inheritdoc/>
|
|
|
- public void Dispose () { Applied -= ConfigurationManager_Applied; }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// <para>Adds a new <paramref name="length"/> long line to the canvas starting at <paramref name="start"/>.</para>
|
|
|
/// <para>
|
|
@@ -141,7 +96,7 @@ public class LineCanvas : IDisposable
|
|
|
Attribute? attribute = null
|
|
|
)
|
|
|
{
|
|
|
- _cachedViewport = Rectangle.Empty;
|
|
|
+ _cachedBounds = Rectangle.Empty;
|
|
|
_lines.Add (new (start, length, orientation, style, attribute));
|
|
|
}
|
|
|
|
|
@@ -149,49 +104,67 @@ public class LineCanvas : IDisposable
|
|
|
/// <param name="line"></param>
|
|
|
public void AddLine (StraightLine line)
|
|
|
{
|
|
|
- _cachedViewport = Rectangle.Empty;
|
|
|
+ _cachedBounds = Rectangle.Empty;
|
|
|
_lines.Add (line);
|
|
|
}
|
|
|
|
|
|
- /// <summary>Clears all lines from the LineCanvas.</summary>
|
|
|
- public void Clear ()
|
|
|
- {
|
|
|
- _cachedViewport = Rectangle.Empty;
|
|
|
- _lines.Clear ();
|
|
|
- _exclusionRegion = null;
|
|
|
- }
|
|
|
-
|
|
|
private Region? _exclusionRegion;
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets the list of locations that will be excluded from <see cref="GetCellMap"/> and <see cref="GetMap()"/>.
|
|
|
+ /// Causes the provided region to be excluded from <see cref="GetCellMap"/> and <see cref="GetMap()"/>.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// Each call to this method will add to the exclusion region. To clear the exclusion region, call
|
|
|
+ /// <see cref="ClearCache"/>.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
public void Exclude (Region region)
|
|
|
{
|
|
|
- _exclusionRegion ??= new Region ();
|
|
|
+ _exclusionRegion ??= new ();
|
|
|
_exclusionRegion.Union (region);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Clears any cached states from the canvas Call this method if you make changes to lines that have already been
|
|
|
+ /// Clears the exclusion region. After calling this method, <see cref="GetCellMap"/> and <see cref="GetMap()"/> will
|
|
|
+ /// return all points in the canvas.
|
|
|
+ /// </summary>
|
|
|
+ public void ClearExclusions () { _exclusionRegion = null; }
|
|
|
+
|
|
|
+ /// <summary>Clears all lines from the LineCanvas.</summary>
|
|
|
+ public void Clear ()
|
|
|
+ {
|
|
|
+ _cachedBounds = Rectangle.Empty;
|
|
|
+ _lines.Clear ();
|
|
|
+ ClearExclusions ();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Clears any cached states from the canvas. Call this method if you make changes to lines that have already been
|
|
|
/// added.
|
|
|
/// </summary>
|
|
|
- public void ClearCache () { _cachedViewport = Rectangle.Empty; }
|
|
|
+ public void ClearCache () { _cachedBounds = Rectangle.Empty; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
|
|
|
/// locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
|
|
|
/// intersection symbols.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// Only the points within the <see cref="Bounds"/> of the canvas that are not in the exclusion region will be
|
|
|
+ /// returned. To exclude points from the map, use <see cref="Exclude"/>.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
/// <returns>A map of all the points within the canvas.</returns>
|
|
|
public Dictionary<Point, Cell?> GetCellMap ()
|
|
|
{
|
|
|
Dictionary<Point, Cell?> map = new ();
|
|
|
|
|
|
// walk through each pixel of the bitmap
|
|
|
- for (int y = Viewport.Y; y < Viewport.Y + Viewport.Height; y++)
|
|
|
+ for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++)
|
|
|
{
|
|
|
- for (int x = Viewport.X; x < Viewport.X + Viewport.Width; x++)
|
|
|
+ for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++)
|
|
|
{
|
|
|
IntersectionDefinition? [] intersects = _lines
|
|
|
.Select (l => l.Intersects (x, y))
|
|
@@ -217,6 +190,12 @@ public class LineCanvas : IDisposable
|
|
|
/// locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
|
|
|
/// intersection symbols.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// Only the points within the <paramref name="inArea"/> of the canvas that are not in the exclusion region will be
|
|
|
+ /// returned. To exclude points from the map, use <see cref="Exclude"/>.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
/// <param name="inArea">A rectangle to constrain the search by.</param>
|
|
|
/// <returns>A map of the points within the canvas that intersect with <paramref name="inArea"/>.</returns>
|
|
|
public Dictionary<Point, Rune> GetMap (Rectangle inArea)
|
|
@@ -250,8 +229,14 @@ public class LineCanvas : IDisposable
|
|
|
/// locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
|
|
|
/// intersection symbols.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// Only the points within the <see cref="Bounds"/> of the canvas that are not in the exclusion region will be
|
|
|
+ /// returned. To exclude points from the map, use <see cref="Exclude"/>.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
/// <returns>A map of all the points within the canvas.</returns>
|
|
|
- public Dictionary<Point, Rune> GetMap () { return GetMap (Viewport); }
|
|
|
+ public Dictionary<Point, Rune> GetMap () { return GetMap (Bounds); }
|
|
|
|
|
|
/// <summary>Merges one line canvas into this one.</summary>
|
|
|
/// <param name="lineCanvas"></param>
|
|
@@ -285,13 +270,13 @@ public class LineCanvas : IDisposable
|
|
|
|
|
|
/// <summary>
|
|
|
/// Returns the contents of the line canvas rendered to a string. The string will include all columns and rows,
|
|
|
- /// even if <see cref="Viewport"/> has negative coordinates. For example, if the canvas contains a single line that
|
|
|
+ /// even if <see cref="Bounds"/> has negative coordinates. For example, if the canvas contains a single line that
|
|
|
/// starts at (-1,-1) with a length of 2, the rendered string will have a length of 2.
|
|
|
/// </summary>
|
|
|
/// <returns>The canvas rendered to a string.</returns>
|
|
|
public override string ToString ()
|
|
|
{
|
|
|
- if (Viewport.IsEmpty)
|
|
|
+ if (Bounds.IsEmpty)
|
|
|
{
|
|
|
return string.Empty;
|
|
|
}
|
|
@@ -300,13 +285,13 @@ public class LineCanvas : IDisposable
|
|
|
Dictionary<Point, Rune> runeMap = GetMap ();
|
|
|
|
|
|
// Create the rune canvas
|
|
|
- Rune [,] canvas = new Rune [Viewport.Height, Viewport.Width];
|
|
|
+ Rune [,] canvas = new Rune [Bounds.Height, Bounds.Width];
|
|
|
|
|
|
// Copy the rune map to the canvas, adjusting for any negative coordinates
|
|
|
foreach (KeyValuePair<Point, Rune> kvp in runeMap)
|
|
|
{
|
|
|
- int x = kvp.Key.X - Viewport.X;
|
|
|
- int y = kvp.Key.Y - Viewport.Y;
|
|
|
+ int x = kvp.Key.X - Bounds.X;
|
|
|
+ int y = kvp.Key.Y - Bounds.Y;
|
|
|
canvas [y, x] = kvp.Value;
|
|
|
}
|
|
|
|
|
@@ -330,7 +315,10 @@ public class LineCanvas : IDisposable
|
|
|
return sb.ToString ();
|
|
|
}
|
|
|
|
|
|
- private bool All (IntersectionDefinition? [] intersects, Orientation orientation) { return intersects.All (i => i!.Line.Orientation == orientation); }
|
|
|
+ private static bool All (IntersectionDefinition? [] intersects, Orientation orientation)
|
|
|
+ {
|
|
|
+ return intersects.All (i => i!.Line.Orientation == orientation);
|
|
|
+ }
|
|
|
|
|
|
private void ConfigurationManager_Applied (object? sender, ConfigurationManagerEventArgs e)
|
|
|
{
|
|
@@ -347,13 +335,55 @@ public class LineCanvas : IDisposable
|
|
|
/// <param name="intersects"></param>
|
|
|
/// <param name="types"></param>
|
|
|
/// <returns></returns>
|
|
|
- private bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types) { return intersects.SetEquals (types); }
|
|
|
+ private static bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types) { return intersects.SetEquals (types); }
|
|
|
|
|
|
private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects)
|
|
|
{
|
|
|
- return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : intersects [0]!.Line.Attribute;
|
|
|
+ return Fill?.GetAttribute (intersects [0]!.Point) ?? intersects [0]!.Line.Attribute;
|
|
|
}
|
|
|
|
|
|
+ private readonly Dictionary<IntersectionRuneType, IntersectionRuneResolver> _runeResolvers = new ()
|
|
|
+ {
|
|
|
+ {
|
|
|
+ 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.Cross,
|
|
|
+ new CrossIntersectionRuneResolver ()
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: Add other resolvers
|
|
|
+ };
|
|
|
+
|
|
|
private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
|
|
|
{
|
|
|
if (!intersects.Any ())
|
|
@@ -695,7 +725,7 @@ public class LineCanvas : IDisposable
|
|
|
internal Rune _thickBoth;
|
|
|
internal Rune _thickH;
|
|
|
internal Rune _thickV;
|
|
|
- public IntersectionRuneResolver () { SetGlyphs (); }
|
|
|
+ protected IntersectionRuneResolver () { SetGlyphs (); }
|
|
|
|
|
|
public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
|
|
|
{
|
|
@@ -871,4 +901,11 @@ public class LineCanvas : IDisposable
|
|
|
_normal = Glyphs.URCorner;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public void Dispose ()
|
|
|
+ {
|
|
|
+ Applied -= ConfigurationManager_Applied;
|
|
|
+ GC.SuppressFinalize (this);
|
|
|
+ }
|
|
|
}
|