diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs
index 7eb4e7f600..033731307f 100644
--- a/Terminal.Gui/View/ViewDrawing.cs
+++ b/Terminal.Gui/View/ViewDrawing.cs
@@ -61,27 +61,25 @@ public bool NeedsDisplay
public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
/// Draws the specified character in the specified viewport-relative column and row of the View.
+ ///
+ /// If the provided coordinates are outside the visible content area, this method does nothing.
+ ///
///
/// The top-left corner of the visible content area is ViewPort.Location.
///
/// Column (viewport-relative).
/// Row (viewport-relative).
- /// Ch.
- public void AddRune (int col, int row, Rune ch)
+ /// The Rune.
+ public void AddRune (int col, int row, Rune rune)
{
- if (row < 0 || col < 0)
- {
- return;
- }
-
- // BUGBUG: This should be Viewport.Size
- if (row > _frame.Height - 1 || col > _frame.Width - 1)
+ if (row < 0 || col < 0 || row >= Viewport.Height || col >= Viewport.Width)
{
+ // TODO: Change return type to bool so callers can determine success?
return;
}
Move (col, row);
- Driver.AddRune (ch);
+ Driver.AddRune (rune);
}
/// Clears with the normal background.
@@ -320,9 +318,14 @@ public virtual Attribute GetNormalColor ()
return Enabled ? cs.Normal : cs.Disabled;
}
- /// This moves the cursor to the specified view-relative column and row in the view.
+ /// Moves the drawing cursor to the specified view-relative column and row in the view.
///
+ ///
+ /// If the provided coordinates are outside the visible content area, this method does nothing.
+ ///
+ ///
/// The top-left corner of the visible content area is ViewPort.Location.
+ ///
///
/// Column (viewport-relative).
/// Row (viewport-relative).
@@ -333,9 +336,13 @@ public void Move (int col, int row)
return;
}
- Rectangle screen = ViewportToScreen (new (col, row, 0, 0));
+ if (col < 0 || row < 0 || col >= Viewport.Size.Width || row >= Viewport.Size.Height)
+ {
+ // TODO: Change return type to bool so callers can determine success?
+ return;
+ }
- // TODO: Clamp this to stay within the View's Viewport
+ Rectangle screen = ViewportToScreen (new (col, row, 0, 0));
Driver?.Move (screen.X, screen.Y);
}
diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs
index 38cffae9ac..0dde48c882 100644
--- a/UnitTests/View/DrawTests.cs
+++ b/UnitTests/View/DrawTests.cs
@@ -4,18 +4,70 @@
namespace Terminal.Gui.ViewTests;
-[Trait("Category","Output")]
-public class DrawTests
+[Trait ("Category", "Output")]
+public class DrawTests (ITestOutputHelper output)
{
- private readonly ITestOutputHelper _output;
- public DrawTests (ITestOutputHelper output) { _output = output; }
+ [Fact]
+ [SetupFakeDriver]
+ public void Move_Is_Constrained_To_Viewport ()
+ {
+ var view = new View
+ {
+ X = 1,
+ Y = 1,
+ Width = 3, Height = 3
+ };
+ view.Margin.Thickness = new Thickness (1);
+
+ // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
+
+ view.Move (0, 0);
+ Assert.Equal(new Point(2, 2), new Point (Application.Driver.Col, Application.Driver.Row));
+
+ view.Move (-1, -1);
+ Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row));
+
+ view.Move (1, 1);
+ Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row));
+ }
+
+ [Fact]
+ [SetupFakeDriver]
+ public void AddRune_Is_Constrained_To_Viewport ()
+ {
+ var view = new View
+ {
+ X = 1,
+ Y = 1,
+ Width = 3, Height = 3
+ };
+ view.Margin.Thickness = new Thickness (1);
+ View.Diagnostics = ViewDiagnosticFlags.Padding;
+ view.BeginInit();
+ view.EndInit();
+ view.Draw();
+
+ // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
+ Assert.Equal ((Rune)' ', Application.Driver.Contents [2, 2].Rune);
+
+ view.AddRune(0, 0, Rune.ReplacementChar);
+ Assert.Equal (Rune.ReplacementChar, Application.Driver.Contents [2, 2].Rune);
+
+ view.AddRune (-1, -1, Rune.ReplacementChar);
+ Assert.Equal ((Rune)'M', Application.Driver.Contents [1, 1].Rune);
+
+ view.AddRune (1, 1, Rune.ReplacementChar);
+ Assert.Equal ((Rune)'M', Application.Driver.Contents [3, 3].Rune);
+
+ View.Diagnostics = ViewDiagnosticFlags.Off;
+ }
[Theory]
[InlineData (0, 0, 1, 1)]
[InlineData (0, 0, 2, 2)]
[InlineData (-1, -1, 2, 2)]
[SetupFakeDriver]
- public void Clear_Viewport_Clears_Only_Bounds (int x, int y, int width, int height)
+ public void Clear_Clears_Only_Viewport (int x, int y, int width, int height)
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
@@ -32,28 +84,80 @@ public void Clear_Viewport_Clears_Only_Bounds (int x, int y, int width, int heig
superView.LayoutSubviews ();
superView.Draw ();
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+ ┌─┐
+ │X│
+ └─┘",
+ output);
+ Rectangle toClear = new (x, y, width, height);
+ view.Clear (toClear);
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+ ┌─┐
+ │ │
+ └─┘",
+ output);
+ // Now try to clear beyond Viewport (invalid)
+ superView.SetNeedsDisplay ();
+ superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
- _output);
+ output);
+ toClear = new (-width, -height, width, height);
+ view.Clear (toClear);
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+ ┌─┐
+ │X│
+ └─┘",
+ output);
- Rectangle boundsToClear = new (x, y, width, height);
- view.Clear (boundsToClear);
+ // Now try to clear beyond Viewport (valid)
+ superView.SetNeedsDisplay ();
+ superView.Draw ();
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+ ┌─┐
+ │X│
+ └─┘",
+ output);
+ toClear = new (-1, -1, width + 1, height + 1);
+ view.Clear (toClear);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
- _output);
+ output);
+ // Now clear too much size
+ superView.SetNeedsDisplay ();
+ superView.Draw ();
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+ ┌─┐
+ │X│
+ └─┘",
+ output);
+ toClear = new (0, 0, width * 2, height * 2);
+ view.Clear (toClear);
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+ ┌─┐
+ │ │
+ └─┘",
+ output);
}
+
[Fact]
[AutoInitShutdown]
- [Trait("Category","Unicode")]
+ [Trait ("Category", "Unicode")]
public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
{
const string us = "\U0000f900";
@@ -83,9 +187,9 @@ public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
│豈 │
└────────┘
""";
- TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
+ TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, output);
- TestHelpers.AssertDriverContentsAre (expectedOutput, _output);
+ TestHelpers.AssertDriverContentsAre (expectedOutput, output);
Attribute [] expectedColors =
{
@@ -115,7 +219,7 @@ Colors.ColorSchemes ["Base"].HotNormal
// TODO: Refactor this test to not depend on TextView etc... Make it as primitive as possible
[Fact]
[AutoInitShutdown]
- [Trait("Category","Unicode")]
+ [Trait ("Category", "Unicode")]
public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
{
var tv = new TextView
@@ -161,13 +265,13 @@ public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wi
└────────────────────────────┘
""";
- Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
+ Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, output);
Assert.Equal (new Rectangle (0, 0, 30, 10), pos);
}
[Fact]
[AutoInitShutdown]
- [Trait("Category","Output")]
+ [Trait ("Category", "Output")]
public void Colors_On_TextAlignment_Right_And_Bottom ()
{
var viewRight = new View
@@ -206,7 +310,7 @@ public void Colors_On_TextAlignment_Right_And_Bottom ()
s
t
""",
- _output
+ output
);
TestHelpers.AssertDriverAttributesAre (
@@ -227,14 +331,14 @@ Colors.ColorSchemes ["Base"].Normal
[Fact]
[SetupFakeDriver]
- public void Draw_Minimum_Full_Border_With_Empty_Bounds ()
+ public void Draw_Minimum_Full_Border_With_Empty_Viewport ()
{
var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single };
view.BeginInit ();
view.EndInit ();
view.SetRelativeLayout (Application.Driver.Viewport);
- Assert.Equal (new (0,0,2,2), view.Frame);
+ Assert.Equal (new (0, 0, 2, 2), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
@@ -245,7 +349,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Bounds ()
┌┐
└┘
""",
- _output
+ output
);
}
@@ -259,12 +363,12 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom ()
view.EndInit ();
view.SetRelativeLayout (Application.Driver.Viewport);
- Assert.Equal (new (0,0,2,1), view.Frame);
+ Assert.Equal (new (0, 0, 2, 1), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
- TestHelpers.AssertDriverContentsWithFrameAre (string.Empty, _output);
+ TestHelpers.AssertDriverContentsWithFrameAre (string.Empty, output);
}
[Fact]
@@ -277,7 +381,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left ()
view.EndInit ();
view.SetRelativeLayout (Application.Driver.Viewport);
- Assert.Equal (new (0,0,1,2), view.Frame);
+ Assert.Equal (new (0, 0, 1, 2), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
@@ -288,7 +392,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left ()
│
│
""",
- _output
+ output
);
}
@@ -302,7 +406,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right ()
view.EndInit ();
view.SetRelativeLayout (Application.Driver.Viewport);
- Assert.Equal (new (0,0,1,2), view.Frame);
+ Assert.Equal (new (0, 0, 1, 2), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
@@ -313,7 +417,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right ()
│
│
""",
- _output
+ output
);
}
@@ -328,7 +432,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top ()
view.EndInit ();
view.SetRelativeLayout (Application.Driver.Viewport);
- Assert.Equal (new (0,0,2,1), view.Frame);
+ Assert.Equal (new (0, 0, 2, 1), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
@@ -339,7 +443,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top ()
┌┐
""",
- _output
+ output
);
}
@@ -416,7 +520,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
3V
4i
""",
- _output
+ output
);
content.X = -1;
@@ -431,12 +535,12 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
V
i
""",
- _output
+ output
);
content.X = -2;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
content.X = 0;
content.Y = -1;
@@ -451,7 +555,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
4i
5e
""",
- _output
+ output
);
content.Y = -6;
@@ -466,7 +570,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
9
0
""",
- _output
+ output
);
content.Y = -19;
@@ -477,17 +581,17 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
9
""",
- _output
+ output
);
content.Y = -20;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre ("", output);
content.X = -2;
content.Y = 0;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre ("", output);
}
[Fact]
@@ -533,7 +637,7 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines ()
01234
subVi
""",
- _output
+ output
);
content.X = -1;
@@ -545,7 +649,7 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines ()
12345
ubVie
""",
- _output
+ output
);
content.Y = -1;
@@ -556,17 +660,17 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines ()
ubVie
""",
- _output
+ output
);
content.Y = -2;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre ("", output);
content.X = -20;
content.Y = 0;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre ("", output);
}
[Fact]
@@ -618,7 +722,7 @@ public void Draw_Negative_Viewport_Vertical ()
3V
4i
""",
- _output
+ output
);
content.X = -1;
@@ -633,12 +737,12 @@ public void Draw_Negative_Viewport_Vertical ()
V
i
""",
- _output
+ output
);
content.X = -2;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
content.X = 0;
content.Y = -1;
@@ -653,7 +757,7 @@ public void Draw_Negative_Viewport_Vertical ()
4i
5e
""",
- _output
+ output
);
content.Y = -6;
@@ -668,7 +772,7 @@ public void Draw_Negative_Viewport_Vertical ()
9
0
""",
- _output
+ output
);
content.Y = -19;
@@ -679,17 +783,17 @@ public void Draw_Negative_Viewport_Vertical ()
9
""",
- _output
+ output
);
content.Y = -20;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre ("", output);
content.X = -2;
content.Y = 0;
Application.Refresh ();
- TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+ TestHelpers.AssertDriverContentsWithFrameAre ("", output);
}
[Theory]
@@ -701,7 +805,7 @@ public void DrawHotString_NonBmp (string expected)
var view = new View { Width = 10, Height = 1 };
view.DrawHotString (expected, Attribute.Default, Attribute.Default);
- TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
}
// TODO: The tests below that use Label should use View instead.
@@ -736,9 +840,9 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two ()
│𝔹 │
└────────┘
""";
- TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
- TestHelpers.AssertDriverContentsAre (expected, _output);
+ TestHelpers.AssertDriverContentsAre (expected, output);
Attribute [] expectedColors =
{