Prechádzať zdrojové kódy

Merge pull request #1710 from BDisp/wide-runes-render-issues

Fixes remaining wide runes render issues.
Tig Kindel 3 rokov pred
rodič
commit
4e25402c8b

+ 12 - 4
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -70,7 +70,9 @@ namespace Terminal.Gui {
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 					Curses.move (crow, ccol);
 					Curses.attrset (curAtttib);
-				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					var curAtttib = currentAttribute;
 					Curses.attrset (contents [crow, ccol + 1, 1]);
@@ -78,9 +80,15 @@ namespace Terminal.Gui {
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
 					Curses.move (crow, ccol);
 					Curses.attrset (curAtttib);
+
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					Curses.addch ((int)(uint)' ');
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					Curses.addch ((int)(uint)rune);
+					contents [crow, ccol, 0] = (int)(uint)rune;
 				}
-				Curses.addch ((int)(uint)rune);
-				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 			} else
@@ -88,7 +96,7 @@ namespace Terminal.Gui {
 
 			ccol++;
 			if (runeWidth > 1) {
-				if (validClip) {
+				if (validClip && ccol < Clip.Right) {
 					contents [crow, ccol, 1] = currentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}

+ 23 - 19
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -108,31 +108,34 @@ namespace Terminal.Gui {
 
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-				} else if (runeWidth < 2 && ccol < Cols - 1
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
 					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
-				}
+					contents [crow, ccol + 1, 2] = 1;
 
-				contents [crow, ccol, 0] = (int)(uint)rune;
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					contents [crow, ccol, 0] = (int)(uint)rune;
+				}
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
+
 				dirtyLine [crow] = true;
 			} else
 				needMove = true;
 
 			ccol++;
 			if (runeWidth > 1) {
-				for (int i = 1; i < runeWidth; i++) {
-					if (validClip) {
-						contents [crow, ccol, 1] = currentAttribute;
-						contents [crow, ccol, 2] = 0;
-					} else {
-						break;
-					}
-					ccol++;
+				if (validClip && ccol < Clip.Right) {
+					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 2] = 0;
 				}
+				ccol++;
 			}
+
 			//if (ccol == Cols) {
 			//	ccol = 0;
 			//	if (crow + 1 < Rows)
@@ -248,17 +251,17 @@ namespace Terminal.Gui {
 
 			var savedRow = FakeConsole.CursorTop;
 			var savedCol = FakeConsole.CursorLeft;
-			for (int row = 0; row < rows; row++) {
+			var savedCursorVisible = FakeConsole.CursorVisible;
+			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row])
 					continue;
 				dirtyLine [row] = false;
-				for (int col = 0; col < cols; col++) {
+				for (int col = left; col < cols; col++) {
 					FakeConsole.CursorTop = row;
 					FakeConsole.CursorLeft = col;
 					for (; col < cols; col++) {
-						if (col > 0 && contents [row, col, 2] == 0
-							&& Rune.ColumnWidth ((char)contents [row, col - 1, 0]) > 1) {
-							FakeConsole.CursorLeft = col + 1;
+						if (contents [row, col, 2] == 0) {
+							FakeConsole.CursorLeft++;
 							continue;
 						}
 
@@ -273,6 +276,7 @@ namespace Terminal.Gui {
 			}
 			FakeConsole.CursorTop = savedRow;
 			FakeConsole.CursorLeft = savedCol;
+			FakeConsole.CursorVisible = savedCursorVisible;
 		}
 
 		public override void Refresh ()
@@ -549,13 +553,13 @@ namespace Terminal.Gui {
 			}
 
 			Clip = new Rect (0, 0, Cols, Rows);
-
-			contents = new int [Rows, Cols, 3];
-			dirtyLine = new bool [Rows];
 		}
 
 		public override void UpdateOffScreen ()
 		{
+			contents = new int [Rows, Cols, 3];
+			dirtyLine = new bool [Rows];
+
 			// Can raise an exception while is still resizing.
 			try {
 				for (int row = 0; row < rows; row++) {

+ 69 - 40
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -114,7 +114,7 @@ namespace Terminal.Gui {
 		int lastWindowHeight;
 		int largestWindowHeight;
 #if PROCESS_REQUEST
-		bool neededProcessRequest;
+				bool neededProcessRequest;
 #endif
 		public int NumberOfCSI { get; }
 
@@ -140,7 +140,7 @@ namespace Terminal.Gui {
 					inputReady.Reset ();
 				}
 #if PROCESS_REQUEST
-				neededProcessRequest = false;
+								neededProcessRequest = false;
 #endif
 				if (inputResultQueue.Count > 0) {
 					return inputResultQueue.Dequeue ();
@@ -196,7 +196,8 @@ namespace Terminal.Gui {
 					if (Console.WindowTop != consoleDriver.Top) {
 						// Top only working on Windows.
 						var winPositionEv = new WindowPositionEvent () {
-							Top = Console.WindowTop
+							Top = Console.WindowTop,
+							Left = Console.WindowLeft
 						};
 						inputResultQueue.Enqueue (new InputResult () {
 							EventType = EventType.WindowPosition,
@@ -205,10 +206,10 @@ namespace Terminal.Gui {
 						return;
 					}
 #if PROCESS_REQUEST
-					if (!neededProcessRequest) {
-						Console.Out.Write ("\x1b[6n");
-						neededProcessRequest = true;
-					}
+										if (!neededProcessRequest) {
+											Console.Out.Write ("\x1b[6n");
+											neededProcessRequest = true;
+										}
 #endif
 				}
 			}
@@ -469,7 +470,7 @@ namespace Terminal.Gui {
 			string value = "";
 			for (int i = 0; i < kChar.Length; i++) {
 				var c = kChar [i];
-				if (c == '[') {
+				if (c == '\u001b' || c == '[') {
 					foundPoint++;
 				} else if (foundPoint == 1 && c != ';' && c != '?') {
 					value += c.ToString ();
@@ -1144,6 +1145,7 @@ namespace Terminal.Gui {
 
 		public struct WindowPositionEvent {
 			public int Top;
+			public int Left;
 			public Point CursorPosition;
 		}
 
@@ -1174,17 +1176,16 @@ namespace Terminal.Gui {
 		const int COLOR_BRIGHT_CYAN = 96;
 		const int COLOR_BRIGHT_WHITE = 97;
 
-		int cols, rows, top;
+		int cols, rows, left, top;
 
 		public override int Cols => cols;
 		public override int Rows => rows;
-		public override int Left => 0;
+		public override int Left => left;
 		public override int Top => top;
 		public override bool HeightAsBuffer { get; set; }
 
 		public NetWinVTConsole NetWinConsole { get; }
 		public bool IsWinPlatform { get; }
-		public bool AlwaysSetPosition { get; set; }
 		public override IClipboard Clipboard { get; }
 		internal override int [,,] Contents => contents;
 
@@ -1242,12 +1243,18 @@ namespace Terminal.Gui {
 
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-				} else if (runeWidth < 2 && ccol < Cols - 1
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
 					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+					contents [crow, ccol + 1, 2] = 1;
+
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					contents [crow, ccol, 0] = (int)(uint)rune;
 				}
-				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 
@@ -1256,7 +1263,7 @@ namespace Terminal.Gui {
 
 			ccol++;
 			if (runeWidth > 1) {
-				if (validClip) {
+				if (validClip && ccol < Clip.Right) {
 					contents [crow, ccol, 1] = currentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}
@@ -1288,6 +1295,9 @@ namespace Terminal.Gui {
 			StopReportingMouseMoves ();
 			Console.ResetColor ();
 			Clear ();
+			//Set cursor key to cursor.
+			Console.Out.Write ("\x1b[?25h");
+			Console.Out.Flush ();
 		}
 
 		void Clear ()
@@ -1313,6 +1323,10 @@ namespace Terminal.Gui {
 		{
 			TerminalResized = terminalResized;
 
+			//Set cursor key to application.
+			Console.Out.Write ("\x1b[?25l");
+			Console.Out.Flush ();
+
 			Console.TreatControlCAsInput = true;
 
 			cols = Console.WindowWidth;
@@ -1455,56 +1469,68 @@ namespace Terminal.Gui {
 			}
 
 			int top = Top;
+			int left = Left;
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int cols = Cols;
+			System.Text.StringBuilder output = new System.Text.StringBuilder ();
+			var lastCol = -1;
 
-			var savedCursorVisible = Console.CursorVisible = false;
+			Console.CursorVisible = false;
 			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row]) {
 					continue;
 				}
 				dirtyLine [row] = false;
-				System.Text.StringBuilder output = new System.Text.StringBuilder ();
-				for (int col = 0; col < cols; col++) {
+				output.Clear ();
+				for (int col = left; col < cols; col++) {
 					if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) {
 						return;
 					}
-					var lastCol = -1;
+					lastCol = -1;
+					var outputWidth = 0;
 					for (; col < cols; col++) {
-						if (col > 0 && contents [row, col, 2] == 0
-							&& Rune.ColumnWidth ((char)contents [row, col - 1, 0]) > 1) {
-
-							if (col == cols - 1 && output.Length > 0) {
-								Console.CursorLeft = lastCol;
+						if (contents [row, col, 2] == 0) {
+							if (output.Length > 0) {
+								//Console.CursorLeft = lastCol;
+								//Console.CursorTop = row;
+								SetVirtualCursorPosition (lastCol, row);
 								Console.Write (output);
+								output.Clear ();
+								lastCol += outputWidth;
+								outputWidth = 0;
+							} else if (lastCol == -1) {
+								lastCol = col;
 							}
+							if (lastCol + 1 < cols)
+								lastCol++;
 							continue;
 						}
 
+						if (lastCol == -1)
+							lastCol = col;
+
 						var attr = contents [row, col, 1];
 						if (attr != redrawAttr) {
 							output.Append (WriteAttributes (attr));
-							if (lastCol == -1)
-								lastCol = col;
-						}
-						if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
-							return;
-						}
-						if (AlwaysSetPosition) {
-							Console.Write ($"{output}{(char)contents [row, col, 0]}");
-						} else {
-							output.Append ((char)contents [row, col, 0]);
-							if (lastCol == -1)
-								lastCol = col;
 						}
+						outputWidth++;
+						output.Append ((char)contents [row, col, 0]);
 						contents [row, col, 2] = 0;
-						if (!AlwaysSetPosition && col == cols - 1) {
-							Console.Write (output);
-						}
 					}
 				}
+				if (output.Length > 0) {
+					//Console.CursorLeft = lastCol;
+					//Console.CursorTop = row;
+					SetVirtualCursorPosition (lastCol, row);
+					Console.Write (output);
+				}
 			}
-			Console.CursorVisible = savedCursorVisible;
+		}
+
+		void SetVirtualCursorPosition (int lastCol, int row)
+		{
+			Console.Out.Write ($"\x1b[{row + 1};{lastCol + 1}H");
+			Console.Out.Flush ();
 		}
 
 		System.Text.StringBuilder WriteAttributes (int attr)
@@ -1779,8 +1805,10 @@ namespace Terminal.Gui {
 				break;
 			case NetEvents.EventType.WindowPosition:
 				var newTop = inputEvent.WindowPositionEvent.Top;
-				if (HeightAsBuffer && top != newTop) {
+				var newLeft = inputEvent.WindowPositionEvent.Left;
+				if (HeightAsBuffer && (top != newTop || left != newLeft)) {
 					top = newTop;
+					left = newLeft;
 					Refresh ();
 				}
 				break;
@@ -1798,6 +1826,7 @@ namespace Terminal.Gui {
 				size = new Size (Math.Max (Min_WindowWidth, Console.WindowWidth),
 					Console.WindowHeight);
 				top = 0;
+				left = 0;
 			} else {
 				//largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
 				largestWindowHeight = Console.BufferHeight;

+ 11 - 4
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1480,15 +1480,22 @@ namespace Terminal.Gui {
 					OutputBuffer [prevPosition].Char.UnicodeChar = ' ';
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					var prevPosition = crow * Cols + ccol + 1;
 					OutputBuffer [prevPosition].Char.UnicodeChar = (char)' ';
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					OutputBuffer [position].Char.UnicodeChar = (char)' ';
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					OutputBuffer [position].Char.UnicodeChar = (char)rune;
+					contents [crow, ccol, 0] = (int)(uint)rune;
 				}
 				OutputBuffer [position].Attributes = (ushort)currentAttribute;
-				OutputBuffer [position].Char.UnicodeChar = (char)rune;
-				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 				WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow);
@@ -1496,7 +1503,7 @@ namespace Terminal.Gui {
 
 			ccol++;
 			if (runeWidth > 1) {
-				if (validClip) {
+				if (validClip && ccol < Clip.Right) {
 					position = crow * Cols + ccol;
 					OutputBuffer [position].Attributes = (ushort)currentAttribute;
 					OutputBuffer [position].Char.UnicodeChar = (char)0x00;

+ 0 - 18
Terminal.Gui/Core/Application.cs

@@ -123,24 +123,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		/// <summary>
-		/// Used only by <see cref="NetDriver"/> to forcing always moving the cursor position when writing to the screen.
-		/// </summary>
-		public static bool AlwaysSetPosition {
-			get {
-				if (Driver is NetDriver) {
-					return (Driver as NetDriver).AlwaysSetPosition;
-				}
-				return false;
-			}
-			set {
-				if (Driver is NetDriver) {
-					(Driver as NetDriver).AlwaysSetPosition = value;
-					Driver.Refresh ();
-				}
-			}
-		}
-
 		static Key alternateForwardKey = Key.PageDown | Key.CtrlMask;
 
 		/// <summary>

+ 3 - 6
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;

+ 7 - 2
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

+ 0 - 20
Terminal.Gui/Views/Button.cs

@@ -221,26 +221,6 @@ namespace Terminal.Gui {
 			SetNeedsDisplay ();
 		}
 
-		/// <inheritdoc/>
-		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));
-			}
-		}
-
 		///<inheritdoc/>
 		public override bool ProcessHotKey (KeyEvent kb)
 		{

+ 195 - 0
UICatalog/Scenarios/RuneWidthGreaterThanOne.cs

@@ -0,0 +1,195 @@
+using System;
+using System.Globalization;
+using System.Text;
+using System.Threading;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "RuneWidthGreaterThanOne", Description: "Test rune width greater than one")]
+	[ScenarioCategory ("Controls")]
+	public class RuneWidthGreaterThanOne : Scenario {
+		private Label _label;
+		private TextField _text;
+		private Button _button;
+		private Label _labelR;
+		private Label _labelV;
+		private Window _win;
+		private string _lastRunesUsed;
+
+		public override void Init (Toplevel top, ColorScheme colorScheme)
+		{
+			Application.Init ();
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("Margin", new MenuItem [] {
+					new MenuItem ("With margin", "", WithMargin),
+					new MenuItem ("Without margin", "", WithoutMargin)
+				}),
+				new MenuBarItem ("Draw Margin Frame", new MenuItem [] {
+					new MenuItem ("With draw", "", WithDrawMargin),
+					new MenuItem ("Without draw", "", WithoutDrawMargin)
+				}),
+				new MenuBarItem ("Runes length", new MenuItem [] {
+					new MenuItem ("Wide", "", WideRunes),
+					new MenuItem ("Narrow", "", NarrowRunes),
+					new MenuItem ("Mixed", "", MixedRunes)
+				})
+			});
+
+			_label = new Label () {
+				X = Pos.Center (),
+				Y = 0,
+				ColorScheme = new ColorScheme () {
+					Normal = Colors.Base.Focus
+				}
+			};
+			_text = new TextField () {
+				X = Pos.Center (),
+				Y = 2,
+				Width = 20
+			};
+			_button = new Button () {
+				X = Pos.Center (),
+				Y = 4
+			};
+			_labelR = new Label () {
+				X = Pos.AnchorEnd (30),
+				Y = 18
+			};
+			_labelV = new Label () {
+				TextDirection = TextDirection.TopBottom_LeftRight,
+				X = Pos.AnchorEnd (30),
+				Y = Pos.Bottom (_labelR)
+			};
+			_win = new Window () {
+				X = 5,
+				Y = 5,
+				Width = Dim.Fill (22),
+				Height = Dim.Fill (5)
+			};
+			_win.Add (_label, _text, _button, _labelR, _labelV);
+			Application.Top.Add (menu, _win);
+
+			WideRunes ();
+			//NarrowRunes ();
+			//MixedRunes ();
+			Application.Run ();
+		}
+
+		private void UnsetClickedEvent ()
+		{
+			switch (_lastRunesUsed) {
+			case "Narrow":
+				_button.Clicked -= NarrowMessage;
+				break;
+			case "Mixed":
+				_button.Clicked -= MixedMessage;
+				break;
+			case "Wide":
+				_button.Clicked -= WideMessage;
+				break;
+			}
+		}
+
+		private void MixedMessage ()
+		{
+			MessageBox.Query ("Say Hello 你", $"Hello {_text.Text}", "Ok");
+		}
+
+		private void NarrowMessage ()
+		{
+			MessageBox.Query ("Say Hello", $"Hello {_text.Text}", "Ok");
+		}
+
+		private void WideMessage ()
+		{
+			MessageBox.Query ("こんにちはと言う", $"こんにちは {_text.Text}", "Ok");
+		}
+
+		private void MixedRunes ()
+		{
+			UnsetClickedEvent ();
+			_label.Text = "Enter your name 你:";
+			_text.Text = "gui.cs 你:";
+			_button.Text = "Say Hello 你";
+			_button.Clicked += MixedMessage;
+			_labelR.X = Pos.AnchorEnd (21);
+			_labelR.Y = 18;
+			_labelR.Text = "This is a test text 你";
+			_labelV.X = Pos.AnchorEnd (21);
+			_labelV.Y = Pos.Bottom (_labelR);
+			_labelV.Text = "This is a test text 你";
+			_win.Title = "HACC Demo 你";
+			_lastRunesUsed = "Mixed";
+			Application.Refresh ();
+		}
+
+		private void NarrowRunes ()
+		{
+			UnsetClickedEvent ();
+			_label.Text = "Enter your name:";
+			_text.Text = "gui.cs";
+			_button.Text = "Say Hello";
+			_button.Clicked += NarrowMessage;
+			_labelR.X = Pos.AnchorEnd (19);
+			_labelR.Y = 18;
+			_labelR.Text = "This is a test text";
+			_labelV.X = Pos.AnchorEnd (19);
+			_labelV.Y = Pos.Bottom (_labelR);
+			_labelV.Text = "This is a test text";
+			_win.Title = "HACC Demo";
+			_lastRunesUsed = "Narrow";
+			Application.Refresh ();
+		}
+
+		private void WideRunes ()
+		{
+			UnsetClickedEvent ();
+			_label.Text = "あなたの名前を入力してください:";
+			_text.Text = "ティラミス";
+			_button.Text = "こんにちはと言う";
+			_button.Clicked += WideMessage;
+			_labelR.X = Pos.AnchorEnd (29);
+			_labelR.Y = 18;
+			_labelR.Text = "あなたの名前を入力してください";
+			_labelV.X = Pos.AnchorEnd (29);
+			_labelV.Y = Pos.Bottom (_labelR);
+			_labelV.Text = "あなたの名前を入力してください";
+			_win.Title = "デモエムポンズ";
+			_lastRunesUsed = "Wide";
+			Application.Refresh ();
+		}
+
+		private void WithoutDrawMargin ()
+		{
+			_win.Border.BorderStyle = BorderStyle.None;
+			_win.Border.DrawMarginFrame = false;
+		}
+
+		private void WithDrawMargin ()
+		{
+			_win.Border.DrawMarginFrame = true;
+			_win.Border.BorderStyle = BorderStyle.Single;
+		}
+
+		private void WithoutMargin ()
+		{
+			_win.X = 0;
+			_win.Y = 0;
+			_win.Width = Dim.Fill ();
+			_win.Height = Dim.Fill ();
+		}
+
+		private void WithMargin ()
+		{
+			_win.X = 5;
+			_win.Y = 5;
+			_win.Width = Dim.Fill (22);
+			_win.Height = Dim.Fill (5);
+		}
+
+		public override void Run ()
+		{
+		}
+	}
+}

+ 0 - 20
UICatalog/UICatalog.cs

@@ -66,7 +66,6 @@ namespace UICatalog {
 		private static bool _useSystemConsole = false;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 		private static bool _heightAsBuffer = false;
-		private static bool _alwaysSetPosition;
 		private static bool _isFirstRunning = true;
 
 		static void Main (string [] args)
@@ -152,7 +151,6 @@ namespace UICatalog {
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.Init ();
 			Application.HeightAsBuffer = _heightAsBuffer;
-			Application.AlwaysSetPosition = _alwaysSetPosition;
 
 			// Set this here because not initialized until driver is loaded
 			_baseColorScheme = Colors.Base;
@@ -311,7 +309,6 @@ namespace UICatalog {
 			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 			menuItems.Add (new MenuItem [] { null });
 			menuItems.Add (CreateSizeStyle ());
-			menuItems.Add (CreateAlwaysSetPosition ());
 			menuItems.Add (CreateDisabledEnabledMouse ());
 			menuItems.Add (CreateKeybindings ());
 			return menuItems;
@@ -349,23 +346,6 @@ namespace UICatalog {
 			return menuItems.ToArray ();
 		}
 
-		static MenuItem [] CreateAlwaysSetPosition ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Always set position (NetDriver only)";
-			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
-			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = Application.AlwaysSetPosition;
-			item.Action += () => {
-				Application.AlwaysSetPosition = !item.Checked;
-				item.Checked = _alwaysSetPosition = Application.AlwaysSetPosition;
-			};
-			menuItems.Add (item);
-
-			return menuItems.ToArray ();
-		}
-
 		static MenuItem [] CreateSizeStyle ()
 		{
 			List<MenuItem> menuItems = new List<MenuItem> ();

+ 0 - 2
UnitTests/ApplicationTests.cs

@@ -44,7 +44,6 @@ namespace Terminal.Gui.Core {
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Current);
 			Assert.Throws<ArgumentNullException> (() => Application.HeightAsBuffer == true);
-			Assert.False (Application.AlwaysSetPosition);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
@@ -57,7 +56,6 @@ namespace Terminal.Gui.Core {
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Current);
 			Assert.False (Application.HeightAsBuffer);
-			Assert.False (Application.AlwaysSetPosition);
 			Assert.NotNull (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);

+ 106 - 1
UnitTests/ConsoleDriverTests.cs

@@ -1,14 +1,23 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
+using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.ConsoleDrivers {
 	public class ConsoleDriverTests {
+		readonly ITestOutputHelper output;
+
+		public ConsoleDriverTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Init_Inits ()
 		{
@@ -503,5 +512,101 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.True (closed);
 			Assert.Empty (FakeConsole.MockKeyPresses);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
+		{
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = @"これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。"
+			};
+			var win = new Window ("ワイドルーン") { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (tv);
+			Application.Top.Add (win);
+			var lbl = new Label ("ワイドルーン。");
+			var dg = new Dialog ("テスト", 14, 4, new Button ("選ぶ"));
+			dg.Add (lbl);
+			Application.Begin (Application.Top);
+			Application.Begin (dg);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 10);
+
+			var expected = @"
+┌ ワイドルーン ──────────────┐
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+│これは ┌ テスト ────┐ です。│
+│これは │ワイドルーン│ です。│
+│これは │  [ 選ぶ ]  │ です。│
+│これは └────────────┘ です。│
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 10), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Write_Do_Not_Change_On_ProcessKey ()
+		{
+			var win = new Window ();
+			Application.Begin (win);
+			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
+
+			System.Threading.Tasks.Task.Run (() => {
+				System.Threading.Tasks.Task.Delay (500).Wait ();
+				Application.MainLoop.Invoke (() => {
+					var lbl = new Label ("Hello World") { X = Pos.Center () };
+					var dlg = new Dialog ("Test", new Button ("Ok"));
+					dlg.Add (lbl);
+					Application.Begin (dlg);
+
+					var expected = @"
+┌──────────────────┐
+│┌ Test ─────────┐ │
+││  Hello World  │ │
+││               │ │
+││               │ │
+││     [ Ok ]    │ │
+│└───────────────┘ │
+└──────────────────┘
+";
+
+					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
+					dlg.Redraw (dlg.Bounds);
+
+					expected = @"
+┌──────────────────┐
+│┌ Test ─────────┐ │
+││  Hello World  │ │
+││               │ │
+││               │ │
+││     [ Ok ]    │ │
+│└───────────────┘ │
+└──────────────────┘
+";
+
+					pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+					win.RequestStop ();
+				});
+			});
+
+			Application.Run (win);
+			Application.Shutdown ();
+		}
 	}
 }

+ 4 - 2
UnitTests/GraphViewTests.cs

@@ -159,8 +159,10 @@ namespace Terminal.Gui.Views {
 			}
 
 			// Remove unnecessary empty lines
-			for (int r = lines.Count - 1; r > h - 1; r--) {
-				lines.RemoveAt (r);
+			if (lines.Count > 0) {
+				for (int r = lines.Count - 1; r > h - 1; r--) {
+					lines.RemoveAt (r);
+				}
 			}
 
 			// Remove trailing whitespace on each line

+ 53 - 51
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;
 		}
 
 		/// <summary>
@@ -48,59 +48,61 @@ namespace Terminal.Gui {
 			List<Type> scenarioClasses = Scenario.GetDerivedClasses<Scenario> ();
 			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
+					FakeConsole.MockKeyPresses.Clear ();
+					int stackSize = CreateInput ("");
+
+					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 ms;
+					if (scenarioClass.Name == "CharacterMap") {
+						ms = 2000;
+					} else {
+						ms = 1000;
+					}
+					var abortCount = 0;
+					Func<MainLoop, bool> abortCallback = (MainLoop loop) => {
+						abortCount++;
+						Application.RequestStop ();
+						return false;
+					};
+					var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
 
-				// Setup some fake keypresses 
-				// Passing empty string will cause just a ctrl-q to be fired
-				Console.MockKeyPresses.Clear ();
-				int stackSize = CreateInput ("");
+					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.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+					//Application.End (rs);
 
-				int iterations = 0;
-				Application.Iteration = () => {
-					iterations++;
-					// Stop if we run out of control...
-					if (iterations > 10) {
-						Application.RequestStop ();
+					// 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}");
 					}
-				};
 
-				int ms;
-				if (scenarioClass.Name == "CharacterMap") {
-					ms = 1500;
-				}else {
-					ms = 1000;
+					Assert.Equal (0, abortCount);
+					// # of key up events should match # of iterations
+					Assert.Equal (1, iterations);
+					Assert.Equal (stackSize, iterations);
 				}
-				var abortCount = 0;
-				Func<MainLoop, bool> 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) {

+ 266 - 4
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 (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 (0, 0, 7, 3), pos);
+			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 		}
 	}
 }