| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003 |
- using System.Text;
- using UnitTests;
- using Xunit.Abstractions;
- namespace ViewBaseTests.Drawing;
- public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBase
- {
- #region GetClip / SetClip Tests
- [Fact]
- public void GetClip_ReturnsDriverClip ()
- {
- IDriver driver = CreateFakeDriver ();
- var region = new Region (new (10, 10, 20, 20));
- driver.Clip = region;
- View view = new () { Driver = driver };
- Region? result = view.GetClip ();
- Assert.NotNull (result);
- Assert.Equal (region, result);
- }
- [Fact]
- public void SetClip_NullRegion_DoesNothing ()
- {
- IDriver driver = CreateFakeDriver ();
- var original = new Region (new (5, 5, 10, 10));
- driver.Clip = original;
- View view = new () { Driver = driver };
- view.SetClip (null);
- Assert.Equal (original, driver.Clip);
- }
- [Fact]
- public void SetClip_ValidRegion_SetsDriverClip ()
- {
- IDriver driver = CreateFakeDriver ();
- var region = new Region (new (10, 10, 30, 30));
- View view = new () { Driver = driver };
- view.SetClip (region);
- Assert.Equal (region, driver.Clip);
- }
- #endregion
- #region SetClipToScreen Tests
- [Fact]
- public void SetClipToScreen_ReturnsPreviousClip ()
- {
- IDriver driver = CreateFakeDriver ();
- var original = new Region (new (5, 5, 10, 10));
- driver.Clip = original;
- View view = new () { Driver = driver };
- Region? previous = view.SetClipToScreen ();
- Assert.Equal (original, previous);
- Assert.NotEqual (original, driver.Clip);
- }
- [Fact]
- public void SetClipToScreen_SetsClipToScreen ()
- {
- IDriver driver = CreateFakeDriver ();
- View view = new () { Driver = driver };
- view.SetClipToScreen ();
- Assert.NotNull (driver.Clip);
- Assert.Equal (driver.Screen, driver.Clip.GetBounds ());
- }
- #endregion
- #region ExcludeFromClip Tests
- [Fact]
- public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow ()
- {
- View view = new () { Driver = null };
- Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
- Assert.Null (exception);
- }
- [Fact]
- public void ExcludeFromClip_Rectangle_ExcludesArea ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (new (0, 0, 80, 25));
- View view = new () { Driver = driver };
- var toExclude = new Rectangle (10, 10, 20, 20);
- view.ExcludeFromClip (toExclude);
- // Verify the region was excluded
- Assert.NotNull (driver.Clip);
- Assert.False (driver.Clip.Contains (15, 15));
- }
- [Fact]
- public void ExcludeFromClip_Region_NullDriver_DoesNotThrow ()
- {
- View view = new () { Driver = null };
- Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Region (new (5, 5, 10, 10))));
- Assert.Null (exception);
- }
- [Fact]
- public void ExcludeFromClip_Region_ExcludesArea ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (new (0, 0, 80, 25));
- View view = new () { Driver = driver };
- var toExclude = new Region (new (10, 10, 20, 20));
- view.ExcludeFromClip (toExclude);
- // Verify the region was excluded
- Assert.NotNull (driver.Clip);
- Assert.False (driver.Clip.Contains (15, 15));
- }
- #endregion
- #region AddFrameToClip Tests
- [Fact]
- public void AddFrameToClip_NullDriver_ReturnsNull ()
- {
- var view = new View { X = 0, Y = 0, Width = 10, Height = 10 };
- view.BeginInit ();
- view.EndInit ();
- Region? result = view.AddFrameToClip ();
- Assert.Null (result);
- }
- [Fact]
- public void AddFrameToClip_IntersectsWithFrame ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region? previous = view.AddFrameToClip ();
- Assert.NotNull (previous);
- Assert.NotNull (driver.Clip);
- // The clip should now be the intersection of the screen and the view's frame
- var expectedBounds = new Rectangle (1, 1, 20, 20);
- Assert.Equal (expectedBounds, driver.Clip.GetBounds ());
- }
- #endregion
- #region AddViewportToClip Tests
- [Fact]
- public void AddViewportToClip_NullDriver_ReturnsNull ()
- {
- var view = new View { X = 0, Y = 0, Width = 10, Height = 10 };
- view.BeginInit ();
- view.EndInit ();
- Region? result = view.AddViewportToClip ();
- Assert.Null (result);
- }
- [Fact]
- public void AddViewportToClip_IntersectsWithViewport ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region? previous = view.AddViewportToClip ();
- Assert.NotNull (previous);
- Assert.NotNull (driver.Clip);
- // The clip should be the viewport area
- Rectangle viewportScreen = view.ViewportToScreen (new Rectangle (Point.Empty, view.Viewport.Size));
- Assert.Equal (viewportScreen, driver.Clip.GetBounds ());
- }
- [Fact]
- public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.SetContentSize (new Size (100, 100));
- view.ViewportSettings = ViewportSettingsFlags.ClipContentOnly;
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region? previous = view.AddViewportToClip ();
- Assert.NotNull (previous);
- Assert.NotNull (driver.Clip);
- // The clip should be limited to visible content
- Rectangle visibleContent = view.ViewportToScreen (new Rectangle (new (-view.Viewport.X, -view.Viewport.Y), view.GetContentSize ()));
- Rectangle viewport = view.ViewportToScreen (new Rectangle (Point.Empty, view.Viewport.Size));
- Rectangle expected = Rectangle.Intersect (viewport, visibleContent);
- Assert.Equal (expected, driver.Clip.GetBounds ());
- }
- #endregion
- #region Clip Interaction Tests
- [Fact]
- public void ClipRegions_StackCorrectly_WithNestedViews ()
- {
- IDriver driver = CreateFakeDriver (100, 100);
- driver.Clip = new (driver.Screen);
- var superView = new View
- {
- X = 1,
- Y = 1,
- Width = 50,
- Height = 50,
- Driver = driver
- };
- superView.BeginInit ();
- superView.EndInit ();
- var view = new View
- {
- X = 5,
- Y = 5,
- Width = 30,
- Height = 30
- };
- superView.Add (view);
- superView.LayoutSubViews ();
- // Set clip to superView's frame
- Region? superViewClip = superView.AddFrameToClip ();
- Rectangle superViewBounds = driver.Clip.GetBounds ();
- // Now set clip to view's frame
- Region? viewClip = view.AddFrameToClip ();
- Rectangle viewBounds = driver.Clip.GetBounds ();
- // Child clip should be within superView clip
- Assert.True (superViewBounds.Contains (viewBounds.Location));
- // Restore superView clip
- view.SetClip (superViewClip);
- // Assert.Equal (superViewBounds, driver.Clip.GetBounds ());
- }
- [Fact]
- public void ClipRegions_RespectPreviousClip ()
- {
- IDriver driver = CreateFakeDriver ();
- var initialClip = new Region (new (20, 20, 40, 40));
- driver.Clip = initialClip;
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 60,
- Height = 60,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region? previous = view.AddFrameToClip ();
- // The new clip should be the intersection of the initial clip and the view's frame
- Rectangle expected = Rectangle.Intersect (
- initialClip.GetBounds (),
- view.FrameToScreen ()
- );
- Assert.Equal (expected, driver.Clip.GetBounds ());
- // Restore should give us back the original
- view.SetClip (previous);
- Assert.Equal (initialClip.GetBounds (), driver.Clip.GetBounds ());
- }
- #endregion
- #region Edge Cases
- [Fact]
- public void AddFrameToClip_EmptyFrame_WorksCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 0,
- Height = 0,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region? previous = view.AddFrameToClip ();
- Assert.NotNull (previous);
- Assert.NotNull (driver.Clip);
- }
- [Fact]
- public void AddViewportToClip_EmptyViewport_WorksCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 1, // Minimal size to have adornments
- Height = 1,
- Driver = driver
- };
- view.Border!.Thickness = new (1);
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- // With border thickness of 1, the viewport should be empty
- Assert.True (view.Viewport.Size.Width == 0 || view.Viewport.Size.Height == 0);
- Region? previous = view.AddViewportToClip ();
- Assert.NotNull (previous);
- }
- [Fact]
- public void ClipRegions_OutOfBounds_HandledCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 100, // Outside screen bounds
- Y = 100,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region? previous = view.AddFrameToClip ();
- Assert.NotNull (previous);
- // The clip should be empty since the view is outside the screen
- Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100));
- }
- #endregion
- #region Drawing Tests
- [Fact]
- public void Clip_Set_BeforeDraw_ClipsDrawing ()
- {
- IDriver driver = CreateFakeDriver ();
- var clip = new Region (new (10, 10, 10, 10));
- driver.Clip = clip;
- var view = new View
- {
- X = 0,
- Y = 0,
- Width = 50,
- Height = 50,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- view.Draw ();
- // Verify clip was used
- Assert.NotNull (driver.Clip);
- }
- [Fact]
- public void Draw_UpdatesDriverClip ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 1,
- Y = 1,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- view.Draw ();
- // Clip should be updated to exclude the drawn view
- Assert.NotNull (driver.Clip);
- // Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded
- }
- [Fact]
- public void Draw_WithSubViews_ClipsCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var superView = new View
- {
- X = 1,
- Y = 1,
- Width = 50,
- Height = 50,
- Driver = driver
- };
- var view = new View { X = 5, Y = 5, Width = 20, Height = 20 };
- superView.Add (view);
- superView.BeginInit ();
- superView.EndInit ();
- superView.LayoutSubViews ();
- superView.Draw ();
- // Both superView and view should be excluded from clip
- Assert.NotNull (driver.Clip);
- // Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded
- }
- /// <summary>
- /// Tests that wide glyphs (🍎) are correctly clipped when overlapped by bordered subviews
- /// at different column alignments (even vs odd). Demonstrates:
- /// 1. Full clipping at even columns (X=0, X=2)
- /// 2. Partial clipping at odd columns (X=1) resulting in half-glyphs (�)
- /// 3. The recursive draw flow and clip exclusion mechanism
- ///
- /// For detailed draw flow documentation, see ViewDrawingClippingTests.DrawFlow.md
- /// </summary>
- [Fact]
- public void Draw_WithBorderSubView_DrawsCorrectly ()
- {
- IApplication app = Application.Create ();
- app.Init ("fake");
- IDriver driver = app!.Driver!;
- driver.SetScreenSize (30, 20);
- driver!.Clip = new (driver.Screen);
- var superView = new Runnable ()
- {
- X = 0,
- Y = 0,
- Width = Dim.Auto () + 4,
- Height = Dim.Auto () + 1,
- Driver = driver
- };
- Rune codepoint = Glyphs.Apple;
- superView.DrawingContent += (s, e) =>
- {
- var view = s as View;
- for (var r = 0; r < view!.Viewport.Height; r++)
- {
- for (var c = 0; c < view.Viewport.Width; c += 2)
- {
- if (codepoint != default (Rune))
- {
- view.AddRune (c, r, codepoint);
- }
- }
- }
- e.DrawContext?.AddDrawnRectangle (view.Viewport);
- e.Cancel = true;
- };
- var viewWithBorderAtX0 = new View
- {
- Text = "viewWithBorderAtX0",
- BorderStyle = LineStyle.Dashed,
- X = 0,
- Y = 1,
- Width = Dim.Auto (),
- Height = 3
- };
- var viewWithBorderAtX1 = new View
- {
- Text = "viewWithBorderAtX1",
- BorderStyle = LineStyle.Dashed,
- X = 1,
- Y = Pos.Bottom (viewWithBorderAtX0) + 1,
- Width = Dim.Auto (),
- Height = 3
- };
- var viewWithBorderAtX2 = new View
- {
- Text = "viewWithBorderAtX2",
- BorderStyle = LineStyle.Dashed,
- X = 2,
- Y = Pos.Bottom (viewWithBorderAtX1) + 1,
- Width = Dim.Auto (),
- Height = 3
- };
- superView.Add (viewWithBorderAtX0, viewWithBorderAtX1, viewWithBorderAtX2);
- driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
- app.Begin (superView);
- // Begin calls LayoutAndDraw, so no need to call it again here
- // app.LayoutAndDraw();
- DriverAssert.AssertDriverContentsAre (
- """
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
- ┆viewWithBorderAtX0┆🍎🍎🍎
- └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- ①┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎
- ①┆viewWithBorderAtX1┆ 🍎🍎
- ①└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- 🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
- 🍎┆viewWithBorderAtX2┆🍎🍎
- 🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- """,
- output,
- driver);
- DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX0┆🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m①┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m①┆viewWithBorderAtX1┆ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m①└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┆viewWithBorderAtX2┆🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
- output, driver);
- DriverImpl? driverImpl = driver as DriverImpl;
- FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
- output.WriteLine ("Driver Output After Redraw:\n" + driver.GetOutput().GetLastOutput());
- // BUGBUG: Border.set_LineStyle does not call SetNeedsDraw
- viewWithBorderAtX1!.Border!.LineStyle = LineStyle.Single;
- viewWithBorderAtX1.Border!.SetNeedsDraw ();
- app.LayoutAndDraw ();
- DriverAssert.AssertDriverContentsAre (
- """
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
- ┆viewWithBorderAtX0┆🍎🍎🍎
- └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- ①┌──────────────────┐ 🍎🍎
- ①│viewWithBorderAtX1│ 🍎🍎
- ①└──────────────────┘ 🍎🍎
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- 🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
- 🍎┆viewWithBorderAtX2┆🍎🍎
- 🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
- 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
- """,
- output,
- driver);
- }
- [Fact]
- public void Draw_WithBorderSubView_At_Col1_In_WideGlyph_DrawsCorrectly ()
- {
- IApplication app = Application.Create ();
- app.Init ("fake");
- IDriver driver = app!.Driver!;
- driver.SetScreenSize (6, 3); // Minimal: 6 cols wide (3 for content + 2 for border + 1), 3 rows high (1 for content + 2 for border)
- driver!.Clip = new (driver.Screen);
- var superView = new Runnable ()
- {
- X = 0,
- Y = 0,
- Width = Dim.Fill (),
- Height = Dim.Fill (),
- Driver = driver
- };
- Rune codepoint = Glyphs.Apple;
- superView.DrawingContent += (s, e) =>
- {
- View? view = s as View;
- view?.AddStr (0, 0, "🍎🍎🍎🍎");
- view?.AddStr (0, 1, "🍎🍎🍎🍎");
- view?.AddStr (0, 2, "🍎🍎🍎🍎");
- e.DrawContext?.AddDrawnRectangle (view!.Viewport);
- e.Cancel = true;
- };
- // Minimal border at X=1 (odd column), Width=3, Height=3 (includes border)
- var viewWithBorder = new View
- {
- Text = "X",
- BorderStyle = LineStyle.Single,
- X = 1,
- Y = 0,
- Width = 3,
- Height = 3
- };
- superView.Add (viewWithBorder);
- driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
- app.Begin (superView);
- DriverAssert.AssertDriverContentsAre (
- """
- ①┌─┐🍎
- ①│X│🍎
- ①└─┘🍎
- """,
- output,
- driver);
- DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m①┌─┐🍎①│X│🍎①└─┘🍎",
- output, driver);
- DriverImpl? driverImpl = driver as DriverImpl;
- FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
- output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
- }
- [Fact]
- public void Draw_WithBorderSubView_At_Col3_In_WideGlyph_DrawsCorrectly ()
- {
- IApplication app = Application.Create ();
- app.Init ("fake");
- IDriver driver = app!.Driver!;
- driver.SetScreenSize (6, 3); // Screen: 6 cols wide, 3 rows high; enough for 3x3 border subview at col 3 plus content on the left
- driver!.Clip = new (driver.Screen);
- var superView = new Runnable ()
- {
- X = 0,
- Y = 0,
- Width = Dim.Fill (),
- Height = Dim.Fill (),
- Driver = driver
- };
- Rune codepoint = Glyphs.Apple;
- superView.DrawingContent += (s, e) =>
- {
- View? view = s as View;
- view?.AddStr (0, 0, "🍎🍎🍎🍎");
- view?.AddStr (0, 1, "🍎🍎🍎🍎");
- view?.AddStr (0, 2, "🍎🍎🍎🍎");
- e.DrawContext?.AddDrawnRectangle (view!.Viewport);
- e.Cancel = true;
- };
- // Minimal border at X=3 (odd column), Width=3, Height=3 (includes border)
- var viewWithBorder = new View
- {
- Text = "X",
- BorderStyle = LineStyle.Single,
- X = 3,
- Y = 0,
- Width = 3,
- Height = 3
- };
- driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
- superView.Add (viewWithBorder);
- app.Begin (superView);
- DriverAssert.AssertDriverContentsAre (
- """
- 🍎①┌─┐
- 🍎①│X│
- 🍎①└─┘
- """,
- output,
- driver);
- DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎①┌─┐🍎①│X│🍎①└─┘",
- output, driver);
- DriverImpl? driverImpl = driver as DriverImpl;
- FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
- output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
- }
- [Fact]
- public void Draw_NonVisibleView_DoesNotUpdateClip ()
- {
- IDriver driver = CreateFakeDriver ();
- var originalClip = new Region (driver.Screen);
- driver.Clip = originalClip.Clone ();
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 20,
- Height = 20,
- Visible = false,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.Draw ();
- // Clip should not be modified for invisible views
- Assert.True (driver.Clip.Equals (originalClip));
- }
- [Fact]
- public void ExcludeFromClip_ExcludesRegion ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- var excludeRect = new Rectangle (15, 15, 10, 10);
- view.ExcludeFromClip (excludeRect);
- Assert.NotNull (driver.Clip);
- Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip
- }
- [Fact]
- public void ExcludeFromClip_WithNullClip_DoesNotThrow ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = null!;
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
- Assert.Null (exception);
- }
- #endregion
- #region Misc Tests
- [Fact]
- public void SetClip_SetsDriverClip ()
- {
- IDriver driver = CreateFakeDriver ();
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- var newClip = new Region (new (5, 5, 30, 30));
- view.SetClip (newClip);
- Assert.Equal (newClip, driver.Clip);
- }
- [Fact (Skip = "See BUGBUG in SetClip")]
- public void SetClip_WithNullClip_ClearsClip ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (new (10, 10, 20, 20));
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.SetClip (null);
- Assert.Null (driver.Clip);
- }
- [Fact]
- public void Draw_Excludes_View_From_Clip ()
- {
- IDriver driver = CreateFakeDriver ();
- var originalClip = new Region (driver.Screen);
- driver.Clip = originalClip.Clone ();
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 20,
- Height = 20,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Region clipWithViewExcluded = originalClip.Clone ();
- clipWithViewExcluded.Exclude (view.Frame);
- view.Draw ();
- Assert.Equal (clipWithViewExcluded, driver.Clip);
- Assert.NotNull (driver.Clip);
- }
- [Fact]
- public void Draw_EmptyViewport_DoesNotCrash ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 10,
- Y = 10,
- Width = 1,
- Height = 1,
- Driver = driver
- };
- view.Border!.Thickness = new (1);
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- // With border of 1, viewport should be empty (0x0 or negative)
- Exception? exception = Record.Exception (() => view.Draw ());
- Assert.Null (exception);
- }
- [Fact]
- public void Draw_VeryLargeView_HandlesClippingCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 0,
- Y = 0,
- Width = 1000,
- Height = 1000,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Exception? exception = Record.Exception (() => view.Draw ());
- Assert.Null (exception);
- }
- [Fact]
- public void Draw_NegativeCoordinates_HandlesClippingCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = -10,
- Y = -10,
- Width = 50,
- Height = 50,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Exception? exception = Record.Exception (() => view.Draw ());
- Assert.Null (exception);
- }
- [Fact]
- public void Draw_OutOfScreenBounds_HandlesClippingCorrectly ()
- {
- IDriver driver = CreateFakeDriver ();
- driver.Clip = new (driver.Screen);
- var view = new View
- {
- X = 100,
- Y = 100,
- Width = 50,
- Height = 50,
- Driver = driver
- };
- view.BeginInit ();
- view.EndInit ();
- view.LayoutSubViews ();
- Exception? exception = Record.Exception (() => view.Draw ());
- Assert.Null (exception);
- }
- #endregion
- }
|