浏览代码

Illustrates #2331 (Scrollview not respecting clip) does not reproduce (#2332)

* Proves that the issue #2331 don't have reason to happen.

* fixes #2336

* Fixes #2331. ScrollView may not be honoring clip region; CustomButton shows outside

* More appropriate solution for the issue #2331.

* Start refactoring LineCanvas for mixing line style support (e.g. double into single)

* Add remaining resolvers

* Implement corner border style mixing in LineCanvas

* Refactor and simplify resolvers

* Move tests to Core folder and namespace to Terminal.Gui.CoreTests

* Fixes #2333. TextField is selecting badly a word on double click.

* Add unit test deleting a word with accented char.

* Fixes 2331. ScrollView may not be honoring clip region.

* Add a custom button scenario.

* Fixes #2350. Clipping broke (see Clipping scenario).

* Is preferable use NeedDisplay instead of Bounds.

---------

Co-authored-by: Tig Kindel <[email protected]>
Co-authored-by: tznind <[email protected]>
BDisp 2 年之前
父节点
当前提交
c85ff954aa

+ 3 - 1
Terminal.Gui/Core/TextFormatter.cs

@@ -1190,7 +1190,9 @@ namespace Terminal.Gui {
 			for (int line = 0; line < linesFormated.Count; line++) {
 				if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
 					continue;
-				if ((isVertical && line > maxBounds.Left + maxBounds.Width - bounds.X) || (!isVertical && line > maxBounds.Top + maxBounds.Height - bounds.Y))
+				if ((isVertical && line >= maxBounds.Left + maxBounds.Width - 1)
+					|| (!isVertical && line >= maxBounds.Top + maxBounds.Height - 1))
+
 					break;
 
 				var runes = lines [line].ToRunes ();

+ 15 - 11
Terminal.Gui/Core/View.cs

@@ -1105,15 +1105,8 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public void Clear ()
 		{
-			Rect containerBounds = GetContainerBounds ();
-			Rect viewBounds = Bounds;
-			if (!containerBounds.IsEmpty) {
-				viewBounds.Width = Math.Min (viewBounds.Width, containerBounds.Width);
-				viewBounds.Height = Math.Min (viewBounds.Height, containerBounds.Height);
-			}
-
-			var h = viewBounds.Height;
-			var w = viewBounds.Width;
+			var h = Frame.Height;
+			var w = Frame.Width;
 			for (var line = 0; line < h; line++) {
 				Move (0, line);
 				for (var col = 0; col < w; col++)
@@ -1525,13 +1518,13 @@ namespace Terminal.Gui {
 			}
 
 			if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
-				Clear ();
+				Rect containerBounds = GetContainerBounds ();
+				Clear (ViewToScreen (GetNeedDisplay (containerBounds)));
 				SetChildNeedsDisplay ();
 				// Draw any Text
 				if (TextFormatter != null) {
 					TextFormatter.NeedsFormat = true;
 				}
-				Rect containerBounds = GetContainerBounds ();
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
 				    HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
 				    containerBounds);
@@ -1569,6 +1562,17 @@ namespace Terminal.Gui {
 			ClearNeedsDisplay ();
 		}
 
+		Rect GetNeedDisplay (Rect containerBounds)
+		{
+			Rect rect = NeedDisplay;
+			if (!containerBounds.IsEmpty) {
+				rect.Width = Math.Min (NeedDisplay.Width, containerBounds.Width);
+				rect.Height = Math.Min (NeedDisplay.Height, containerBounds.Height);
+			}
+
+			return rect;
+		}
+
 		Rect GetContainerBounds ()
 		{
 			var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);

+ 313 - 0
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -0,0 +1,313 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "ASCIICustomButtonTest", Description: "ASCIICustomButton sample")]
+	[ScenarioCategory ("Controls")]
+	public class ASCIICustomButtonTest : Scenario {
+		private static bool smallerWindow;
+		private ScrollViewTestWindow scrollViewTestWindow;
+		private MenuItem miSmallerWindow;
+
+		public override void Init (ColorScheme colorScheme)
+		{
+			Application.Init ();
+			scrollViewTestWindow = new ScrollViewTestWindow ();
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem("Window Size", new MenuItem [] {
+					miSmallerWindow = new MenuItem ("Smaller Window", "", ChangeWindowSize) {
+						CheckType = MenuItemCheckStyle.Checked
+					},
+					null,
+					new MenuItem("Quit", "",() => Application.RequestStop(),null,null, Key.Q | Key.CtrlMask)
+				})
+			});
+			Application.Top.Add (menu, scrollViewTestWindow);
+			Application.Run ();
+		}
+
+		private void ChangeWindowSize ()
+		{
+			smallerWindow = miSmallerWindow.Checked = !miSmallerWindow.Checked;
+			scrollViewTestWindow.Dispose ();
+			Application.Top.Remove (scrollViewTestWindow);
+			scrollViewTestWindow = new ScrollViewTestWindow ();
+			Application.Top.Add (scrollViewTestWindow);
+		}
+
+		public override void Run ()
+		{
+		}
+
+		public class ASCIICustomButton : Button {
+			public string Description => $"Description of: {id}";
+
+			public event Action<ASCIICustomButton> PointerEnter;
+
+			private Label fill;
+			private FrameView border;
+			private string id;
+
+			public ASCIICustomButton (string text, Pos x, Pos y, int width, int height) : base (text)
+			{
+				CustomInitialize ("", text, x, y, width, height);
+			}
+
+			public ASCIICustomButton (string id, string text, Pos x, Pos y, int width, int height) : base (text)
+			{
+				CustomInitialize (id, text, x, y, width, height);
+			}
+
+			private void CustomInitialize (string id, string text, Pos x, Pos y, int width, int height)
+			{
+				this.id = id;
+				X = x;
+				Y = y;
+
+				Frame = new Rect {
+					Width = width,
+					Height = height
+				};
+
+				border = new FrameView () {
+					Width = width,
+					Height = height
+				};
+
+				AutoSize = false;
+
+				var fillText = new System.Text.StringBuilder ();
+				for (int i = 0; i < Bounds.Height; i++) {
+					if (i > 0) {
+						fillText.AppendLine ("");
+					}
+					for (int j = 0; j < Bounds.Width; j++) {
+						fillText.Append ("█");
+					}
+				}
+
+				fill = new Label (fillText.ToString ()) {
+					Visible = false,
+					CanFocus = false
+				};
+
+				var title = new Label (text) {
+					X = Pos.Center (),
+					Y = Pos.Center (),
+				};
+
+				border.MouseClick += This_MouseClick;
+				border.Subviews [0].MouseClick += This_MouseClick;
+				fill.MouseClick += This_MouseClick;
+				title.MouseClick += This_MouseClick;
+
+				Add (border, fill, title);
+			}
+
+			private void This_MouseClick (MouseEventArgs obj)
+			{
+				OnMouseEvent (obj.MouseEvent);
+			}
+
+			public override bool OnMouseEvent (MouseEvent mouseEvent)
+			{
+				Debug.WriteLine ($"{mouseEvent.Flags}");
+				if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+					if (!HasFocus && SuperView != null) {
+						if (!SuperView.HasFocus) {
+							SuperView.SetFocus ();
+						}
+						SetFocus ();
+						SetNeedsDisplay ();
+					}
+
+					OnClicked ();
+					return true;
+				}
+				return base.OnMouseEvent (mouseEvent);
+			}
+
+			public override bool OnEnter (View view)
+			{
+				border.Visible = false;
+				fill.Visible = true;
+				PointerEnter.Invoke (this);
+				view = this;
+				return base.OnEnter (view);
+			}
+
+			public override bool OnLeave (View view)
+			{
+				border.Visible = true;
+				fill.Visible = false;
+				if (view == null)
+					view = this;
+				return base.OnLeave (view);
+			}
+		}
+
+		public class ScrollViewTestWindow : Window {
+			private List<Button> buttons;
+			private const int BUTTONS_ON_PAGE = 7;
+			private const int BUTTON_HEIGHT = 3;
+
+			private ScrollView scrollView;
+			private ASCIICustomButton selected;
+
+			public ScrollViewTestWindow ()
+			{
+				Title = "ScrollViewTestWindow";
+
+				Label titleLabel = null;
+				if (smallerWindow) {
+					Width = 80;
+					Height = 25;
+
+					scrollView = new ScrollView () {
+						X = 3,
+						Y = 1,
+						Width = 24,
+						Height = BUTTONS_ON_PAGE * BUTTON_HEIGHT,
+						ShowVerticalScrollIndicator = true,
+						ShowHorizontalScrollIndicator = false
+					};
+				} else {
+					Width = Dim.Fill ();
+					Height = Dim.Fill ();
+
+					titleLabel = new Label ("DOCUMENTS") {
+						X = 0,
+						Y = 0
+					};
+
+					scrollView = new ScrollView () {
+						X = 0,
+						Y = 1,
+						Width = 27,
+						Height = BUTTONS_ON_PAGE * BUTTON_HEIGHT,
+						ShowVerticalScrollIndicator = true,
+						ShowHorizontalScrollIndicator = false
+					};
+				}
+
+				scrollView.ClearKeybindings ();
+
+				buttons = new List<Button> ();
+				Button prevButton = null;
+				int count = 20;
+				for (int j = 0; j < count; j++) {
+					Pos yPos = prevButton == null ? 0 : Pos.Bottom (prevButton);
+					var button = new ASCIICustomButton (j.ToString (), $"section {j}", 0, yPos, 25, BUTTON_HEIGHT);
+					button.Id = $"button{j}";
+					button.Clicked += Button_Clicked;
+					button.PointerEnter += Button_PointerEnter;
+					button.MouseClick += Button_MouseClick;
+					button.KeyPress += Button_KeyPress;
+					scrollView.Add (button);
+					buttons.Add (button);
+					prevButton = button;
+				}
+
+				var closeButton = new ASCIICustomButton ("close", "Close", 0, Pos.Bottom (prevButton), 25, BUTTON_HEIGHT);
+				closeButton.Clicked += Button_Clicked;
+				closeButton.PointerEnter += Button_PointerEnter;
+				closeButton.MouseClick += Button_MouseClick;
+				closeButton.KeyPress += Button_KeyPress;
+				scrollView.Add (closeButton);
+				buttons.Add (closeButton);
+
+				var pages = buttons.Count / BUTTONS_ON_PAGE;
+				if (buttons.Count % BUTTONS_ON_PAGE > 0)
+					pages++;
+
+				scrollView.ContentSize = new Size (25, pages * BUTTONS_ON_PAGE * BUTTON_HEIGHT);
+				if (smallerWindow) {
+					Add (scrollView);
+				} else {
+					Add (titleLabel, scrollView);
+				}
+			}
+
+			private void Button_KeyPress (KeyEventEventArgs obj)
+			{
+				switch (obj.KeyEvent.Key) {
+				case Key.End:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
+						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0)));
+					obj.Handled = true;
+					return;
+				case Key.Home:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X, 0);
+					obj.Handled = true;
+					return;
+				case Key.PageDown:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 Math.Max (scrollView.ContentOffset.Y - scrollView.Frame.Height,
+						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
+						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0))));
+					obj.Handled = true;
+					return;
+				case Key.PageUp:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 Math.Min (scrollView.ContentOffset.Y + scrollView.Frame.Height, 0));
+					obj.Handled = true;
+					return;
+				}
+			}
+
+			private void Button_MouseClick (MouseEventArgs obj)
+			{
+				if (obj.MouseEvent.Flags == MouseFlags.WheeledDown) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						scrollView.ContentOffset.Y - BUTTON_HEIGHT);
+					obj.Handled = true;
+				} else if (obj.MouseEvent.Flags == MouseFlags.WheeledUp) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						Math.Min (scrollView.ContentOffset.Y + BUTTON_HEIGHT, 0));
+					obj.Handled = true;
+				}
+			}
+
+			private void Button_Clicked ()
+			{
+				MessageBox.Query ("Button clicked.", $"'{selected.Text}' clicked!", "Ok");
+				if (selected.Text == "Close") {
+					Application.RequestStop ();
+				}
+			}
+
+			private void Button_PointerEnter (ASCIICustomButton obj)
+			{
+				bool? moveDown;
+				if (obj.Frame.Y > selected?.Frame.Y) {
+					moveDown = true;
+				} else if (obj.Frame.Y < selected?.Frame.Y) {
+					moveDown = false;
+				} else {
+					moveDown = null;
+				}
+				var offSet = selected != null ? obj.Frame.Y - selected.Frame.Y + (-scrollView.ContentOffset.Y % BUTTON_HEIGHT) : 0;
+				selected = obj;
+				if (moveDown == true && selected.Frame.Y + scrollView.ContentOffset.Y + BUTTON_HEIGHT >= scrollView.Frame.Height && offSet != BUTTON_HEIGHT) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						Math.Min (scrollView.ContentOffset.Y - BUTTON_HEIGHT, -(selected.Frame.Y - scrollView.Frame.Height + BUTTON_HEIGHT)));
+				} else if (moveDown == true && selected.Frame.Y + scrollView.ContentOffset.Y >= scrollView.Frame.Height) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						scrollView.ContentOffset.Y - BUTTON_HEIGHT);
+				} else if (moveDown == true && selected.Frame.Y + scrollView.ContentOffset.Y < 0) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						-selected.Frame.Y);
+				} else if (moveDown == false && selected.Frame.Y < -scrollView.ContentOffset.Y) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						Math.Max (scrollView.ContentOffset.Y + BUTTON_HEIGHT, selected.Frame.Y));
+				} else if (moveDown == false && selected.Frame.Y + scrollView.ContentOffset.Y > scrollView.Frame.Height) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 -(selected.Frame.Y - scrollView.Frame.Height + BUTTON_HEIGHT));
+				}
+			}
+		}
+	}
+}

