Adds ViewportSettings.Transparent (#3886)

This commit is contained in:
Tig
2025-03-01 15:22:23 -07:00
committed by GitHub
parent cae1d6c47e
commit 98f70b2632
41 changed files with 5589 additions and 3992 deletions

View File

@@ -0,0 +1,411 @@
#nullable enable
using Moq;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class ClearViewportTests (ITestOutputHelper _output)
{
public class TestableView : View
{
public bool TestOnClearingViewport () { return OnClearingViewport (); }
public int OnClearingViewportCalled { get; set; } = 0;
public bool CancelOnClearingViewport { get; set; }
protected override bool OnClearingViewport ()
{
OnClearingViewportCalled++;
return CancelOnClearingViewport;
}
public int OnClearedViewportCalled { get; set; } = 0;
protected override void OnClearedViewport ()
{
OnClearedViewportCalled++;
}
}
[Fact]
public void DoClearViewport_ViewportIsTransparent_DoesNotClear ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
view.Object.ViewportSettings = ViewportSettings.Transparent;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (0, view.Object.OnClearingViewportCalled);
Assert.Equal (0, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_OnClearingViewportReturnsTrue_DoesNotClear ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
view.Object.CancelOnClearingViewport = true;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (0, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_ClearingViewportEventCancelled_DoesNotClear ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
view.Object.ClearingViewport += (sender, e) => e.Cancel = true;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (0, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_ClearsViewport ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (1, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_RaisesClearingViewportEvent ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
var eventRaised = false;
view.Object.ClearingViewport += (sender, e) => eventRaised = true;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.True (eventRaised);
}
[Fact]
[SetupFakeDriver]
public void Clear_ClearsEntireViewport ()
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
// On Draw exit the view is excluded from the clip, so this will do nothing.
view.ClearViewport ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
View.SetClipToScreen ();
view.ClearViewport ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[SetupFakeDriver]
public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly ()
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single,
ViewportSettings = ViewportSettings.ClearContentOnly
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
View.SetClipToScreen ();
view.ClearViewport ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[AutoInitShutdown]
public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
{
var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
view.DrawingContent += (s, e) =>
{
Region savedClip = view.AddViewportToClip ();
for (var row = 0; row < view.Viewport.Height; row++)
{
Application.Driver?.Move (1, row + 1);
for (var col = 0; col < view.Viewport.Width; col++)
{
Application.Driver?.AddStr ($"{col}");
}
}
View.SetClip (savedClip);
e.Cancel = true;
};
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
var expected = @"
┌──────────────────┐
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
└──────────────────┘
"
;
Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
Assert.Equal (new (0, 0, 20, 10), pos);
view.FillRect (view.Viewport);
expected = @"
┌──────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────┘
"
;
pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
{
var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
view.DrawingContent += (s, e) =>
{
Region savedClip = view.AddViewportToClip ();
for (var row = 0; row < view.Viewport.Height; row++)
{
Application.Driver?.Move (1, row + 1);
for (var col = 0; col < view.Viewport.Width; col++)
{
Application.Driver?.AddStr ($"{col}");
}
}
View.SetClip (savedClip);
e.Cancel = true;
};
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
var expected = @"
┌──────────────────┐
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
└──────────────────┘
"
;
Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
Assert.Equal (new (0, 0, 20, 10), pos);
view.FillRect (view.Viewport);
expected = @"
┌──────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────┘
";
pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
top.Dispose ();
}
[Theory]
[AutoInitShutdown (configLocation: ConfigLocations.Default)]
[InlineData (true)]
[InlineData (false)]
public void Clear_Does_Not_Spillover_Its_Parent (bool label)
{
var root = new View { Width = 20, Height = 10, ColorScheme = Colors.ColorSchemes ["Base"] };
string text = new ('c', 100);
View v = label
// Label has Width/Height == AutoSize, so Frame.Size will be (100, 1)
? new Label { Text = text }
// TextView has Width/Height == (Dim.Fill, 1), so Frame.Size will be 20 (width of root), 1
: new TextView { Width = Dim.Fill (), Height = 1, Text = text };
root.Add (v);
var top = new Toplevel ();
top.Add (root);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
if (label)
{
Assert.False (v.CanFocus);
Assert.Equal (new (0, 0, text.Length, 1), v.Frame);
}
else
{
Assert.True (v.CanFocus);
Assert.Equal (new (0, 0, 20, 1), v.Frame);
}
TestHelpers.AssertDriverContentsWithFrameAre (
@"
cccccccccccccccccccc",
_output
);
Attribute [] attributes =
{
Colors.ColorSchemes ["TopLevel"].Normal,
Colors.ColorSchemes ["Base"].Normal,
Colors.ColorSchemes ["Base"].Focus
};
if (label)
{
TestHelpers.AssertDriverAttributesAre (
@"
111111111111111111110
111111111111111111110",
_output,
Application.Driver,
attributes
);
}
else
{
TestHelpers.AssertDriverAttributesAre (
@"
222222222222222222220
111111111111111111110",
_output,
Application.Driver,
attributes
);
}
if (label)
{
root.CanFocus = true;
v.CanFocus = true;
Assert.True (v.HasFocus);
v.SetFocus ();
Assert.True (v.HasFocus);
Application.LayoutAndDraw ();
TestHelpers.AssertDriverAttributesAre (
@"
222222222222222222220
111111111111111111110",
_output,
Application.Driver,
attributes
);
}
Application.End (runState);
top.Dispose ();
}
}

View File

@@ -0,0 +1,303 @@
#nullable enable
using System.Text;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class ClipTests (ITestOutputHelper _output)
{
[Fact]
[SetupFakeDriver]
public void Move_Is_Not_Constrained_To_Viewport ()
{
var view = new View
{
X = 1,
Y = 1,
Width = 3, Height = 3
};
view.Margin!.Thickness = new (1);
view.Move (0, 0);
Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row));
view.Move (-1, -1);
Assert.Equal (new (1, 1), new Point (Application.Driver!.Col, Application.Driver!.Row));
view.Move (1, 1);
Assert.Equal (new (3, 3), 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.Padding!.Thickness = new (1);
view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;
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);
// When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
view.AddRune (0, 0, Rune.ReplacementChar);
Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
view.AddRune (-1, -1, Rune.ReplacementChar);
Assert.Equal ((Rune)'P', Application.Driver?.Contents! [1, 1].Rune);
view.AddRune (1, 1, Rune.ReplacementChar);
Assert.Equal ((Rune)'P', Application.Driver?.Contents! [3, 3].Rune);
}
[Theory]
[InlineData (0, 0, 1, 1)]
[InlineData (0, 0, 2, 2)]
[InlineData (-1, -1, 2, 2)]
[SetupFakeDriver]
public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
Rectangle toFill = new (x, y, width, height);
View.SetClipToScreen ();
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
// Now try to clear beyond Viewport (invalid; clipping should prevent)
superView.SetNeedsDraw ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (-width, -height, width, height);
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
// Now try to clear beyond Viewport (valid)
superView.SetNeedsDraw ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (-1, -1, width + 1, height + 1);
View.SetClipToScreen ();
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
// Now clear too much size
superView.SetNeedsDraw ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (0, 0, width * 2, height * 2);
View.SetClipToScreen ();
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
// TODO: Simplify this test to just use AddRune directly
[Fact]
[SetupFakeDriver]
[Trait ("Category", "Unicode")]
public void Clipping_Wide_Runes ()
{
((FakeDriver)Application.Driver!).SetBufferSize (30, 1);
var top = new View
{
Id = "top",
Width = Dim.Fill (),
Height = Dim.Fill ()
};
var frameView = new View
{
Id = "frameView",
Width = Dim.Fill (),
Height = Dim.Fill (),
Text = """
"""
};
frameView.Border!.LineStyle = LineStyle.Single;
frameView.Border.Thickness = new (1, 0, 0, 0);
top.Add (frameView);
View.SetClipToScreen ();
top.Layout ();
top.Draw ();
var expectedOutput = """
""";
TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
var view = new View
{
Text = "0123456789",
//Text = "ワイドルー。",
X = 2,
Height = Dim.Auto (),
Width = Dim.Auto (),
BorderStyle = LineStyle.Single
};
view.Border!.Thickness = new (1, 0, 1, 0);
top.Add (view);
top.Layout ();
View.SetClipToScreen ();
top.Draw ();
// 012345678901234567890123456789012345678
// 012 34 56 78 90 12 34 56 78 90 12 34 56 78
// │こ れ は 広 い ル ー ン ラ イ ン で す 。
// 01 2345678901234 56 78 90 12 34 56
// │<> |0123456989│<39> ン ラ イ ン で す 。
expectedOutput = """
<EFBFBD>0123456789<EFBFBD>
""";
TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
}
// TODO: Add more AddRune tests to cover all the cases where wide runes are clipped
[Fact]
[SetupFakeDriver]
public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped ()
{
// Screen is 25x25
// View is 25x25
// Viewport is (0, 0, 23, 23)
// ContentSize is (10, 10)
// ViewportToScreen is (1, 1, 23, 23)
// Visible content is (1, 1, 10, 10)
// Expected clip is (1, 1, 10, 10) - same as visible content
Rectangle expectedClip = new (1, 1, 10, 10);
// Arrange
var view = new View
{
Width = Dim.Fill (),
Height = Dim.Fill (),
ViewportSettings = ViewportSettings.ClipContentOnly
};
view.SetContentSize (new Size (10, 10));
view.Border!.Thickness = new (1);
view.BeginInit ();
view.EndInit ();
Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
// Act
view.AddViewportToClip ();
// Assert
Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
view.Dispose ();
}
[Fact]
[SetupFakeDriver]
public void SetClip_Default_ClipsToViewport ()
{
// Screen is 25x25
// View is 25x25
// Viewport is (0, 0, 23, 23)
// ContentSize is (10, 10)
// ViewportToScreen is (1, 1, 23, 23)
// Visible content is (1, 1, 10, 10)
// Expected clip is (1, 1, 23, 23) - same as Viewport
Rectangle expectedClip = new (1, 1, 23, 23);
// Arrange
var view = new View
{
Width = Dim.Fill (),
Height = Dim.Fill ()
};
view.SetContentSize (new Size (10, 10));
view.Border!.Thickness = new (1);
view.BeginInit ();
view.EndInit ();
Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
view.Viewport = view.Viewport with { X = 1, Y = 1 };
// Act
view.AddViewportToClip ();
// Assert
Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
view.Dispose ();
}
}

View File

@@ -0,0 +1,32 @@
#nullable enable
using System.Text;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class DrawEventTests (ITestOutputHelper _output)
{
[Fact]
[AutoInitShutdown]
public void DrawContentComplete_Event_Is_Always_Called ()
{
var viewCalled = false;
var tvCalled = false;
var view = new View { Width = 10, Height = 10, Text = "View" };
view.DrawComplete += (s, e) => viewCalled = true;
var tv = new TextView { Y = 11, Width = 10, Height = 10 };
tv.DrawComplete += (s, e) => tvCalled = true;
var top = new Toplevel ();
top.Add (view, tv);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
Assert.True (viewCalled);
Assert.True (tvCalled);
top.Dispose ();
}
}

View File

@@ -1,5 +1,6 @@
#nullable enable
using System.Text;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
@@ -8,248 +9,6 @@ namespace Terminal.Gui.ViewTests;
public class DrawTests (ITestOutputHelper _output)
{
[Fact]
[SetupFakeDriver]
public void Move_Is_Not_Constrained_To_Viewport ()
{
var view = new View
{
X = 1,
Y = 1,
Width = 3, Height = 3
};
view.Margin!.Thickness = new (1);
view.Move (0, 0);
Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row));
view.Move (-1, -1);
Assert.Equal (new (1, 1), new Point (Application.Driver!.Col, Application.Driver!.Row));
view.Move (1, 1);
Assert.Equal (new (3, 3), 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.Padding!.Thickness = new (1);
view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;
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);
// When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
view.AddRune (0, 0, Rune.ReplacementChar);
Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
view.AddRune (-1, -1, Rune.ReplacementChar);
Assert.Equal ((Rune)'P', Application.Driver?.Contents! [1, 1].Rune);
view.AddRune (1, 1, Rune.ReplacementChar);
Assert.Equal ((Rune)'P', Application.Driver?.Contents! [3, 3].Rune);
}
[Theory]
[InlineData (0, 0, 1, 1)]
[InlineData (0, 0, 2, 2)]
[InlineData (-1, -1, 2, 2)]
[SetupFakeDriver]
public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
Rectangle toFill = new (x, y, width, height);
View.SetClipToScreen ();
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
// Now try to clear beyond Viewport (invalid; clipping should prevent)
superView.SetNeedsDraw ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (-width, -height, width, height);
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
// Now try to clear beyond Viewport (valid)
superView.SetNeedsDraw ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (-1, -1, width + 1, height + 1);
View.SetClipToScreen ();
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
// Now clear too much size
superView.SetNeedsDraw ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (0, 0, width * 2, height * 2);
View.SetClipToScreen ();
view.FillRect (toFill);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[SetupFakeDriver]
public void Clear_ClearsEntireViewport ()
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
// On Draw exit the view is excluded from the clip, so this will do nothing.
view.ClearViewport ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
View.SetClipToScreen ();
view.ClearViewport ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[SetupFakeDriver]
public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly ()
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single,
ViewportSettings = ViewportSettings.ClearContentOnly
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
View.SetClipToScreen ();
view.ClearViewport ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[AutoInitShutdown]
[Trait ("Category", "Unicode")]
@@ -290,72 +49,6 @@ public class DrawTests (ITestOutputHelper _output)
top.Dispose ();
}
// TODO: Simplify this test to just use AddRune directly
[Fact]
[SetupFakeDriver]
[Trait ("Category", "Unicode")]
public void Clipping_Wide_Runes ()
{
((FakeDriver)Application.Driver!).SetBufferSize (30, 1);
var top = new View ()
{
Id = "top",
Width = Dim.Fill (),
Height = Dim.Fill (),
};
var frameView = new View ()
{
Id = "frameView",
Width = Dim.Fill (),
Height = Dim.Fill (),
Text = """
""",
};
frameView.Border!.LineStyle = LineStyle.Single;
frameView.Border.Thickness = new (1, 0, 0, 0);
top.Add (frameView);
View.SetClipToScreen ();
top.Layout ();
top.Draw ();
string expectedOutput = """
""";
TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
var view = new View
{
Text = "0123456789",
//Text = "ワイドルー。",
X = 2,
Height = Dim.Auto (),
Width = Dim.Auto (),
BorderStyle = LineStyle.Single
};
view.Border!.Thickness = new (1, 0, 1, 0);
top.Add (view);
top.Layout ();
View.SetClipToScreen ();
top.Draw ();
// 012345678901234567890123456789012345678
// 012 34 56 78 90 12 34 56 78 90 12 34 56 78
// │こ れ は 広 い ル ー ン ラ イ ン で す 。
// 01 2345678901234 56 78 90 12 34 56
// │<> |0123456989│<39> ン ラ イ ン で す 。
expectedOutput = """
<EFBFBD>0123456789<EFBFBD>
""";
TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
}
// TODO: Add more AddRune tests to cover all the cases where wide runes are clipped
[Fact]
[AutoInitShutdown]
[Trait ("Category", "Output")]
@@ -941,74 +634,6 @@ public class DrawTests (ITestOutputHelper _output)
// This test has nothing to do with color - removing as it is not relevant and fragile
}
[Fact]
[SetupFakeDriver]
public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped ()
{
// Screen is 25x25
// View is 25x25
// Viewport is (0, 0, 23, 23)
// ContentSize is (10, 10)
// ViewportToScreen is (1, 1, 23, 23)
// Visible content is (1, 1, 10, 10)
// Expected clip is (1, 1, 10, 10) - same as visible content
Rectangle expectedClip = new (1, 1, 10, 10);
// Arrange
var view = new View
{
Width = Dim.Fill (),
Height = Dim.Fill (),
ViewportSettings = ViewportSettings.ClipContentOnly
};
view.SetContentSize (new Size (10, 10));
view.Border!.Thickness = new (1);
view.BeginInit ();
view.EndInit ();
Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
// Act
view.ClipViewport ();
// Assert
Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
view.Dispose ();
}
[Fact]
[SetupFakeDriver]
public void SetClip_Default_ClipsToViewport ()
{
// Screen is 25x25
// View is 25x25
// Viewport is (0, 0, 23, 23)
// ContentSize is (10, 10)
// ViewportToScreen is (1, 1, 23, 23)
// Visible content is (1, 1, 10, 10)
// Expected clip is (1, 1, 23, 23) - same as Viewport
Rectangle expectedClip = new (1, 1, 23, 23);
// Arrange
var view = new View
{
Width = Dim.Fill (),
Height = Dim.Fill ()
};
view.SetContentSize (new Size (10, 10));
view.Border!.Thickness = new (1);
view.BeginInit ();
view.EndInit ();
Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
view.Viewport = view.Viewport with { X = 1, Y = 1 };
// Act
view.ClipViewport ();
// Assert
Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
view.Dispose ();
}
[Fact]
[TestRespondersDisposed]
public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
@@ -1042,4 +667,266 @@ public class DrawTests (ITestOutputHelper _output)
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.Frame = new (3, 3, 10, 1);
Assert.Equal (new (3, 3, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
//Application.Refresh();
top.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit",
_output
);
Application.End (runState);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
top.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.X = 3;
view.Y = 3;
view.Width = 10;
view.Height = 1;
Assert.Equal (new (3, 3, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
View.SetClipToScreen ();
top.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit"
,
_output
);
Application.End (runState);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.Frame = new (1, 1, 10, 1);
Assert.Equal (new (1, 1, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
top.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit"
,
_output
);
Application.End (runState);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
top.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.X = 1;
view.Y = 1;
view.Width = 10;
view.Height = 1;
Assert.Equal (new (1, 1, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
View.SetClipToScreen ();
top.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit"
,
_output
);
Application.End (runState);
top.Dispose ();
}
public class DerivedView : View
{
public DerivedView () { CanFocus = true; }
public bool IsKeyDown { get; set; }
public bool IsKeyPress { get; set; }
public bool IsKeyUp { get; set; }
public override string Text { get; set; }
protected override bool OnDrawingContent ()
{
var idx = 0;
// BUGBUG: v2 - this should use Viewport, not Frame
for (var r = 0; r < Frame.Height; r++)
{
for (var c = 0; c < Frame.Width; c++)
{
if (idx < Text.Length)
{
char rune = Text [idx];
if (rune != '\n')
{
AddRune (c, r, (Rune)Text [idx]);
}
idx++;
if (rune == '\n')
{
break;
}
}
}
}
ClearNeedsDraw ();
return true;
}
protected override bool OnKeyDown (Key keyEvent)
{
IsKeyDown = true;
return true;
}
public override bool OnKeyUp (Key keyEvent)
{
IsKeyUp = true;
return true;
}
protected override bool OnKeyDownNotHandled (Key keyEvent)
{
IsKeyPress = true;
return true;
}
}
}

View File

@@ -296,4 +296,64 @@ public class NeedsDrawTests ()
Assert.Equal (new (1, 1, 5, 5), view._needsDrawRect);
}
[Fact]
[AutoInitShutdown]
public void Frame_Set_After_Initialize_Update_NeededDisplay ()
{
var frame = new FrameView ();
var label = new Label
{
ColorScheme = Colors.ColorSchemes ["Menu"], X = 0, Y = 0, Text = "This should be the first line."
};
var view = new View
{
X = 0, // don't overcomplicate unit tests
Y = 1,
Height = Dim.Auto (DimAutoStyle.Text),
Width = Dim.Auto (DimAutoStyle.Text),
Text = "Press me!"
};
frame.Add (label, view);
frame.X = Pos.Center ();
frame.Y = Pos.Center ();
frame.Width = 40;
frame.Height = 8;
Toplevel top = new ();
top.Add (frame);
RunState runState = Application.Begin (top);
top.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 80, 25), top._needsDrawRect); };
frame.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 40, 8), frame._needsDrawRect); };
label.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 38, 1), label._needsDrawRect); };
view.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 13, 1), view._needsDrawRect); };
Assert.Equal (new (0, 0, 80, 25), top.Frame);
Assert.Equal (new (20, 8, 40, 8), frame.Frame);
Assert.Equal (
new (20, 8, 60, 16),
new Rectangle (
frame.Frame.Left,
frame.Frame.Top,
frame.Frame.Right,
frame.Frame.Bottom
)
);
Assert.Equal (new (0, 0, 30, 1), label.Frame);
Assert.Equal (new (0, 1, 9, 1), view.Frame); // this proves frame was set
Application.End (runState);
top.Dispose ();
}
}

