diff --git a/Terminal.Gui/Core/TextFormatter.cs b/Terminal.Gui/Core/TextFormatter.cs index d485cf353..920575b83 100644 --- a/Terminal.Gui/Core/TextFormatter.cs +++ b/Terminal.Gui/Core/TextFormatter.cs @@ -1145,20 +1145,17 @@ namespace Terminal.Gui { var start = isVertical ? bounds.Top : bounds.Left; var size = isVertical ? bounds.Height : bounds.Width; var current = start; - var startX = start < 0 - ? start - : isVertical ? start - y : start - x; var savedClip = Application.Driver?.Clip; if (Application.Driver != null && containerBounds != default) { Application.Driver.Clip = containerBounds == default ? bounds : new Rect (Math.Max (containerBounds.X, bounds.X), Math.Max (containerBounds.Y, bounds.Y), - Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), - Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top)); + Math.Max (Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), 0), + Math.Max (Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top), 0)); } - for (var idx = startX; current < start + size; idx++) { + for (var idx = (isVertical ? start - y : start - x); current < start + size; idx++) { if (idx < 0) { current++; continue; diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 4e90ba4db..20971d9ab 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1417,15 +1417,20 @@ namespace Terminal.Gui { Border.DrawContent (this); } - if (!ustring.IsNullOrEmpty (Text) || (this is Label && !AutoSize)) { + if (!ustring.IsNullOrEmpty (TextFormatter.Text) || (this is Label && !AutoSize)) { Clear (); // Draw any Text if (TextFormatter != null) { TextFormatter.NeedsFormat = true; } + var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds); + containerBounds.X = Math.Max (containerBounds.X, Driver.Clip.X); + containerBounds.Y = Math.Max (containerBounds.Y, Driver.Clip.Y); + containerBounds.Width = Math.Min (containerBounds.Width, Driver.Clip.Width); + containerBounds.Height = Math.Min (containerBounds.Height, Driver.Clip.Height); TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (), HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled, - SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds)); + containerBounds); } // Invoke DrawContentEvent diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 947c3ac08..ca204fa13 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -221,26 +221,6 @@ namespace Terminal.Gui { SetNeedsDisplay (); } - /// - public override void Redraw (Rect bounds) - { - if (ColorScheme != null) { - Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); - } - - if (Border != null) { - Border.DrawContent (this); - } - - if (!ustring.IsNullOrEmpty (TextFormatter.Text)) { - Clear (); - TextFormatter.NeedsFormat = true; - TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (), - HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled, - SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds)); - } - } - /// public override bool ProcessHotKey (KeyEvent kb) { diff --git a/UnitTests/ScenarioTests.cs b/UnitTests/ScenarioTests.cs index 61fd9a3e8..10757b366 100644 --- a/UnitTests/ScenarioTests.cs +++ b/UnitTests/ScenarioTests.cs @@ -26,15 +26,15 @@ namespace Terminal.Gui { int CreateInput (string input) { // Put a control-q in at the end - Console.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true)); + FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true)); foreach (var c in input.Reverse ()) { if (char.IsLetter (c)) { - Console.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false)); + FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false)); } else { - Console.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false)); + FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false)); } } - return Console.MockKeyPresses.Count; + return FakeConsole.MockKeyPresses.Count; } /// @@ -48,59 +48,61 @@ namespace Terminal.Gui { List scenarioClasses = Scenario.GetDerivedClasses (); Assert.NotEmpty (scenarioClasses); - foreach (var scenarioClass in scenarioClasses) { + lock (FakeConsole.MockKeyPresses) { + foreach (var scenarioClass in scenarioClasses) { - // Setup some fake keypresses - // Passing empty string will cause just a ctrl-q to be fired - Console.MockKeyPresses.Clear (); - int stackSize = CreateInput (""); + // Setup some fake keypresses + // Passing empty string will cause just a ctrl-q to be fired + FakeConsole.MockKeyPresses.Clear (); + int stackSize = CreateInput (""); - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - int iterations = 0; - Application.Iteration = () => { - iterations++; - // Stop if we run out of control... - if (iterations > 10) { - Application.RequestStop (); + int iterations = 0; + Application.Iteration = () => { + iterations++; + // Stop if we run out of control... + if (iterations > 10) { + Application.RequestStop (); + } + }; + + int ms; + if (scenarioClass.Name == "CharacterMap") { + ms = 2000; + } else { + ms = 1000; } - }; + var abortCount = 0; + Func abortCallback = (MainLoop loop) => { + abortCount++; + Application.RequestStop (); + return false; + }; + var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback); - int ms; - if (scenarioClass.Name == "CharacterMap") { - ms = 1500; - }else { - ms = 1000; + var scenario = (Scenario)Activator.CreateInstance (scenarioClass); + scenario.Init (Application.Top, Colors.Base); + scenario.Setup (); + // There is no need to call Application.Begin because Init already creates the Application.Top + // If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run. + //var rs = Application.Begin (Application.Top); + scenario.Run (); + + //Application.End (rs); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + + if (abortCount != 0) { + output.WriteLine ($"Scenario {scenarioClass} had abort count of {abortCount}"); + } + + Assert.Equal (0, abortCount); + // # of key up events should match # of iterations + Assert.Equal (1, iterations); + Assert.Equal (stackSize, iterations); } - var abortCount = 0; - Func abortCallback = (MainLoop loop) => { - abortCount++; - Application.RequestStop (); - return false; - }; - var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback); - - var scenario = (Scenario)Activator.CreateInstance (scenarioClass); - scenario.Init (Application.Top, Colors.Base); - scenario.Setup (); - // There is no need to call Application.Begin because Init already creates the Application.Top - // If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run. - //var rs = Application.Begin (Application.Top); - scenario.Run (); - - //Application.End (rs); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - - if(abortCount != 0) { - output.WriteLine ($"Scenario {scenarioClass} had abort count of {abortCount}"); - } - - Assert.Equal (0, abortCount); - // # of key up events should match # of iterations - Assert.Equal (1, iterations); - Assert.Equal (stackSize, iterations); } #if DEBUG_IDISPOSABLE foreach (var inst in Responder.Instances) { diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index f3b499abf..8a88eaac3 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -1985,14 +1985,276 @@ Y view.Frame = new Rect (0, 0, 8, 4); ((FakeDriver)Application.Driver).SetBufferSize (7, 3); + } + + [Fact, AutoInitShutdown] + public void DrawTextFormatter_Respects_The_Clip_Bounds () + { + var view = new View (new Rect (0, 0, 20, 20)); + view.Add (new Label ("0123456789abcdefghij")); + view.Add (new Label (0, 1, "1\n2\n3\n4\n5\n6\n7\n8\n9\n0")); + view.Add (new Button (1, 1, "Press me!")); + var scrollView = new ScrollView (new Rect (1, 1, 15, 10)) { + ContentSize = new Size (40, 40), + ShowHorizontalScrollIndicator = true, + ShowVerticalScrollIndicator = true + }; + scrollView.Add (view); + var win = new Window (new Rect (1, 1, 20, 14), "Test"); + win.Add (scrollView); + Application.Top.Add (win); + Application.Begin (Application.Top); + + var expected = @" + ┌ Test ────────────┐ + │ │ + │ 0123456789abcd▲ │ + │ 1[ Press me! ]┬ │ + │ 2 │ │ + │ 3 ┴ │ + │ 4 ░ │ + │ 5 ░ │ + │ 6 ░ │ + │ 7 ░ │ + │ 8 ▼ │ + │ ◄├───┤░░░░░░░► │ + │ │ + └──────────────────┘ +"; + + var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + expected = @" -┌────── -│ -│ + ┌ Test ────────────┐ + │ │ + │ 123456789abcde▲ │ + │ [ Press me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄├───┤░░░░░░░► │ + │ │ + └──────────────────┘ "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 7, 3), pos); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 23456789abcdef▲ │ + │ Press me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄├────┤░░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 3456789abcdefg▲ │ + │ Press me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄├────┤░░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 456789abcdefgh▲ │ + │ ress me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄░├───┤░░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 56789abcdefghi▲ │ + │ ess me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄░├────┤░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 6789abcdefghij▲ │ + │ ss me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄░├────┤░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 789abcdefghij ▲ │ + │ s me! ] ┬ │ + │ │ │ + │ ┴ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ░ │ + │ ▼ │ + │ ◄░░├───┤░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Home, new KeyModifiers ()))); + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 1[ Press me! ]▲ │ + │ 2 ┬ │ + │ 3 │ │ + │ 4 ┴ │ + │ 5 ░ │ + │ 6 ░ │ + │ 7 ░ │ + │ 8 ░ │ + │ 9 ▼ │ + │ ◄├───┤░░░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 2 ▲ │ + │ 3 ┬ │ + │ 4 │ │ + │ 5 ┴ │ + │ 6 ░ │ + │ 7 ░ │ + │ 8 ░ │ + │ 9 ░ │ + │ 0 ▼ │ + │ ◄├───┤░░░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); + + Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Application.Top.Redraw (Application.Top.Bounds); + + expected = @" + ┌ Test ────────────┐ + │ │ + │ 3 ▲ │ + │ 4 ┬ │ + │ 5 │ │ + │ 6 ┴ │ + │ 7 ░ │ + │ 8 ░ │ + │ 9 ░ │ + │ 0 ░ │ + │ ▼ │ + │ ◄├───┤░░░░░░░► │ + │ │ + └──────────────────┘ +"; + + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (1, 1, 21, 14), pos); } } }