From 2eb05113af34a565bafa2b517d9bb07c53dad2fc Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 5 Mar 2023 18:34:58 +0000 Subject: [PATCH] Fix negative bounds on draw text and thus fixes scrolling. --- Terminal.Gui/Core/TextFormatter.cs | 29 ++-- Terminal.Gui/Core/View.cs | 18 +-- UnitTests/Core/LayoutTests.cs | 17 ++- UnitTests/Text/TextFormatterTests.cs | 190 ++++++++++++++++++++++++++- 4 files changed, 228 insertions(+), 26 deletions(-) diff --git a/Terminal.Gui/Core/TextFormatter.cs b/Terminal.Gui/Core/TextFormatter.cs index e6e1592e3..5d71066e4 100644 --- a/Terminal.Gui/Core/TextFormatter.cs +++ b/Terminal.Gui/Core/TextFormatter.cs @@ -1176,22 +1176,29 @@ namespace Terminal.Gui { } var isVertical = IsVerticalDirection (textDirection); - var savedClip = Application.Driver?.Clip; var maxBounds = bounds; if (Application.Driver != null) { - Application.Driver.Clip = maxBounds = containerBounds == default + maxBounds = containerBounds == default ? bounds : new Rect (Math.Max (containerBounds.X, bounds.X), Math.Max (containerBounds.Y, bounds.Y), Math.Max (Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), 0), Math.Max (Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top), 0)); } + if (maxBounds.Width == 0 || maxBounds.Height == 0) { + return; + } + var savedClip = Application.Driver?.Clip; + if (Application.Driver != null) { + Application.Driver.Clip = maxBounds; + } + var lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0; - for (int line = 0; line < linesFormated.Count; line++) { + for (int line = lineOffset; line < linesFormated.Count; line++) { if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height)) continue; if ((isVertical && line >= maxBounds.Left + maxBounds.Width) - || (!isVertical && line >= maxBounds.Top + maxBounds.Height)) + || (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset)) break; @@ -1267,18 +1274,21 @@ namespace Terminal.Gui { throw new ArgumentOutOfRangeException (); } + var colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0; var start = isVertical ? bounds.Top : bounds.Left; var size = isVertical ? bounds.Height : bounds.Width; - var current = start; + var current = start + colOffset; - for (var idx = (isVertical ? start - y : start - x); current < start + size; idx++) { - if (!fillRemaining && idx < 0) { + for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size; idx++) { + if (idx < 0 || x + current + colOffset < 0) { current++; continue; } else if (!fillRemaining && idx > runes.Length - 1) { break; } - if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X) || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) + if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) + || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) + break; var rune = (Rune)' '; @@ -1316,8 +1326,9 @@ namespace Terminal.Gui { } } } - if (Application.Driver != null) + if (Application.Driver != null) { Application.Driver.Clip = (Rect)savedClip; + } } } } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index eb8907c3d..91755ff40 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1511,15 +1511,17 @@ namespace Terminal.Gui { if (!ustring.IsNullOrEmpty (TextFormatter.Text)) { Rect containerBounds = GetContainerBounds (); - Clear (GetNeedDisplay (containerBounds)); - SetChildNeedsDisplay (); - // Draw any Text - if (TextFormatter != null) { - TextFormatter.NeedsFormat = true; + if (!containerBounds.IsEmpty) { + Clear (GetNeedDisplay (containerBounds)); + SetChildNeedsDisplay (); + // Draw any Text + if (TextFormatter != null) { + TextFormatter.NeedsFormat = true; + } + TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (), + HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), + containerBounds); } - TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (), - HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), - containerBounds); } // Invoke DrawContentEvent diff --git a/UnitTests/Core/LayoutTests.cs b/UnitTests/Core/LayoutTests.cs index 8e5489cd9..68ea9577f 100644 --- a/UnitTests/Core/LayoutTests.cs +++ b/UnitTests/Core/LayoutTests.cs @@ -328,14 +328,17 @@ namespace Terminal.Gui.CoreTests { Application.Begin (Application.Top); Assert.True (label.AutoSize); + // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) + // and height 0 because wasn't set and the text is empty Assert.Equal ("{X=0,Y=0,Width=28,Height=0}", label.Bounds.ToString ()); label.Text = "First line\nSecond line"; Application.Refresh (); - // Here the AutoSize ensuring the right size + // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) + // and height 2 because wasn't set and the text has 2 lines Assert.True (label.AutoSize); - Assert.Equal ("{X=0,Y=0,Width=11,Height=2}", label.Bounds.ToString ()); + Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ()); label.AutoSize = false; Application.Refresh (); @@ -347,12 +350,16 @@ namespace Terminal.Gui.CoreTests { label.Text = "First changed line\nSecond changed line\nNew line"; Application.Refresh (); + // Here the AutoSize is false and the width 28 (Dim.Fill) and + // height 1 because wasn't set and SetMinWidthHeight ensuring the minimum height Assert.False (label.AutoSize); Assert.Equal ("{X=0,Y=0,Width=28,Height=1}", label.Bounds.ToString ()); label.AutoSize = true; Application.Refresh (); + // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) + // and height 3 because wasn't set and the text has 3 lines Assert.True (label.AutoSize); Assert.Equal ("{X=0,Y=0,Width=28,Height=3}", label.Bounds.ToString ()); } @@ -461,9 +468,13 @@ Y Assert.Equal ("123 ", GetContents ()); lbl.Text = "12"; + // Here the AutoSize ensuring the right size with width 3 (Dim.Absolute) + // that was set on the OnAdded method with the text length of 3 + // and height 1 because wasn't set and the text has 1 line Assert.Equal (new Rect (0, 0, 3, 1), lbl.Frame); Assert.Equal (new Rect (0, 0, 3, 1), lbl.NeedDisplay); - Assert.Equal (new Rect (0, 0, 80, 25), lbl.SuperView.NeedDisplay); + Assert.Equal (new Rect (0, 0, 0, 0), lbl.SuperView.NeedDisplay); + Assert.True (lbl.SuperView.LayoutNeeded); lbl.SuperView.Redraw (lbl.SuperView.Bounds); Assert.Equal ("12 ", GetContents ()); diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index a791a6ffb..142035b21 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -2162,7 +2162,7 @@ namespace Terminal.Gui.TextTests { var height = 8; var wrappedLines = TextFormatter.WordWrap (text, width, true); var breakLines = ""; - foreach (var line in wrappedLines) breakLines += $"{line}{Environment.NewLine}"; + foreach (var line in wrappedLines) breakLines += $"{line}{Environment.NewLine}"; var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () }; var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () }; @@ -2200,7 +2200,7 @@ namespace Terminal.Gui.TextTests { var height = 3; var wrappedLines = TextFormatter.WordWrap (text, height, true); var breakLines = ""; - for (int i = 0; i < wrappedLines.Count; i++) breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}"; + for (int i = 0; i < wrappedLines.Count; i++) breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}"; var label = new Label (breakLines) { TextDirection = TextDirection.TopBottom_LeftRight, Width = Dim.Fill (), @@ -2237,7 +2237,7 @@ namespace Terminal.Gui.TextTests { var height = 8; var wrappedLines = TextFormatter.WordWrap (text, width, true); var breakLines = ""; - foreach (var line in wrappedLines) breakLines += $"{line}{Environment.NewLine}"; + foreach (var line in wrappedLines) breakLines += $"{line}{Environment.NewLine}"; var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () }; var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () }; @@ -2276,7 +2276,7 @@ namespace Terminal.Gui.TextTests { var height = 4; var wrappedLines = TextFormatter.WordWrap (text, width, true); var breakLines = ""; - for (int i = 0; i < wrappedLines.Count; i++) breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}"; + for (int i = 0; i < wrappedLines.Count; i++) breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}"; var label = new Label (breakLines) { TextDirection = TextDirection.TopBottom_LeftRight, Width = Dim.Fill (), @@ -2888,7 +2888,7 @@ namespace Terminal.Gui.TextTests { Assert.Equal ("nd", list1 [10].ToString ()); Assert.Equal ("Line", list1 [11].ToString ()); Assert.Equal ("- 2.", list1 [^1].ToString ()); - foreach (var txt in list1) wrappedText1 += txt; + foreach (var txt in list1) wrappedText1 += txt; Assert.Equal (" Asentencehaswords. This isthesecondLine- 2.", wrappedText1); // With preserveTrailingSpaces = true. @@ -2910,7 +2910,7 @@ namespace Terminal.Gui.TextTests { Assert.Equal ("Line", list2 [13].ToString ()); Assert.Equal (" - ", list2 [14].ToString ()); Assert.Equal ("2. ", list2 [^1].ToString ()); - foreach (var txt in list2) wrappedText2 += txt; + foreach (var txt in list2) wrappedText2 += txt; Assert.Equal (" A sentence has words. This is the second Line - 2. ", wrappedText2); } @@ -4288,5 +4288,183 @@ t ", output); 0 0", new Attribute [] { Colors.Base.Normal }); } + + [Fact, AutoInitShutdown] + public void Draw_Negative_Bounds_Horizontal_Without_New_Lines () + { + var subView = new View () { Id = "subView", Y = 1, Width = 7, Text = "subView" }; + var view = new View () { Id = "view", Width = 20, Height = 2, Text = "01234567890123456789" }; + view.Add (subView); + var content = new View () { Id = "content", Width = 20, Height = 20 }; + content.Add (view); + var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 }; + container.Add (content); + var top = Application.Top; + top.Add (container); + Application.Driver.Clip = container.Frame; + Application.Begin (top); + + TestHelpers.AssertDriverContentsWithFrameAre (@" + 01234 + subVi", output); + + content.X = -1; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 12345 + ubVie", output); + + content.Y = -1; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + ubVie", output); + + content.Y = -2; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre ("", output); + + content.X = -20; + content.Y = 0; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre ("", output); + } + + [Fact, AutoInitShutdown] + public void Draw_Negative_Bounds_Horizontal_With_New_Lines () + { + var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "s\nu\nb\nV\ni\ne\nw" }; + var view = new View () { Id = "view", Width = 2, Height = 20, Text = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }; + view.Add (subView); + var content = new View () { Id = "content", Width = 20, Height = 20 }; + content.Add (view); + var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 }; + container.Add (content); + var top = Application.Top; + top.Add (container); + Application.Driver.Clip = container.Frame; + Application.Begin (top); + + TestHelpers.AssertDriverContentsWithFrameAre (@" + 0s + 1u + 2b + 3V + 4i", output); + + content.X = -1; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + s + u + b + V + i", output); + + content.X = -2; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@"", output); + + content.X = 0; + content.Y = -1; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 1u + 2b + 3V + 4i + 5e", output); + + content.Y = -6; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 6w + 7 + 8 + 9 + 0 ", output); + + content.Y = -19; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 9", output); + + content.Y = -20; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre ("", output); + + content.X = -2; + content.Y = 0; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre ("", output); + } + + [Fact, AutoInitShutdown] + public void Draw_Negative_Bounds_Vertical () + { + var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "subView", TextDirection = TextDirection.TopBottom_LeftRight }; + var view = new View () { Id = "view", Width = 2, Height = 20, Text = "01234567890123456789", TextDirection = TextDirection.TopBottom_LeftRight }; + view.Add (subView); + var content = new View () { Id = "content", Width = 20, Height = 20 }; + content.Add (view); + var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 }; + container.Add (content); + var top = Application.Top; + top.Add (container); + Application.Driver.Clip = container.Frame; + Application.Begin (top); + + TestHelpers.AssertDriverContentsWithFrameAre (@" + 0s + 1u + 2b + 3V + 4i", output); + + content.X = -1; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + s + u + b + V + i", output); + + content.X = -2; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@"", output); + + content.X = 0; + content.Y = -1; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 1u + 2b + 3V + 4i + 5e", output); + + content.Y = -6; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 6w + 7 + 8 + 9 + 0 ", output); + + content.Y = -19; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" + 9", output); + + content.Y = -20; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre ("", output); + + content.X = -2; + content.Y = 0; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre ("", output); + } } } \ No newline at end of file