View File

@@ -0,0 +1,107 @@
#nullable enable
using System.Text;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class TransparentTests (ITestOutputHelper _output)
{
[Fact]
[SetupFakeDriver]
public void Transparent_Text_Occludes ()
{
var super = new View
{
Id = "super",
Width = 20,
Height = 5,
};
super.DrawingContent += (sender, args) =>
{
var s = sender as View;
s!.FillRect(s!.Viewport, Glyphs.Stipple);
args.Cancel = true;
};
var sub = new View
{
X = 1,
Y = 1,
Width = 15,
Height = 3,
Id = "sub",
Text = "Sub",
ViewportSettings = ViewportSettings.Transparent,
BorderStyle = LineStyle.Single
};
super.Add (sub);
super.Layout ();
super.Draw ();
_ = TestHelpers.AssertDriverContentsWithFrameAre (
@"
░░░░░░░░░░░░░░░░░░░░
░┌─────────────┐░░░░
░│Sub░░░░░░░░░░│░░░░
░└─────────────┘░░░░
░░░░░░░░░░░░░░░░░░░░", _output);
}
[Fact]
[SetupFakeDriver]
public void Transparent_Subview_Occludes ()
{
var super = new View
{
Id = "super",
Width = 20,
Height = 5,
};
super.DrawingContent += (sender, args) =>
{
var s = sender as View;
s!.FillRect (s!.Viewport, Glyphs.Stipple);
args.Cancel = true;
};
var sub = new View
{
X = 1,
Y = 1,
Width = 15,
Height = 3,
Id = "sub",
ViewportSettings = ViewportSettings.Transparent,
BorderStyle = LineStyle.Single
};
var subSub = new View
{
X = Pos.Center(),
Y = Pos.Center(),
Width = Dim.Auto(),
Height = Dim.Auto(),
Id = "subSub",
Text = "subSub",
};
sub.Add (subSub);
super.Add (sub);
super.Layout ();
super.Draw ();
_ = TestHelpers.AssertDriverContentsWithFrameAre (
@"
░░░░░░░░░░░░░░░░░░░░
░┌─────────────┐░░░░
░│░░░subSub░░░░│░░░░
░└─────────────┘░░░░
░░░░░░░░░░░░░░░░░░░░", _output);
}
}