+ 66 - 14
UnitTests/Core/BorderTests.cs

@@ -1,15 +1,19 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection.Emit;
 using Xunit;
+using Xunit.Abstractions;
 using Rune = System.Rune;
 
 namespace Terminal.Gui.CoreTests {
 	public class BorderTests {
-		[Fact]
-		[AutoInitShutdown]
+		readonly ITestOutputHelper output;
+
+		public BorderTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact, AutoInitShutdown]
 		public void Constructor_Defaults ()
 		{
 			var b = new Border ();
@@ -45,8 +49,7 @@ namespace Terminal.Gui.CoreTests {
 			Assert.False (b.DrawMarginFrame);
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void ActualWidth_ActualHeight ()
 		{
 			var v = new View (new Rect (5, 10, 60, 20), "", new Border ());
@@ -81,8 +84,7 @@ namespace Terminal.Gui.CoreTests {
 			Assert.Equal (new Thickness (5, 5, 5, 5), b.GetSumThickness ());
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void DrawContent_With_Child_Border ()
 		{
 			var top = Application.Top;
@@ -303,8 +305,7 @@ namespace Terminal.Gui.CoreTests {
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void DrawContent_With_Parent_Border ()
 		{
 			var top = Application.Top;
@@ -540,8 +541,7 @@ namespace Terminal.Gui.CoreTests {
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void BorderOnControlWithNoChildren ()
 		{
 			var label = new TextField ("Loading...") {
@@ -557,5 +557,57 @@ namespace Terminal.Gui.CoreTests {
 
 			Assert.Null (Record.Exception (() => label.Redraw (label.Bounds)));
 		}
+
+		[Fact, AutoInitShutdown]
+		public void BorderStyle_And_DrawMarginFrame_Gets_Sets ()
+		{
+			var lblTop = new Label ("At 0,0");
+			var lblFrame = new Label ("Centered") { X = Pos.Center (), Y = Pos.Center () };
+			var frame = new FrameView () { Y = 1, Width = 20, Height = 3 };
+			var lblFill = new Label () { Width = Dim.Fill(),Height = Dim.Fill(), Visible = false };
+			var fillText = new System.Text.StringBuilder ();
+			for (int i = 0; i < frame.Bounds.Height; i++) {
+				if (i > 0) {
+					fillText.AppendLine ("");
+				}
+				for (int j = 0; j < frame.Bounds.Width; j++) {
+					fillText.Append ("█");
+				}
+			}
+			lblFill.Text = fillText.ToString ();
+			frame.Add (lblFill, lblFrame);
+			var lblBottom = new Label ("At 0,4") { Y = 4 };
+			Application.Top.Add (lblTop, frame, lblBottom);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (BorderStyle.Single, frame.Border.BorderStyle);
+			Assert.True (frame.Border.DrawMarginFrame);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0              
+┌──────────────────┐
+│     Centered     │
+└──────────────────┘
+At 0,4              ", output);
+
+			frame.Border.BorderStyle = BorderStyle.None;
+			Application.Refresh ();
+			Assert.True (frame.Border.DrawMarginFrame);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0        
+              
+      Centered
+              
+At 0,4        ", output);
+
+			frame.Border.DrawMarginFrame = false;
+			lblFill.Visible = true;
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0              
+████████████████████
+██████Centered██████
+████████████████████
+At 0,4              ", output);
+		}
 	}
 }

+ 223 - 5
UnitTests/Views/ScrollViewTests.cs

@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using NStack;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -280,5 +276,227 @@ namespace Terminal.Gui.ViewTests {
 ◄░░░├─┤░► 
 ", output);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Frame_And_Labels_Does_Not_Overspill_ScrollView ()
+		{
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (50, 50)
+			};
+			for (int i = 0; i < 8; i++) {
+				sv.Add (new CustomButton ("█", $"Button {i}", 20, 3) { Y = i * 3 });
+			}
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   █████████▲
+   ██████But┬
+   █████████┴
+   ┌────────░
+   │     But░
+   └────────░
+   ┌────────░
+   │     But░
+   └────────▼
+   ◄├┤░░░░░► ", output);
+
+			sv.ContentOffset = new Point (5, 5);
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   ─────────▲
+   ─────────┬
+    Button 2│
+   ─────────┴
+   ─────────░
+    Button 3░
+   ─────────░
+   ─────────░
+    Button 4▼
+   ◄├─┤░░░░► ", output);
+		}
+
+		private class CustomButton : FrameView {
+			private Label labelFill;
+			private Label labelText;
+
+			public CustomButton (string fill, ustring text, int width, int height)
+			{
+				Width = width;
+				Height = height;
+				labelFill = new Label () { AutoSize = false, Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
+				var fillText = new System.Text.StringBuilder ();
+				for (int i = 0; i < Bounds.Height; i++) {
+					if (i > 0) {
+						fillText.AppendLine ("");
+					}
+					for (int j = 0; j < Bounds.Width; j++) {
+						fillText.Append (fill);
+					}
+				}
+				labelFill.Text = fillText.ToString ();
+				labelText = new Label (text) { X = Pos.Center (), Y = Pos.Center () };
+				Add (labelFill, labelText);
+				CanFocus = true;
+			}
+
+			public override bool OnEnter (View view)
+			{
+				Border.BorderStyle = BorderStyle.None;
+				Border.DrawMarginFrame = false;
+				labelFill.Visible = true;
+				view = this;
+				return base.OnEnter (view);
+			}
+
+			public override bool OnLeave (View view)
+			{
+				Border.BorderStyle = BorderStyle.Single;
+				Border.DrawMarginFrame = true;
+				labelFill.Visible = false;
+				if (view == null)
+					view = this;
+				return base.OnLeave (view);
+			}
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Clear_Window_Inside_ScrollView ()
+		{
+			var topLabel = new Label ("At 15,0") { X = 15 };
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (23, 23),
+				KeepContentAlwaysInViewport = false
+			};
+			var bottomLabel = new Label ("At 15,15") { X = 15, Y = 15 };
+			Application.Top.Add (topLabel, sv, bottomLabel);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
+                       
+                       
+            ▲          
+            ┬          
+            ┴          
+            ░          
+            ░          
+            ░          
+            ░          
+            ░          
+            ▼          
+   ◄├┤░░░░░►           
+                       
+                       
+               At 15,15", output);
+
+			var attributes = new Attribute [] {
+				Colors.TopLevel.Normal,
+				Colors.TopLevel.Focus,
+				Colors.Base.Normal
+			};
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111110000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", attributes);
+
+			sv.Add (new Window ("1") { X = 3, Y = 3, Width = 20, Height = 20 });
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
+                       
+                       
+            ▲          
+            ┬          
+            ┴          
+      ┌ 1 ──░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ▼          
+   ◄├┤░░░░░►           
+                       
+                       
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00011111111110000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", attributes);
+
+			sv.ContentOffset = new Point (20, 20);
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
+                       
+                       
+     │      ▲          
+     │      ░          
+   ──┘      ░          
+            ░          
+            ░          
+            ┬          
+            │          
+            ┴          
+            ▼          
+   ◄░░░░├─┤►           
+                       
+                       
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00022200000010000000000
+00022200000010000000000
+00022200000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111110000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", attributes);
+		}
 	}
 }