diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs
index a36093906b..d49d4ae0b7 100644
--- a/Terminal.Gui/View/ViewDrawing.cs
+++ b/Terminal.Gui/View/ViewDrawing.cs
@@ -4,6 +4,28 @@
namespace Terminal.Gui {
public partial class View {
+ ///
+ /// Specifies the side to start when draw frame with
+ /// method.
+ ///
+ public enum Side {
+ ///
+ /// Start on left.
+ ///
+ Left,
+ ///
+ /// Start on top.
+ ///
+ Top,
+ ///
+ /// Start on right.
+ ///
+ Right,
+ ///
+ /// Start on bottom.
+ ///
+ Bottom
+ };
ColorScheme _colorScheme;
@@ -184,7 +206,7 @@ public void SetSubViewNeedsDisplay ()
/// This clears the Bounds used by this view.
///
///
- public void Clear () => Clear (ViewToScreen(Bounds));
+ public void Clear () => Clear (ViewToScreen (Bounds));
// BUGBUG: This version of the Clear API should be removed. We should have a tenet that says
// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
@@ -504,24 +526,257 @@ public virtual void OnDrawContentComplete (Rect contentArea)
}
///
- /// Draw a frame based on the passed bounds to the screen relative.
+ /// Draws a rectangular frame. The frame will be merged (auto-joined) with any other lines drawn by this View
+ /// if is true, otherwise will be rendered immediately.
///
- /// The bounds view relative.
+ /// The view relative location and size of the frame.
/// The line style.
- /// The color to use.
- public void DrawFrame (Rect bounds, LineStyle lineStyle, Attribute? attribute = null)
+ /// The colors to be used.
+ /// When drawing the frame, allow it to integrate (join) to other frames in other controls.
+ /// Or false to simply draw the rect exactly with no side effects.
+ public void DrawFrame (Rect rect, LineStyle lineStyle, Attribute? attribute = null, bool mergeWithLineCanvas = true)
{
- var vts = ViewToScreen (bounds);
- LineCanvas.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ LineCanvas lc;
+ if (mergeWithLineCanvas) {
+ lc = new LineCanvas ();
+ } else {
+ lc = LineCanvas;
+ }
+ var vts = ViewToScreen (rect);
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
Orientation.Horizontal, lineStyle, attribute);
- LineCanvas.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
Orientation.Vertical, lineStyle, attribute);
- LineCanvas.AddLine (new Point (vts.X, vts.Bottom - 1), vts.Width,
+ lc.AddLine (new Point (vts.Right - 1, vts.Bottom - 1), -vts.Width,
Orientation.Horizontal, lineStyle, attribute);
- LineCanvas.AddLine (new Point (vts.X, vts.Y), vts.Height,
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
Orientation.Vertical, lineStyle, attribute);
- OnRenderLineCanvas ();
+ if (mergeWithLineCanvas) {
+ LineCanvas.Merge (lc);
+ } else {
+ OnRenderLineCanvas ();
+ }
+ }
+
+ ///
+ /// Draws an incomplete frame. The frame will be merged (auto-joined) with any other lines drawn by this View
+ /// if is true, otherwise will be rendered immediately.
+ /// The frame is always drawn clockwise. For and the end position must
+ /// be greater or equal to the start and for and the end position must
+ /// be less or equal to the start.
+ ///
+ /// The start and side position screen relative.
+ /// The end and side position screen relative.
+ /// The view relative location and size of the frame.
+ /// The line style.
+ /// The colors to be used.
+ /// When drawing the frame, allow it to integrate (join) to other frames in other controls.
+ /// Or false to simply draw the rect exactly with no side effects.
+ public void DrawIncompleteFrame ((int start, Side side) startPos, (int end, Side side) endPos, Rect rect, LineStyle lineStyle, Attribute? attribute = null, bool mergeWithLineCanvas = true)
+ {
+ var vts = ViewToScreen (rect);
+ LineCanvas lc;
+ if (mergeWithLineCanvas) {
+ lc = new LineCanvas ();
+ } else {
+ lc = LineCanvas;
+ }
+ var start = startPos.start;
+ var end = endPos.end;
+ switch (startPos.side) {
+ case Side.Left:
+ if (start == vts.Y) {
+ lc.AddLine (new Point (vts.X, start), 1,
+ Orientation.Vertical, lineStyle, attribute);
+ } else {
+ if (end <= start && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.X, start), end - start - 1,
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ } else {
+ lc.AddLine (new Point (vts.X, start), vts.Y - start - 1,
+ Orientation.Vertical, lineStyle, attribute);
+ }
+ }
+ switch (endPos.side) {
+ case Side.Left:
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Bottom - 1), -vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ if (end <= vts.Bottom - 1 && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -(vts.Bottom - end),
+ Orientation.Vertical, lineStyle, attribute);
+ }
+ break;
+ case Side.Top:
+ lc.AddLine (new Point (vts.X, vts.Y), end,
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ case Side.Right:
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), end + 1,
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ case Side.Bottom:
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Bottom - 1), -end,
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ }
+ break;
+
+ case Side.Top:
+ if (start == vts.Width - 1) {
+ lc.AddLine (new Point (vts.X + start, vts.Y), -1,
+ Orientation.Horizontal, lineStyle, attribute);
+ } else if (end >= start && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.X + start, vts.Y), end - start + 1,
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ } else if (vts.Width - start > 0) {
+ lc.AddLine (new Point (vts.X + start, vts.Y), Math.Max (vts.Width - start, 0),
+ Orientation.Horizontal, lineStyle, attribute);
+ }
+ switch (endPos.side) {
+ case Side.Left:
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Bottom - 1), -vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -end,
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ case Side.Top:
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Bottom - 1), -vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ if (end >= 0 && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.X, vts.Y), end + 1,
+ Orientation.Horizontal, lineStyle, attribute);
+ }
+ break;
+ case Side.Right:
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), end,
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ case Side.Bottom:
+ lc.AddLine (new Point (vts.Right - 1, vts.Y), vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Right - 1, vts.Bottom - 1), -(vts.Width - end),
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ }
+ break;
+ case Side.Right:
+ if (start == vts.Bottom - 1) {
+ lc.AddLine (new Point (vts.Width - 1, start), -1,
+ Orientation.Vertical, lineStyle, attribute);
+ } else {
+ if (end >= start && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.Width - 1, start), end - start + 1,
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ } else {
+ lc.AddLine (new Point (vts.Width - 1, start), vts.Bottom - start,
+ Orientation.Vertical, lineStyle, attribute);
+ }
+ }
+ switch (endPos.side) {
+ case Side.Left:
+ lc.AddLine (new Point (vts.Width - 1, vts.Bottom - 1), -vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -(vts.Bottom - end),
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ case Side.Top:
+ lc.AddLine (new Point (vts.Width - 1, vts.Bottom - 1), -vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Y), end,
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ case Side.Right:
+ lc.AddLine (new Point (vts.Width - 1, vts.Bottom - 1), -vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ if (end >= 0 && end < vts.Bottom - 1 && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.Width - 1, vts.Y), end + 1,
+ Orientation.Vertical, lineStyle, attribute);
+ }
+ break;
+ case Side.Bottom:
+ lc.AddLine (new Point (vts.Width - 1, vts.Bottom - 1), -(vts.Width - end),
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ }
+ break;
+ case Side.Bottom:
+ if (start == vts.X) {
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), 1,
+ Orientation.Horizontal, lineStyle, attribute);
+ } else if (end <= start && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.X + start, vts.Bottom - 1), -(start - end + 1),
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ } else {
+ lc.AddLine (new Point (vts.X + start, vts.Bottom - 1), -(start + 1),
+ Orientation.Horizontal, lineStyle, attribute);
+ }
+ switch (endPos.side) {
+ case Side.Left:
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -(vts.Bottom - end),
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ case Side.Top:
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Y), end + 1,
+ Orientation.Horizontal, lineStyle, attribute);
+ break;
+ case Side.Right:
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Width - 1, vts.Y), end,
+ Orientation.Vertical, lineStyle, attribute);
+ break;
+ case Side.Bottom:
+ lc.AddLine (new Point (vts.X, vts.Bottom - 1), -vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ lc.AddLine (new Point (vts.X, vts.Y), vts.Width,
+ Orientation.Horizontal, lineStyle, attribute);
+ lc.AddLine (new Point (vts.Width - 1, vts.Y), vts.Height,
+ Orientation.Vertical, lineStyle, attribute);
+ if (vts.Width - end > 0 && startPos.side == endPos.side) {
+ lc.AddLine (new Point (vts.Width - 1, vts.Bottom - 1), -(vts.Width - end),
+ Orientation.Horizontal, lineStyle, attribute);
+ }
+ break;
+ }
+ break;
+ }
+
+ if (mergeWithLineCanvas) {
+ LineCanvas.Merge (lc);
+ } else {
+ OnRenderLineCanvas ();
+ }
}
}
}
\ No newline at end of file
diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs
index 8027710b43..d5fa06c771 100644
--- a/UnitTests/View/DrawTests.cs
+++ b/UnitTests/View/DrawTests.cs
@@ -2,6 +2,7 @@
using System;
using Xunit;
using Xunit.Abstractions;
+using static Terminal.Gui.View;
namespace Terminal.Gui.ViewsTests {
public class DrawTests {
@@ -336,11 +337,11 @@ public void Draw_Negative_Bounds_Vertical ()
}
[Fact, AutoInitShutdown]
- public void DrawFrame_Test ()
+ public void DrawFrame_Merge ()
{
var label = new View () { X = Pos.Center (), Y = Pos.Center (), Text = "test", AutoSize = true };
var view = new View () { Width = 10, Height = 5 };
- view.DrawContentComplete += (s, e) => view.DrawFrame (view.Bounds, LineStyle.Single);
+ view.DrawContent += (s, e) => view.DrawFrame (view.Bounds, LineStyle.Single);
view.Add (label);
Application.Top.Add (view);
Application.Begin (Application.Top);
@@ -352,5 +353,316 @@ public void DrawFrame_Test ()
│ │
└────────┘", output);
}
+
+ [Fact, AutoInitShutdown]
+ public void DrawFrame_Without_Merge ()
+ {
+ var label = new View () { X = Pos.Center (), Y = Pos.Center (), Text = "test", AutoSize = true };
+ var view = new View () { Width = 10, Height = 5 };
+ view.DrawContentComplete += (s, e) => {
+ view.DrawFrame (view.Bounds, LineStyle.Single, null, false);
+ view.OnRenderLineCanvas ();
+ };
+ view.Add (label);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│ │
+│ test │
+│ │
+└────────┘", output);
+ }
+
+ [Theory, AutoInitShutdown]
+ [InlineData (1, Side.Left, 5, Side.Left, @"
+┌────────┐
+│ │
+ test │
+ │
+─────────┘")]
+ [InlineData (1, Side.Left, 4, Side.Left, @"
+┌────────┐
+│ │
+ test │
+ │
+└────────┘")]
+ [InlineData (0, Side.Left, 3, Side.Left, @"
+┌────────┐
+ │
+ test │
+│ │
+└────────┘")]
+ [InlineData (5, Side.Top, -1, Side.Top, @"
+│ ────┐
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (5, Side.Top, 0, Side.Top, @"
+┌ ────┐
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (6, Side.Top, 1, Side.Top, @"
+┌─ ───┐
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (7, Side.Top, 2, Side.Top, @"
+┌── ──┐
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (8, Side.Top, 3, Side.Top, @"
+┌─── ─┐
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (9, Side.Top, 4, Side.Top, @"
+┌──── ┐
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (10, Side.Top, 5, Side.Top, @"
+┌───── │
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (3, Side.Right, -1, Side.Right, @"
+┌─────────
+│
+│ test
+│ │
+└────────┘")]
+ [InlineData (3, Side.Right, 0, Side.Right, @"
+┌────────┐
+│
+│ test
+│ │
+└────────┘")]
+ [InlineData (4, Side.Right, 1, Side.Right, @"
+┌────────┐
+│ │
+│ test
+│
+└────────┘")]
+ [InlineData (4, Side.Bottom, 10, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+└──── │")]
+ [InlineData (4, Side.Bottom, 9, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+└──── ┘")]
+ [InlineData (3, Side.Bottom, 8, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+└─── ─┘")]
+ [InlineData (2, Side.Bottom, 7, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+└── ──┘")]
+ [InlineData (1, Side.Bottom, 6, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+└─ ───┘")]
+ [InlineData (0, Side.Bottom, 5, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+└ ────┘")]
+ [InlineData (-1, Side.Bottom, 5, Side.Bottom, @"
+┌────────┐
+│ │
+│ test │
+│ │
+│ ────┘")]
+ public void DrawIncompleteFrame_All_Sides (int start, Side startSide, int end, Side endSide, string expected)
+ {
+ View view = GetViewsForDrawFrameTests (start, startSide, end, endSide);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+ }
+
+ private static View GetViewsForDrawFrameTests (int start, Side startSide, int end, Side endSide)
+ {
+ var label = new View () { X = Pos.Center (), Y = Pos.Center (), Text = "test", AutoSize = true };
+ var view = new View () { Width = 10, Height = 5 };
+ view.DrawContent += (s, e) =>
+ view.DrawIncompleteFrame (new (start, startSide), new (end, endSide), view.Bounds, LineStyle.Single);
+ view.Add (label);
+ return view;
+ }
+
+ [Theory, AutoInitShutdown]
+ [InlineData (4, Side.Left, 4, Side.Right, @"
+┌────────┐
+│ │
+│ test │
+│ │
+│ │")]
+ [InlineData (0, Side.Top, 0, Side.Bottom, @"
+─────────┐
+ │
+ test │
+ │
+─────────┘")]
+ [InlineData (0, Side.Right, 0, Side.Left, @"
+│ │
+│ │
+│ test │
+│ │
+└────────┘")]
+ [InlineData (9, Side.Bottom, 9, Side.Top, @"
+┌─────────
+│
+│ test
+│
+└─────────")]
+ public void DrawIncompleteFrame_Three_Full_Sides (int start, Side startSide, int end, Side endSide, string expected)
+ {
+ View view = GetViewsForDrawFrameTests (start, startSide, end, endSide);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+ }
+
+ [Theory, AutoInitShutdown]
+ [InlineData (4, Side.Left, 10, Side.Top, @"
+┌─────────
+│
+│ test
+│
+│ ")]
+ [InlineData (0, Side.Top, 5, Side.Right, @"
+─────────┐
+ │
+ test │
+ │
+ │")]
+ [InlineData (0, Side.Right, 0, Side.Bottom, @"
+ │
+ │
+ test │
+ │
+─────────┘")]
+ [InlineData (9, Side.Bottom, 0, Side.Left, @"
+│
+│
+│ test
+│
+└─────────")]
+ public void DrawIncompleteFrame_Two_Full_Sides (int start, Side startSide, int end, Side endSide, string expected)
+ {
+ View view = GetViewsForDrawFrameTests (start, startSide, end, endSide);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+ }
+
+ [Theory, AutoInitShutdown]
+ [InlineData (4, Side.Left, 0, Side.Left, @"
+│
+│
+│ test
+│
+│ ")]
+ [InlineData (0, Side.Top, 9, Side.Top, @"
+──────────
+
+ test ")]
+ [InlineData (0, Side.Right, 4, Side.Right, @"
+ │
+ │
+ test │
+ │
+ │")]
+ [InlineData (9, Side.Bottom, 0, Side.Bottom, @"
+ test
+
+──────────")]
+ public void DrawIncompleteFrame_One_Full_Sides (int start, Side startSide, int end, Side endSide, string expected)
+ {
+ View view = GetViewsForDrawFrameTests (start, startSide, end, endSide);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+ }
+
+ [Theory, AutoInitShutdown]
+ [InlineData (0, Side.Bottom, 0, Side.Top, @"
+┌
+│
+│ test
+│
+└ ")]
+ [InlineData (0, Side.Left, 0, Side.Right, @"
+┌────────┐
+
+ test ")]
+ [InlineData (9, Side.Top, 9, Side.Bottom, @"
+ ┐
+ │
+ test │
+ │
+ ┘")]
+ [InlineData (4, Side.Right, 4, Side.Left, @"
+ test
+
+└────────┘")]
+ public void DrawIncompleteFrame_One_Full_Sides_With_Corner (int start, Side startSide, int end, Side endSide, string expected)
+ {
+ View view = GetViewsForDrawFrameTests (start, startSide, end, endSide);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+ }
+
+ [Theory, AutoInitShutdown]
+ [InlineData (2, Side.Left, 2, Side.Left, @"
+│ test")]
+ [InlineData (3, Side.Top, 6, Side.Top, @"
+ ────
+
+ test")]
+ [InlineData (2, Side.Right, 2, Side.Right, @"
+ test │")]
+ [InlineData (6, Side.Bottom, 3, Side.Bottom, @"
+ test
+
+ ────")]
+ public void DrawIncompleteFrame_One_Part_Sides (int start, Side startSide, int end, Side endSide, string expected)
+ {
+ View view = GetViewsForDrawFrameTests (start, startSide, end, endSide);
+ Application.Top.Add (view);
+ Application.Begin (Application.Top);
+
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+ }
}
}